Federico Mariani created CAMEL-24154:
----------------------------------------

             Summary: camel-aws2-ddb - DDB Streams consumer never subscribes to 
shards created after startup - silent data loss on resharding
                 Key: CAMEL-24154
                 URL: https://issues.apache.org/jira/browse/CAMEL-24154
             Project: Camel
          Issue Type: Bug
          Components: camel-aws
            Reporter: Federico Mariani
         Attachments: ShardIteratorHandlerReshardingTest.java

Found during a code review of camel-aws2-ddb on main (4.22.0-SNAPSHOT).

In {{ShardIteratorHandler.getShardIterators()}} (ddbstream, lines ~49-77), 
{{shardTree.populate(...)}} is only called when 
{{currentShardIterators.isEmpty()}} — i.e. on the first poll, or after *all* 
tracked shards have been drained and removed. On every later poll the 
else-branch resolves successors of a closed shard against that stale snapshot: 
{{shardTree.getChildren(shardId)}}. Children created by DynamoDB's routine 
shard rotation (roughly every 4 hours) or by a split *after* the initial 
{{describeStream}} are not in the tree, so {{children.isEmpty()}} is true and 
the closed shard is treated as "still an active leaf". Once its records are 
drained, {{updateShardIterator(shardId, null)}} removes it and its lineage is 
never followed.

While at least one other shard remains active the iterator map never empties, 
so the tree is never refreshed:
* On a table with 2+ partitions, after any partition's shard rotates, *all 
subsequent writes to that partition are silently never delivered* — for hours, 
until every other shard also happens to close.
* On a single-partition table the map does empty, but re-initialization uses 
the *configured* iterator type: with the default {{FROM_LATEST}} the records 
written between the parent closing and re-initialization are skipped; with 
{{FROM_START}} the entire 24h trim-horizon backlog is re-delivered as 
duplicates every ~4h.

Related defects in the same class (kept here for context, listed in the 
umbrella): {{getShardIterators()}} returns the internal HashMap by reference 
while {{updateShardIterator}} structurally modifies it during the consumer's 
iteration (ConcurrentModificationException when a shard retires); 
{{describeStream}} pagination ({{lastEvaluatedShardId}}) is ignored so streams 
with >100 shards lose partitions; and {{requestFreshShardIterator}} sends 
{{AFTER_SEQUENCE_NUMBER}} with a null sequence number when the iterator expired 
before any record was seen, leaving the consumer permanently stuck.

*Reproducer*: attached {{ShardIteratorHandlerReshardingTest.java}} (unit test 
with a mutable mock streams client, goes under 
{{components/camel-aws/camel-aws2-ddb/src/test/java/org/apache/camel/component/aws2/ddbstream/}}).
 Two active shards A and B; A rotates into children A1/A2; after A is drained 
the handler keeps polling only B:
{noformat}
children of the rotated shard-A must be consumed, but the handler returned 
only: {shard-B=iterator-B}
{noformat}

*Suggested fix*: when a tracked shard closes (children lookup empty or 
{{nextShardIterator == null}}), re-issue a paginated {{describeStream}} to 
refresh the tree and start the closed shard's children at {{TRIM_HORIZON}}.

_This issue was researched and filed by Claude Code (AI) on behalf of Federico 
Mariani (fmariani). Full findings document from the review is attached to the 
umbrella issues._



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to