shashank created CAMEL-25569:
--------------------------------
Summary: camel-aws2-kinesis - after an expired shard iterator the
consumer starts the shard over (TRIM_HORIZON: all records again, LATEST:
records skipped), and with LATEST it skips the first records of a shard created
by resharding
Key: CAMEL-25569
URL: https://issues.apache.org/jira/browse/CAMEL-25569
Project: Camel
Issue Type: Bug
Components: camel-aws2-kinesis
Reporter: shashank
Assignee: shashank
{{Kinesis2Consumer.getShardIterator}} requests every new shard iterator with
the configured {{iteratorType}} (line 265 at main 5987a263107b). That is the
position to start a shard at when the consumer starts. It is also used in two
cases where the consumer already has a position:
*1. The cached iterator has expired.* A shard iterator is valid for 5 minutes.
Since CAMEL-24156 the consumer catches {{ExpiredIteratorException}}, removes
the cached iterator and calls {{getShardIterator}} again (lines 159-175), which
requests the configured type:
* {{TRIM_HORIZON}} (the default): the consumer routes all the records of the
shard again, from the oldest record of the retention period (24 hours by
default).
* {{LATEST}}: the records stored since the last poll are skipped.
* {{AT_TIMESTAMP}}, {{AT_SEQUENCE_NUMBER}}, {{AFTER_SEQUENCE_NUMBER}}: the
consumer goes back to the configured position.
The only log line is the WARN "Shard iterator expired ..., requesting a fresh
one". An iterator expires when the consumer does not poll the shard for 5
minutes: the route was suspended for longer ({{suspendRoute}} /
{{resumeRoute}}, the same consumer continues), {{delay}} or the back-off
({{backoffMultiplier}}) is above 5 minutes, or the route takes that long for
the records of one poll. (A route that is stopped and started gets a new
consumer, which starts from the configured type; that is not changed.) With a
resume strategy the request is then changed by its adapter, as for the first
iterator.
*2. A shard created by resharding.* When a shard is split or merged while the
consumer runs, the shard monitor (every 10 seconds by default, lines 466-477)
adds the new shards to the list, and the consumer requests their first iterator
with the configured type. With {{LATEST}} the records stored in a new shard
before that request are skipped. The consumer was reading the parent shard, so
every record of the new shard is new for it.
For comparison, the aws2-ddbstream consumer of the same component family does
both on main: after an expired iterator it requests {{AFTER_SEQUENCE_NUMBER}}
with the last sequence number it saw ({{Ddb2StreamConsumer}} lines 77-85,
{{ShardIteratorHandler.requestFreshShardIterator}} lines 108-115), and it reads
child shards from {{TRIM_HORIZON}} ({{ShardIteratorHandler}} line 77).
h3. Reproduction
New {{KinesisConsumerPositionTest}} (5 tests): the Kinesis client is a Mockito
mock backed by a small in-memory stream (shards with records, iterators that
are positions and can be expired, parent/child shards); the scheduler is not
started and the test calls {{poll()}}. On main 3 tests fail:
* {{expiredIteratorContinuesAfterTheLastRoutedRecord}} ({{TRIM_HORIZON}}; a, b
routed; iterators expire; c stored): {{only the record that was not routed yet
==> expected: <1> but was: <3>}} (a and b are routed again).
* {{expiredIteratorDoesNotSkipTheRecordsOfTheGapWithLatest}}: {{the record
stored while the iterator expired ==> expected: <1> but was: <0>}}.
* {{shardCreatedByReshardingIsReadFromItsStartWithLatest}}: {{the records
stored in the child shards before the consumer saw these shards ==> expected:
<[a, b, c]> but was: <[a]>}} (the records of the two child shards are skipped).
The other two pass on main and guard what must not change: a shard without
routed records gets the configured type after an expiry, and the shards of the
first shard list are read from {{LATEST}} also when they are child shards of an
earlier resharding.
h3. Proposed fix
In {{Kinesis2Consumer}}:
* remember the sequence number of the last record of each response per shard,
and request {{AFTER_SEQUENCE_NUMBER}} with it when a new iterator is needed for
that shard;
* in the shard monitor, mark a shard that appears after the first list and
descends from a known shard, and request {{TRIM_HORIZON}} for it;
* otherwise the configured type, as before. The resume strategy is applied
after that, as before.
Known limit: with {{LATEST}}, when the iterator of a shard expires before any
record was routed from it, the new iterator is {{LATEST}} again and the records
stored in between are skipped, as on main. The three per-shard collections of
the fix ({{lastSequenceNumbers}}, {{knownShardIds}}, {{childShardIds}}) are not
pruned when a shard leaves the shard list, like {{currentShardIterators}} on
main.
Behaviour change, with a 4.24 upgrade-guide entry: no repeated or skipped
records after an expired iterator; child shards are read from their start
whatever the {{iteratorType}}. No new option. camel-aws2-kinesis unit tests
with the fix: 46, 0 failures (the integration tests need LocalStack and were
not run).
Found with a TLA+ model of the consumer position (shards as record logs,
iterator expiry between polls, a split, the shard monitor): on main TLC
violates {{NoDuplicate}} with {{TRIM_HORIZON}} (13 states: route record 1, the
iterator expires, the next poll gets a new iterator at the start, record 1 is
routed again) and {{NoSkip}} with {{LATEST}} (after an expiry, and for the
child shard after a split: 10 states); both hold with the fix, except the known
limit above. Then confirmed with the real consumer as described.
Affected: main and camel-4.22.x (the expired-iterator handling of CAMEL-24156;
read, not run). camel-4.18.x and camel-4.14.x do not catch
{{ExpiredIteratorException}} (the consumer fails every poll instead) and have
the resharding case (line 260 on camel-4.18.x; read, not run).
Duplicate check (2026-10-12): JIRA text "kinesis shard iterator expired" (1:
CAMEL-24156, which added the handling this ticket is about), "Kinesis2Consumer
AFTER_SEQUENCE_NUMBER" (1: CAMEL-24156), "kinesis child shard" (none), "kinesis
resharding" (3: CAMEL-19789 repeated first record, CAMEL-12179 KCL consumer,
CAMEL-24156; none about the position of new shards), "kinesis split shard" (4,
same tickets plus CAMEL-24197 and CAMEL-24191); GitHub pull requests
"Kinesis2Consumer expired iterator": #24863 (merged, CAMEL-24156); no open pull
request touches {{Kinesis2Consumer}} (#26871 touches {{KclKinesis2Consumer}}
only).
_Filed with Claude Code on behalf of allthingssecurity._
--
This message was sent by Atlassian Jira
(v8.20.10#820010)