crdb stack/6 resolved buffering - #4710
Conversation
|
Code review One issue found. Checked for bugs and CLAUDE.md compliance. (The inline-comment tool was unavailable in this session, so this is posted as a top-level comment with permalinks.) Grace-deadline batch cut discards the buffer, so the batch can return zero records and the mirror stalls peerdb/flow/connectors/cockroachdb/cdc.go Lines 478 to 484 in 3388ef7 This branch returns peerdb/flow/connectors/cockroachdb/cdc.go Lines 687 to 693 in 3388ef7 Since The result: Suggested fix: either force-emit before cutting the batch here, or gate the grace deadline on |
3388ef7 to
314cb61
Compare
🔄 Flaky Test DetectedAnalysis: All four failures are fixed wall-clock deadline expiries (60s mirror-startup budget in SetupCDCFlowStatusQuery, plus one EnvWaitFor resync timeout) in MySQL/MariaDB→ClickHouse e2e tests on a CockroachDB-focused branch, with disjoint failure sets across the two failing matrix jobs and a third matrix job passing all of them — consistent with runner contention rather than a regression. ✅ Automatically retrying the workflow |
314cb61 to
37584f3
Compare
jgao54
left a comment
There was a problem hiding this comment.
one small nit, otherwise lgtm!
| // stashRecord buffers a converted record under its commit timestamp and row | ||
| // key. A replayed message (same timestamp, table and key) overwrites its | ||
| // earlier copy instead of queueing a duplicate. | ||
| func (state *changefeedPullState) stashRecord( |
There was a problem hiding this comment.
ah this is neat, since it also dedupes duplicated messages send from server-side retries
There was a problem hiding this comment.
Yeah that fell out of keying the buffer by timestamp and row key, reconnect and server side replays just land on the same slot.
| // no data loss: nothing buffered was emitted, so the persisted | ||
| // cursor still precedes these records and the next batch re-reads | ||
| // them from the changefeed | ||
| c.logger.Info("[cockroachdb] leaving records past the final resolved timestamp for the next batch", |
There was a problem hiding this comment.
nit: this logline would get a bit noisy as it would be emitted every PullRecord on a busy pipe, can be removed or emit at debug level
There was a problem hiding this comment.
Moved it to debug. And thanks for the review, and for all of them across the stack, the connector came out a lot better for how thorough you've been.
Events now wait in an in-memory buffer keyed by commit timestamp and row key, are emitted in commit order once a resolved timestamp at or beyond them arrives, and the checkpoint advances to that same value, so batches no longer re-deliver events on the happy path. Session reconnect replays land on the same buffer keys and collapse. The buffer is bounded by raw message bytes (128 MiB); crossing the cap or a stalled resolved stream force-emits past the checkpoint with a warning, which is the pre-existing at-least-once behavior. Records still buffered at batch end are left for the next batch to re-read from the cursor.
37584f3 to
9f51504
Compare
Follow-up to #4669, implementing the buffering discussed on the dedupe thread there and on Slack.
Changefeed events now wait in an in-memory buffer keyed by commit timestamp and row key. When a resolved timestamp arrives, everything it covers is emitted in commit order and the checkpoint advances to that same value, so nothing handed to the stream ever lies past the persisted checkpoint. Batch boundaries stop producing duplicates: records still buffered when a batch ends are dropped and re-read by the next batch from the cursor. Session reconnect replays land on the same buffer keys and collapse.
The buffer is bounded in bytes computed from the raw changefeed message sizes, not record count, since single events can be large. Crossing the cap (128 MiB) or a resolved stream that stalls past the batch deadline force-emits the buffer past the checkpoint with a warning, which is exactly the pre-existing at-least-once behavior, so the degraded mode is the previously reviewed semantics. Delivery overall remains at least once: a crash between emit and checkpoint persist still replays.
Validation: new unit tests for the replay-collapse, partial-emit-at-resolved and force-emit ordering paths, plus a new e2e test (Test_CDC_Exactly_Once_Across_Batches) asserting 82 events arrive exactly once across two consecutive pulls. Full suite run against a 5-node v26.2.5 cluster behind haproxy, 17/17 passing, and unit suites green.