Repository navigation
Conversation
…those A chunk whose insert collided reran the whole range after each backoff, and under sustained key moves each rerun collided on a different landed row, so the passes a chunk makes had no bound. The chunk now commits every row the shadow admits, names the rest as pending in its Position, and later passes insert only those keys.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A chunk whose insert collides with a shadow row now commits every row the shadow admits, keeps only the held-off keys pending, and retries those keys alone, so the passes a chunk makes stay bounded whatever the rate of key moves.
Why
The copier's collision retry reran the whole chunk — the strict
INSERT … ON CONFLICT (pk) DO NOTHINGover the full key range — after each backoff. The chunk collided as a whole on the first row whose unique value another shadow row still held, so none of its rows committed, and the next pass read the whole range again. Under sustained key moves into the chunk's range, each pass could collide on a different landed row the flush had not rewritten yet; a chunk could be sent back to the start as often as moves arrived, and whether it landed withinCollisionAttemptsdepended on the move rate, not on the bound. A move rate above one per backoff into one chunk's range was enough to exhaust the attempts on a chunk whose every row but one the shadow would have admitted.What
pkg/internal/chunksql: two statements besideInsert, selecting rows exactly as it does.InsertSkippingruns over the chunk's range withON CONFLICT DO NOTHINGon every unique index and exclusion constraint, and returns the rows inserted plus the ascending keys of the rows it read that are in the shadow neither now nor from before — the rows held off.InsertKeysis the same statement over abigint[]of keys. The source rows are read once into aMATERIALIZEDCTE so the insert and the held-off query see the same rows.pkg/copier: the first pass is the strict insert as before, so the chunk that collides on nothing — the common case — still costs one statement, andchunksql.Insertstays the one text the copier and the verifier share. On a collision the second pass runsInsertSkippingwith no wait and settles the chunk: it stays in flight, but itsPositionentry gainsPending{Chunk, Keys}, andClassifyreads every key of that chunk outsideKeysas landed. Each later pass waits one backoff longer than the one before and runsInsertKeysover the pending keys alone — never the range again, so a value that moves into the range after the second pass is the applier's to write. Pending keys only shrink (the flush rewrites the row holding a key's value and the next pass lands it, or the source deletes the row and the next pass no longer reads it); the chunk lands when they are gone. A pass refused as a whole (a deferred unique constraint at commit) is rerun as it was.CollisionAttemptsnow bounds passes, and an exhausted chunk stays in flight with its keys pending, so a checkpoint records none of it and the resume clear removes the rows committed around them.ledgertracks a settled chunk's rows and pending keys;RowsInsertedcounts the rows of every committed pass.chunkTxis the guarded transactioncopyChunk,copySkipping, andcopyKeysshare.chunksqlfreezes the new statements; ledger andPositionunit tests cover settling and the pending-key classification; the copier integration test holds slot 15 under shadow key 2000, asserts the first chunk commits around it with key 15 alone pending (key 16 classifies as landed, key 15 in flight, the shadow holds every row of the chunk but one), then removes the row and asserts the chunk lands with every row; the exhaustion test asserts the chunk stops with key 15 still pending and absent. A new catch-up proof runs primary-key moves faster than one per backoff from before the first chunk is read, so every move lands in the last chunk while it is in flight, asserts the copier counted collisions (the moves demonstrably reached an in-flight chunk), and converges within the attempts.invariants.md, thepkg/copierrow of the design doc, and theOptionsandPositiondoc comments.Before / after
The seats table has
idas the primary key and a uniqueslot. The first chunk covers keys 1–100 and is read while the shadow already holds a row under key 2000 whose slot is 15 (a move the applier wrote, whose other half — the delete of the row that used to hold slot 15 — the flush has not rewritten yet). Before, every pass reran all 100 rows and the whole chunk failed on key 15 each time; after, the second pass commits 99 rows, key 15 alone waits, and the pass after the row under key 2000 is rewritten inserts key 15 by itself. The applier can write keys 1–14 and 16–100 from the second pass onward instead of deferring every change to the chunk.An exhausted chunk reads the same on both sides for the applier — in flight — but after this change the rows committed around the pending key stay in the shadow until the resume clear removes them, and
Position.Pendingnames the key that never landed.🤖 Generated with Amp (Claude Opus 4.6)