Repository navigation
Conversation
A unique value the source moves between a landed key and a key in flight collides on the chunk's insert until the applier has flushed the move; the copy used to stop on the first 23505. Each attempt re-reads the source, so the chunk lands once the flush commits. The catch-up convergence test for unique-key swaps now runs its load throughout the copy.
Resolves docs/copy-and-swap-design.md (copier package row: main's conversion-fingerprint guard kept, this branch's collision-retry tail kept) and docs/invariants.md (CO-4: main's sync-commit xid gate now Enforced, followed by this branch's copier-side collision retry and the remaining PK-move-with-unique-secondary gap).
A primary-key move whose row keeps a unique value, from a landed key to a key whose chunk is in flight, deadlocked the drain against the copier: the pair rule deferred the old key's delete until the chunk landed, and the chunk's insert collided with that row until the delete flushed. The pair now waits only for the old key; the image completes from the source row.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
🤖 Reviewed by Jash's AI agent at The directional dependency in Non-blocking test suggestion at Validation: all 16 reported CI checks pass, including PostgreSQL 14–18. I did not run a local build, tests, or a database reproduction. The exact-head contents API failed, so I could not independently inspect unchanged completion/hold helpers, the fixture setup, or repository-specific instructions outside the supplied diff. Those are limits of this review, not verified behavior. |
JashLal
left a comment
There was a problem hiding this comment.
🤖 Approving on Jash's behalf after the full-diff review at 45d0c05. No concrete blocker found; the optional real-copier collision/retry test and surrounding-source verification limits are recorded in the review. No local build or tests were run. Generated by Codex.
Resolves the three conflicts #239 landing on main left: the design doc's pkg/checksum row takes main's recopy-collision text (this branch never changed it), and invariants.md plus the key-move test comment keep this branch's wording, which supersedes the CO-4 *Open* paragraph #239 wrote for the landed-to-in-flight PK move.
The drain no longer holds a landed old key's delete back for a moved image whose own chunk is still in flight, so a primary-key move that keeps a unique value can land while the copy is running.
Why
A row's primary key moves to a fresh value: the stream delivers an image under the new key paired with a delete under the old one. The drain's pair rule deferred both halves whenever either was inside an in-flight chunk, so that the flush never deleted the old key's row while the image still needed it to complete an unchanged out-of-line column. When the moved row also carries a unique value and the move goes from a landed key to a key in flight, that rule deadlocks against the copier: the chunk's insert of the row under the new key collides with the shadow row still under the old key, the copier runs the chunk again and waits for the flush to remove that row, and the flush is waiting for the chunk to land before it will delete it. The copier's collision attempts run out and the catch-up fails.
docs/invariants.mdCO-4 recorded this as open and the key-move convergence test started its load only after the copy had landed.What
Buffer.Drain's pair rule is one-directional: a moved image waits while its old key's entry is in flight or invisible, and an old key's entry waits while its image is invisible, but a landed old key's delete flushes ahead of an image whose chunk is in flight. The deferred image stays buffered with its marker; when its chunk lands,CompleteFirstnames it, the flush finds the old key's shadow row gone, and completes the image from the source row under its own key instead — the fallback that already served a reused old key, held until the stream passes the read.TestCatchupFlushesALandedOldKeyAheadOfItsInFlightImageholds the new key's chunk in flight by hand, moves a landed row's primary key into it, asserts the old key's shadow row is deleted while the image stays deferred, then lands the chunk and asserts convergence; without the drain change both halves stay deferred until the timeout.docs/invariants.mddescribes the one-directional rule, records the primary-key-move case as enforced, and states the bound that remains: the chunk-level retry still runs out when unique values keep moving into one chunk's range faster than one move per catch-up cycle, each attempt colliding with a different row.Before / after
seats(id PK, uniq UNIQUE). Row(7, 'A')is copied into the shadow under the landed key 7. The source then moves it:UPDATE seats SET id = 1600 WHERE id = 7, and key 1600 is inside the chunk the copier is reading. The only thing that changes is whether the delete under key 7 waits for chunk 1600; what the flush does with the image once chunk 1600 has landed — complete it from a row, then upsert it — is unchanged.The other direction is unchanged: when the old key is the one in flight, the image still waits for it, because the chunk that will copy the old key's row has not written anything the delete could collide with, and the flush must not complete the image from a row that chunk has yet to copy.
🤖 Generated with Amp (Claude Opus 4.6)