perf: rewrite primary keys with bounded shadow generations - #71
Open
HelgeSverre wants to merge 8 commits into
Open
perf: rewrite primary keys with bounded shadow generations#71HelgeSverre wants to merge 8 commits into
HelgeSverre wants to merge 8 commits into
Conversation
HelgeSverre
marked this pull request as ready for review
August 10, 2026 21:14
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.
Summary
ALTER TABLE ... ADD PRIMARY KEYrewrite with bounded shadow-generation builds and an atomic catalog cutoverDROPandRENAMEso deferred cleanup cannot delete a newly reused table keyspaceThis is stacked on #70 and should be merged or retargeted after that PR lands.
Why
The previous rewrite collected and decoded the whole table, then staged all data and secondary-index mutations in one transaction. Peak memory scaled with the full table and the transaction's validated point mutations dominated latency.
The new path builds an unreachable physical generation in bounded commits, validates the source table and catalog at cutover, atomically switches a small generation pointer, and reclaims the previous generation afterward. Explicit transactions and multi-operation ALTER statements retain the original atomic path.
Self-review also found a deferred-cleanup race: dropping and immediately recreating a rewritten table could reuse a generation while an old cleanup task was still deleting it. Persistent per-name generation watermarks and rename target watermarks prevent that reuse.
Performance
Matched native Apple Silicon release builds, full durability, one secondary index, five samples per cell. The baseline is
c7cdebc, immediately before this stack.Native MySQL 8.0.33 on the same host:
MySQL remains substantially faster, particularly as the table grows. The benchmark documentation describes the methodology and deferred-cleanup tradeoff.
Crash and sustained-load testing
The five-minute adversarial campaign completed:
SIGSTOPthenSIGKILLcyclesAll final durability, account-balance, table aggregate, index-versus-scan, and atomicity invariants passed. A final post-fix 60-second campaign also passed 115 forced crashes and 91 recovery-time kills.
One earlier compound-failure run exposed a transient redb 2.6.3 startup panic (
assertion failed: !self.needs_recovery) after repeatedly killing recovery. The preserved database opened successfully on the next clean launch and no persistent corruption was observed. The harness records retry/open failures, and the limitation is documented rather than hidden. Torn-sector and reordered/failed individual I/O testing will require a lower-level fault-injection interface.Validation
cargo fmt --all -- --checkcargo clippy --all-targets -- -D warningscargo test --workspace