Skip to content

CON-599: aws_dynamodb_cdc: stop re-reading exhausted closed shards - #5001

Merged
squiidz merged 25 commits into
mainfrom
jira/CON-599
Oct 28, 2026
Merged

squiidz merged 25 commits into
mainfrom
jira/CON-599

Conversation

@squiidz

@squiidz squiidz commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

What

aws_dynamodb_cdc stops re-reading closed shards it has already read to the end, both within a run and across restarts.

Why

The 5-minute exhausted-shard cleanup dropped a finished reader, and the next shard refresh rediscovered the closed shard (still listed for up to 24h) and read it again from its lagging checkpoint, redelivering records. The final page of a closed shard (nil NextShardIterator) was also dropped.

How

  • In memory: a completedShards set (single-table input and each tableStream) records shards read to the end; refreshShards/refreshTableShards skip them, and entries are pruned once DescribeStream stops listing the shard. Records on the final nil-iterator page are delivered.
  • Persisted: RecordBatcher.CloseShard finalizes a closed shard once its last batch settles. If every batch was acked, Checkpointer.MarkShardCompleted writes the final position plus a ShardCompleted marker with UpdateItem, keeping the stored SequenceNumber and, in global mode, ApproximateCreationTime. If a batch was nacked, only the pinned frontier is written. A restart resolves a marked shard to resumeCompleted and skips it. This also persists the closed-shard tail, which covers most of CON-598.
  • A failed completion write is retried on every shard refresh (RetryFinalize, folded in from CON-601: aws_dynamodb_cdc: retry a failed shard completion write on refresh #5015) and counted in checkpoint_failures. A write canceled by shutdown is logged at debug.
  • Child shards wait for their parents to finish, except while start_from: latest is still honored. A parent that is gone is treated as done, and one that fails to prepare 5 cycles in a row releases its children; the spec lists all three exceptions. A child still waiting after 20 refresh cycles (about 10 minutes) logs a warning naming the parent, repeated every 20 cycles. An exact resume that hits TrimmedDataAccessException falls back to TRIM_HORIZON.
  • The shard readers' iterator refresh calls (trimmed shard and expired iterator) now go through acquireShardIterator, so a hung GetShardIterator times out instead of stalling the shard and its children.
  • A reader that gives up unregisters itself and triggers a refresh, so the shard is prepared again right away. In global mode that re-prepare reads the shard's current row, not the startup cache; the set tracking which shards were resolved is pruned with the shard listing.
  • Global mode failover ignores another region's completed rows that carry no ApproximateCreationTime, so they cannot force a full TRIM_HORIZON replay.
  • Docs: the checkpoint table IAM policy now needs dynamodb:UpdateItem.

Review focus

The persisted completion marker (MarkShardCompleted, ResolveResume/prepareResume, and CloseShard/finalizeShard in batcher.go), the parent-before-child gating in shard discovery, and the global-table resume paths.

Testing

  • Unit tests for each fix (each fails without it), go test -race ./internal/impl/aws/...
  • dynamodb integration tests (-tags integration) against dynamodb-local
  • golangci-lint clean, all four distributions build
  • Not yet run: real-AWS check of the completion marker, especially in global-table mode

Jira: https://redpandadata.atlassian.net/browse/CON-599

Comment thread internal/impl/aws/dynamodb/input_cdc.go Resolved
@squiidz
squiidz marked this pull request as ready for review October 21, 2026 15:21
Comment thread internal/impl/aws/dynamodb/batcher.go Resolved
@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Connect docs check

The docs build passed with this PR's generated docs.

Preview: https://connect-pr-5001--redpanda-connect.netlify.app/connect/home/

This PR changes 1 published page (3 generated files: 3 changed, 0 added, 0 removed). Review the rendered pages, not only the Go strings.

Page Preview
/connect/components/inputs/aws_dynamodb_cdc/ preview

Per-file diffs and the rendered-HTML findings are in the job summary. Checked at 1db8f68.

@Leward Leward left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR body is out of date with the code at.

It says: "The set is in memory only, so a restart re-reads each retained closed shard once." However, the code now persists completion (MarkShardCompleted).

Please update the body so that reviewers check the persisted marker and these changes.

Comment thread internal/impl/aws/dynamodb/batcher.go Resolved
Comment thread internal/impl/aws/dynamodb/input_cdc.go Resolved
@Leward

Leward commented Oct 27, 2026

Copy link
Copy Markdown
Contributor

#5015 fixes a gap in the finalization this PR adds, not a bug on main: CloseShard, finalizing and forgetShards do not exist there yet. If this PR merges alone, it ships the leak #5015 fixes. Can you fold #5015 into this PR as a commit and close #5015?

Non-blocking design point, for a follow-up PR. Checkpoint writes only run in reaction to an event (ack, CloseShard, last settle). Nothing owns a failed write, so each gap gets its own retry path. With #5015 there are four:

finalizing and closeCP exist only to coordinate these paths:

Proposal: one persister per batcher. The tracker keeps the wanted state per shard (frontier, frontier time, completed) and the persisted state. One goroutine writes the difference. Acks and closes wake it, and a timer with backoff retries it. This removes finalizing, closeCP, RetryFinalize, and the persistMu single-flight. AckBatch would stop returning write errors, so that change needs its own review. Can you open a ticket for it?

@squiidz
squiidz requested a review from Leward October 27, 2026 12:23
@squiidz

squiidz commented Oct 27, 2026

Copy link
Copy Markdown
Contributor Author

Folded in: #5015's commit 21cdd6f is on this branch unchanged, and #5015 is closed out (it was based on this branch). I've also updated the PR body to cover the persisted completion marker, the RetryFinalize path and the global-mode changes. Agreed on the single persister per batcher. I'll open a ticket for it and link it here, as a separate PR since AckBatch no longer returning write errors needs its own review.

@Leward Leward left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes for one item: a child shard can wait for its parent shard for up to 24 hours, and no warning is logged. See the inline comment on parentDone.

The fix I ask for is small: log a warning when a child shard waits for its parent for longer than a limit. It can go in this PR or in a PR stacked on this branch, as #5015 was. I will approve once it is in this branch.

The other inline comments are non-blocking. The two global table fixes and the RetryFinalize retry look correct, and the new tests cover them.

Please also update the PR body. It still says "The set is in memory only, so a restart re-reads each retained closed shard once". That is no longer true: MarkShardCompleted persists a ShardCompleted marker, and a restart skips the shard:

func (c *Checkpointer) MarkShardCompleted(ctx context.Context, shardID, sequenceNumber, approxCreationTime string) error {

The body also does not mention these changes:

  • A child shard waits until its parent shard is read to the end.
  • A trimmed checkpoint resumes from TRIM_HORIZON.
  • A failed completion write is retried on each shard refresh (CON-601).
  • The principal needs dynamodb:UpdateItem on the checkpoint table.

Comment thread internal/impl/aws/dynamodb/input_cdc.go Resolved
Comment thread internal/impl/aws/dynamodb/input_cdc.go Resolved
Comment thread internal/impl/aws/dynamodb/input_cdc.go Resolved
@squiidz
squiidz requested a review from Leward October 27, 2026 16:19
@squiidz

squiidz commented Oct 27, 2026

Copy link
Copy Markdown
Contributor Author

Updated the PR description to cover the items listed in the review, please have another look when you get a chance.

The Checkpointing section now says the principal needs dynamodb:UpdateItem
on the checkpoint table (MarkShardCompleted is an UpdateItem), and describes
the behaviour this PR adds: closed shards read to the end are marked
completed and skipped after a restart, a child shard is read only after its
parent is read to the end (except on the first discovery under
start_from: latest), and a checkpoint pointing at trimmed stream data
resumes from TRIM_HORIZON with a warning. max_tracked_shards now says a
shard is normally forgotten once read to the end and acknowledged.
…hildren

A child shard was held back for as long as its parent stayed listed and
unfinished, so a parent that could not be prepared for a reason other than
trimmed data kept its whole lineage unread until it aged out of the listing
(up to 24h). Discovery now remembers failed prepares across refresh cycles:
a parent whose GetShardIterator returns ResourceNotFoundException is treated
as done for ordering right away, and a parent that fails to prepare on 5
consecutive refresh cycles releases its children with a single warning.
Neither writes a completion marker for the parent, which is still retried
and read if it becomes readable.
GetShardIterator returns no iterator for a closed shard with nothing left
to read, and an iterator refresh can do the same. The reader treated a nil
iterator as "stop" and returned silently, leaving the shard registered but
unread: it was never marked completed and its children stayed held back
behind it. A registered, unfinished reader holding a nil iterator now takes
the closed-shard path (completeShard / completeTableShard), which persists
the completion marker and releases the children.
…utdown

A failed write of a shard's completion marker was logged but not counted,
so dynamodb_cdc_checkpoint_failures missed it. completeShard and
completeTableShard now increment the metric when the write fails. A write
that failed only because the reader's context was canceled (shutdown) is
expected: it is logged at debug instead of error and not counted.
…rdinator

Adds coverage for two paths the PR changed without a direct test:
syncShardReaders restarting a reader that gave up and was re-registered
under the same shard ID (the old goroutine is canceled and a fresh one is
started for the new reader), and a multi-table variant of
TestShardCoordinatorReadsClosedShardOnce driving the table stream
coordinator. The in-memory checkpoint store shared by both coordinator
tests moves into lockedStoreCheckpointer.

@Leward Leward left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The blocking item is in: a child shard held back behind its parent now logs a warning after 20 refresh cycles and again every 20, and the test covers both the single-table and multi-table coordinators. The iterator refreshes in both readers go through acquireShardIterator, the spec lists all three cases where a child is read before its parent, and ForgetResolved bounds the global-mode resolved set. The PR body now matches the code. Two non-blocking suggestions inline, both fine as follow-ups. Please link the single-persister ticket here when it exists.

Comment thread internal/impl/aws/dynamodb/input_cdc.go Resolved
@squiidz
squiidz requested a review from Leward October 28, 2026 15:27
Comment thread internal/impl/aws/dynamodb/input_cdc.go Resolved
@squiidz

squiidz commented Oct 28, 2026

Copy link
Copy Markdown
Contributor Author

Please link the single-persister ticket here when it exists.

@Leward the single-persister follow-up is CON-613: https://redpandadata.atlassian.net/browse/CON-613. It will be a separate PR, since AckBatch no longer returning write errors needs its own review.

@squiidz
squiidz merged commit 7b3b0eb into main Oct 28, 2026
15 checks passed
@squiidz
squiidz deleted the jira/CON-599 branch October 28, 2026 16:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants