Repository navigation
[Spanner Change Stream] Claim CreatedAt timestamp before scheduling partitions in DetectNewPartitionsAction - #40870
Conversation
…artitions in DetectNewPartitionsAction
|
@chenxuesdu @scwhittle could you please review this PR? Thanks! |
|
Assigning reviewers: R: @nielm for label spanner. Note: If you would like to opt out of this review, comment Available commands:
The PR bot will only process comments in the main thread (not review comments). |
|
Actually, I'm not sure this would lead to lost things since if we haven't claimed the offset then on restart we would resume from the last claimed position. |
| for (Map.Entry<Timestamp, List<PartitionMetadata>> batch : batches.entrySet()) { | ||
| numTimestampsHandledSofar++; | ||
| final Timestamp batchCreatedAt = batch.getKey(); | ||
| if (!tracker.tryClaim(batchCreatedAt)) { |
There was a problem hiding this comment.
I think that this breaks how restrictions are supposed to be used.
If a claim fails, none of the work associated after the previous successful claim should be performed. See the design doc: https://docs.google.com/document/d/1AQmx-T9XjSi1PNoEp5_L-lT0j7BkgTbmQnc6uFEMI4c/edit?tab=t.0#heading=h.3624h6581nxd
I think that the existing logic should:
- should move the call to updateBatchToScheduled to be after tryClaim succeeds
- Could possibly be changed to perform claims for each timestamp as it iterates over batches instead of the batches. And then once all timestamps are claimed, the batch gets too lare or upon claim failure the already claimed work could be updated to scheduled and output. That said, dataflow ensures that no splits are performed if there hasn't yet been a single claim, so it seems the existing logic would claim the entire first batch regardless and may make more progress if polling partitions are slow.
There was a problem hiding this comment.
Thanks for catching this! Agreed — performing work or emitting outputs after tryClaim returns false violates the RestrictionTracker contract.
Updated per (1) to keep the existing 200-partition batching and move updateBatchToScheduled to after tryClaim(batchCreatedAt) succeeds. Also overrode DetectNewPartitionsRangeTracker.trySplit to return null when fractionOfRemainder != 0 (matching Bigtable Change Streams' DetectNewPartitionsTracker), so only checkpoints (fractionOfRemainder == 0) split the coordinator restriction and a split can never bisect an unclaimed cross-timestamp batch or spawn an idle future restriction. PTAL!
…-checkpoint splits
@scwhittle That's true for checkpoints ( Addressed both in the latest commit by moving |
| * so only a single active restriction polls and schedules new partitions. | ||
| */ | ||
| @SuppressWarnings({ | ||
| "nullness" // TODO(https://github.com/apache/beam/issues/20497) |
There was a problem hiding this comment.
is it possible to remove this?
There was a problem hiding this comment.
Done — removed @SuppressWarnings("nullness") and switched position.equals(lastAttemptedPosition) to Objects.equals(position, lastAttemptedPosition) so Checker Framework passes cleanly. Thanks for the review!
Summary
Fixes partition stranding and SDF
RestrictionTrackerordering inDetectNewPartitionsDoFn:DetectNewPartitionsAction.schedulePartitionspreviously calledupdateBatchToScheduled(batchPartitionsDifferentCreatedAt)(committingState = SCHEDULEDin Spanner) before callingtracker.tryClaim(batchCreatedAt). IftryClaimfailed, partitions were mutated toSCHEDULEDwithout being emitted, violating the SDFRestrictionTrackercontract.DetectNewPartitionsRangeTrackerinherited non-checkpoint dynamic splitting (fractionOfRemainder != 0) fromTimestampRangeTracker. BecauseDetectNewPartitionsDoFnis a singleton partition-discovery coordinator over[start, Timestamp.MAX_VALUE), a non-checkpoint split (fractionOfRemainder > 0) could both spawn an idle future restriction and split mid-batch (T1 < T_split <= Tk) after an earlier batch was claimed, allowingtryClaim(Tk)to fail and satisfycheckDone()while strandingT1(< T_split) below the residual[T_split, MAX).updateBatchToScheduledto aftertracker.tryClaim(batchCreatedAt)succeeds inDetectNewPartitionsAction.schedulePartitions, immediately returningProcessContinuation.stop()without performing Spanner writes or emitting outputs whentryClaimreturnsfalse, while preserving the 200-partition cross-timestamp batching.DetectNewPartitionsRangeTracker.trySplit(matching Bigtable Change Streams'DetectNewPartitionsTracker) to refuse non-checkpoint splits (fractionOfRemainder != 0), while continuing to delegate checkpoints (fractionOfRemainder == 0) so every split boundary aligns withlastClaimedPosition + 1ns(orfromif unclaimed) and never bisects an unclaimed batch.Testing
DetectNewPartitionsActionTestto verify:< 200partitions) claims the batch's latestCreatedAttimestamp beforedao.updateToScheduledand outputs all partitions in the batch.ProcessContinuation.stop()without callingdao.updateToScheduledorreceiver.outputWithTimestampwhentryClaimreturnsfalse(both for the first batch and for a subsequent batch after a full 200-partition batch).DetectNewPartitionsRangeTrackerTestverifyingtrySplitreturnsnullwhenfractionOfRemainder != 0and checkpoints cleanly whenfractionOfRemainder == 0.