Skip to content

🐛 fix(availability): prove references before reclamation readiness - #2012

Merged
gaborbernat merged 1 commit into
mainfrom
fix/refresh-reference-proof-1382
Aug 31, 2026
Merged

🐛 fix(availability): prove references before reclamation readiness#2012
gaborbernat merged 1 commit into
mainfrom
fix/refresh-reference-proof-1382

Conversation

@gaborbernat

Copy link
Copy Markdown
Member

A reclamation pass built its reference inventory once at the top and reused that same set when it finalized tombstones, so the verdict the finalizer wrote omitted any publish that landed between the two phases. The digest could reach Ready while current metadata still named it, which is the concurrent-reference case #571 rules out. The paging work in #1997 made both phases read one bounded page, so re-proving the inventory once per phase costs a scan rather than a scan per tombstone.

An inventory spans several owner reads that cannot share one redb transaction, so the pass brackets it with a revision instead. MetaStore::reference_revision returns a counter that every driver-row write advances. reclaim_pass reads that counter on both sides of the scan and discards a scan it moved under. Each reclamation write then carries the revision its verdict came from. compare_and_put_reclamation_tombstone compares that revision inside its write transaction and answers TombstoneWrite::ReferencesMoved once the counter has moved, which retires the verdict without writing it. Selection and readiness each prove their own inventory, so the readiness verdict no longer inherits the opening scan.

The counter lives in its own table rather than reusing the journal serial, because a driver transaction can add or remove a reference without appending a journal entry, and that serial does not move across the one mutation the proof has to catch. It advances on any driver-row write, since owners choose their own key layouts and shared storage cannot ask them which prefixes hold references. Counting too many writes costs a retired scan; counting too few costs bytes. A pass that loses the race returns early with its cursors untouched, so the next pass re-proves and repeats the page.

flowchart LR
    R0[Read revision] --> Scan[Scan owner references]
    Scan --> R1{Revision moved?}
    R1 -- yes --> Drop[Discard the scan]
    R1 -- no --> Verdict[Decide referenced]
    Verdict --> CAP{Revision still held?}
    CAP -- no --> Drop
    CAP -- yes --> Write[Write the tombstone]
    classDef accent fill:#cfe4ff,stroke:#1f6feb,color:#0b1f3a;
    classDef warn fill:#ffe3a3,stroke:#d29200,color:#3a2c00;
    class R0,Scan,Verdict,Write accent;
    class R1,CAP,Drop warn;
Loading

ReclamationStore::compare_and_put_reclamation_tombstone now takes the proved revision and returns TombstoneWrite in place of a bare bool, and select_reclamation_candidate and mark_reclamation_ready return Option so a caller can tell a retired proof from a decided digest. Reclaim guards and the destructive interval after readiness stay as they are for #1383, whose reclaim_guard_serial still reads the journal serial this change shows is not a reference proof.

Closes #1382

@gaborbernat gaborbernat added the bug Something isn't working label Aug 31, 2026
A reclamation pass scanned the reference inventory once and reused that set
for both phases. A publish landing between the phases never appeared in it, so
finalization could mark a tombstone ready while current metadata still named
its digest.

The inventory spans several owner reads that cannot share one redb transaction,
so a revision now brackets it: every driver-row write advances the counter, the
pass reads it on both sides of the scan, and a scan the counter moved under is
discarded rather than trusted. Each reclamation write carries the revision its
verdict came from, and the compare-and-put rejects a write whose proof the store
has already outrun. Selection and readiness each prove their own inventory, so
the readiness verdict no longer inherits the opening scan.

The counter lives in its own table rather than the journal serial because a
driver transaction can add or remove a reference without appending a journal
entry, which leaves that serial standing still across exactly the mutation the
proof has to catch. It advances on any driver-row write, since only the owners
know which keys hold references and shared storage cannot ask them; a pass that
loses the race keeps its cursors and re-proves on the next one.

Closes #1382
@read-the-docs-community

read-the-docs-community Bot commented Aug 31, 2026

Copy link
Copy Markdown

Documentation build overview

📚 peryx | 🛠️ Build #34317270 | 📁 Comparing 9b557a6 against latest (5f765fe)

  🔍 Preview build  

1 file changed
± core/availability/blob-reclamation/index.html

@gaborbernat
gaborbernat force-pushed the fix/refresh-reference-proof-1382 branch from a7b552c to 9b557a6 Compare August 31, 2026 18:15
@codspeed-hq

codspeed-hq Bot commented Aug 31, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 29 untouched benchmarks
⏩ 133 skipped benchmarks1


Comparing fix/refresh-reference-proof-1382 (9b557a6) with main (5f765fe)

Open in CodSpeed

Footnotes

  1. 133 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@gaborbernat
gaborbernat merged commit 1487d5b into main Aug 31, 2026
27 checks passed
@gaborbernat
gaborbernat deleted the fix/refresh-reference-proof-1382 branch August 31, 2026 18:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refresh reference proof before reclamation readiness

1 participant