Skip to content

feat: serialize backfill discovery writes to avoid optimistic concurrency races - #440

Closed
rouzwelt wants to merge 1 commit into
2026-10-02-rai-2834from
2026-10-02-rai-2835
Closed

rouzwelt wants to merge 1 commit into
2026-10-02-rai-2834from
2026-10-02-rai-2835

Conversation

@rouzwelt

@rouzwelt rouzwelt commented Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator

Backfill passes now serialize writes to the vault's inventory aggregate, eliminating optimistic-concurrency races that could stop the service at startup. Restarts after rollbacks find every returned receipt in one pass, and concurrent writes to the same aggregate were losing the race on every retry. Reads still run concurrently; only the aggregate writes run sequentially. Fixes RAI-2835. This is the top of a 10-PR stack.

Closes RAI-2835

Contributes to RAI-2601
Contributes to RAI-1215

Live effect: Service restarts after rollbacks succeed instead of failing on optimistic-concurrency conflicts. · Risk: Low (changes backfill write order, no money or keys). · Ships: On merge. · Blocks: None.

Decisions

  • Serialize writes in collection order, not batched or with retry backoff, to eliminate aggregate contention while keeping concurrent balance reads.

Risks

  • Sequential writes add latency to large passes, but balance reads (the network bottleneck) remain concurrent and writes are fast in-process.

Proof

  • New test with 65 discoveries completes without any optimistic-concurrency conflicts.

Rollout

  1. Deploy and watch startup logs after next rollback for absence of "optimistic-concurrency conflict" errors.
  2. Rollback: Revert to previous version; large passes may fail and require manual restarts until fixed.

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

@linear-code

linear-code Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

RAI-2835

RAI-1215

RAI-2601

rouzwelt commented Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator Author

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more


How to use the Graphite Merge Queue

Add the label add-to-gt-merge-queue to this PR to add it to the merge queue.

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has enabled the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

This stack of pull requests is managed by Graphite. Learn more about stacking.

@rouzwelt rouzwelt self-assigned this Oct 2, 2026
@rouzwelt
rouzwelt force-pushed the 2026-10-02-rai-2835 branch from 1aa326e to 9e6fbb5 Compare October 2, 2026 04:13
@rouzwelt
rouzwelt force-pushed the 2026-10-02-rai-2834 branch 2 times, most recently from 8c879a6 to 867bbec Compare October 2, 2026 04:58
@rouzwelt
rouzwelt force-pushed the 2026-10-02-rai-2835 branch from 9e6fbb5 to 959ff76 Compare October 2, 2026 04:58
@rouzwelt
rouzwelt marked this pull request as ready for review October 2, 2026 05:09
@rouzwelt
rouzwelt force-pushed the 2026-10-02-rai-2835 branch from 959ff76 to 9eeb645 Compare October 2, 2026 13:18
@rouzwelt
rouzwelt force-pushed the 2026-10-02-rai-2834 branch from 867bbec to 4caa4eb Compare October 2, 2026 13:18
@rouzwelt
rouzwelt force-pushed the 2026-10-02-rai-2835 branch from 9eeb645 to fae1e7b Compare October 2, 2026 14:13
@rouzwelt
rouzwelt force-pushed the 2026-10-02-rai-2834 branch 2 times, most recently from 454b09c to d9c9caa Compare October 2, 2026 15:23
@rouzwelt
rouzwelt force-pushed the 2026-10-02-rai-2835 branch from fae1e7b to 37d135e Compare October 2, 2026 15:23
@rouzwelt
rouzwelt force-pushed the 2026-10-02-rai-2834 branch from d9c9caa to c493e0a Compare October 2, 2026 18:40
@rouzwelt
rouzwelt force-pushed the 2026-10-02-rai-2835 branch from 37d135e to 869c34f Compare October 2, 2026 18:40
@rouzwelt
rouzwelt force-pushed the 2026-10-02-rai-2834 branch from c493e0a to 61dfd9c Compare October 2, 2026 19:24
@rouzwelt
rouzwelt force-pushed the 2026-10-02-rai-2835 branch from 869c34f to 3563acf Compare October 2, 2026 19:24
@rouzwelt
rouzwelt force-pushed the 2026-10-02-rai-2834 branch from 61dfd9c to a29b6d6 Compare October 2, 2026 20:20
@rouzwelt
rouzwelt force-pushed the 2026-10-02-rai-2835 branch from 3563acf to 0b8d60f Compare October 2, 2026 20:20
@rouzwelt
rouzwelt force-pushed the 2026-10-02-rai-2835 branch from 7aa0307 to 96f3a85 Compare October 5, 2026 19:50
@rouzwelt
rouzwelt force-pushed the 2026-10-02-rai-2834 branch from a90dc8e to 4fb93a7 Compare October 5, 2026 19:51
Comment thread src/receipt_inventory/backfill.rs
@rouzwelt
rouzwelt force-pushed the 2026-10-02-rai-2834 branch from 4fb93a7 to 0598569 Compare October 5, 2026 20:21
@rouzwelt
rouzwelt force-pushed the 2026-10-02-rai-2835 branch 2 times, most recently from 54242b8 to 0f33135 Compare October 5, 2026 21:57
@rouzwelt
rouzwelt force-pushed the 2026-10-02-rai-2834 branch from 0598569 to 0ecfd08 Compare October 5, 2026 21:57
@rouzwelt
rouzwelt force-pushed the 2026-10-02-rai-2835 branch from 0f33135 to 2b07fdc Compare October 6, 2026 00:06
@rouzwelt
rouzwelt force-pushed the 2026-10-02-rai-2834 branch 2 times, most recently from 0e2daec to 9bf378b Compare October 6, 2026 00:40
@rouzwelt
rouzwelt force-pushed the 2026-10-02-rai-2835 branch from 2b07fdc to 551dd28 Compare October 6, 2026 00:40
@rouzwelt
rouzwelt force-pushed the 2026-10-02-rai-2834 branch from 9bf378b to fa35088 Compare October 6, 2026 01:06
@rouzwelt
rouzwelt force-pushed the 2026-10-02-rai-2835 branch from 551dd28 to 1f2a15c Compare October 6, 2026 01:07
@rouzwelt
rouzwelt force-pushed the 2026-10-02-rai-2834 branch from fa35088 to 694558a Compare October 6, 2026 02:17
@rouzwelt
rouzwelt force-pushed the 2026-10-02-rai-2835 branch from 1f2a15c to 37e33e6 Compare October 6, 2026 02:17
@rouzwelt
rouzwelt force-pushed the 2026-10-02-rai-2834 branch from 694558a to 3c63ab0 Compare October 6, 2026 03:01
@rouzwelt
rouzwelt force-pushed the 2026-10-02-rai-2835 branch from 37e33e6 to 2fc2f90 Compare October 6, 2026 03:01

rouzwelt commented Oct 6, 2026

Copy link
Copy Markdown
Collaborator Author

@rain-marvin review

@rain-marvin

rain-marvin Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

🔎 Reviewing 2fc2f90, started by @rouzwelt. The review will appear here when it's done.

@rain-marvin rain-marvin Bot 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.

This PR splits the receipt backfill pass into two steps. It still reads the receipt balances in parallel, at most 4 at a time and pinned to the read block. Then it writes the discoveries to the vault's single inventory aggregate one at a time, in the order the logs were collected. Before, up to 4 writers loaded and persisted the same aggregate at once. A big pass, such as the restart after a rollback, could use up the 3-attempt conflict retry budget and stop startup. The PR also adds a 65-receipt regression test and a SPEC note.

Overall read: the fix is correct and the right size. During run_startup_recovery the backfill is the only writer to the aggregate, because the live monitors, periodic backfills and redemption workers start after it returns. So serial writes remove the conflict at its source. buffered keeps the collection order, so unique_by and the ITN duplicate-deposit check now treat the earliest deposit as canonical every time. The reconciliation loop was already serial. The open thread about try_collect dropping finished reads is not a defect: no durable progress is lost, the checkpoint does not move, and the next pass redoes the same idempotent range. No new findings.

claude-opus-5-5 · high · 12 min

@rouzwelt
rouzwelt force-pushed the 2026-10-02-rai-2834 branch from 3c63ab0 to 8f8799d Compare October 6, 2026 13:30
@rouzwelt
rouzwelt force-pushed the 2026-10-02-rai-2835 branch 2 times, most recently from a481944 to ed56f2f Compare October 6, 2026 14:12

rouzwelt commented Oct 6, 2026

Copy link
Copy Markdown
Collaborator Author

@rain-marvin review

@rain-marvin

rain-marvin Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

🔎 Reviewing ed56f2f, started by @rouzwelt. The review will appear here when it's done.

@rain-marvin rain-marvin Bot 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.

This PR splits receipt backfill discovery into two steps: the pinned balanceOf reads still run concurrently (now buffered so they keep log order, with try_collect), and the writes to the vault's single inventory aggregate (ITN duplicate check, DiscoverReceipt, ReconcileBalance, mint-recovery callback) now run one at a time. Before this, four concurrent writers on one aggregate could use up the three-attempt optimistic-concurrency retry budget, and a large pass (the restart after a rollback) would fail and stop startup.

Overall read: the change does what it says and is about as small as it can be while keeping reads concurrent. The new 65-receipt test was run against the base code and fails there with AggregateConflict after the retries run out; on this head it passes repeatedly. At startup the backfill is the only writer to the aggregate, because the redemption and mint workers start after run_startup_recovery, so the startup failure cannot recur from inside the pass. Sequential writes also make the ITN duplicate-deposit check deterministic when two deposits for the same request land in one pass. Checkpoint behavior is unchanged: a failed read or write still leaves the checkpoint where it was. The two earlier try_collect threads were already settled and nothing in the code changes that. No findings.

claude-opus-5-5 · high · 24 min

rouzwelt commented Oct 6, 2026

Copy link
Copy Markdown
Collaborator Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@graphite-app

graphite-app Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Merge activity

  • Oct 6, 4:11 PM UTC: rouzwelt added this pull request to the Graphite merge queue.
  • Oct 6, 4:12 PM UTC: The Graphite merge queue removed this pull request due to downstack failures on PR #419.
  • Oct 6, 4:12 PM UTC: The Graphite merge queue removed this pull request due to downstack failures on PR #419.
  • Oct 6, 11:43 PM UTC: rouzwelt added this pull request to the Graphite merge queue.
  • Oct 6, 11:44 PM UTC: CI is running for this pull request on a draft pull request (#455) due to your merge queue CI optimization settings.
  • Oct 6, 11:44 PM UTC: Merged by the Graphite merge queue via draft PR: #455.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

externally-merged Graphite MQ merged this PR; Linear should treat the close as a merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants