[Storage] Add inactivity floor to qmdb::keyless Commit operation#3624
Conversation
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
commonware-mcp | febb61a | Apr 17 2026, 10:37 PM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
commonware-mcp | da9c11a | Apr 22 2026, 12:02 PM |
Deploying monorepo with
|
| Latest commit: |
da9c11a
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://f2b5ab6a.monorepo-eu0.pages.dev |
| Branch Preview URL: | https://danlaine-keyless-inactivity.monorepo-eu0.pages.dev |
|
Mixed feelings about calling this "inactivity_floor", which already has a pretty well-defined meaning for keyed databases. Though the upcoming "sync_boundary" might not be much better. "application_boundary"? |
I think the meaning is well-defined here too -- anything below the inactivity floor is inactive, not part of the active (live) set of operations. I could be convinced that we should change all |
|
We can revisit the naming if needed in the mmb-sync pr. |
|
need to update conformance file? |
Codecov Report❌ Patch coverage is @@ Coverage Diff @@
## main #3624 +/- ##
==========================================
+ Coverage 95.87% 95.88% +0.01%
==========================================
Files 442 442
Lines 172121 172834 +713
Branches 4010 4022 +12
==========================================
+ Hits 165017 165724 +707
- Misses 5840 5841 +1
- Partials 1264 1269 +5
... and 4 files with indirect coverage changes Continue to review full report in Codecov by Sentry.
🚀 New features to boost your workflow:
|
Summary
qmdb::keylessis an append-only authenticated database where operations are stored by location rather than by key. Applications use it for structures like logs or event streams — data that grows monotonically and is never overwritten.Without a notion of an inactivity floor, the entire history must be retained and synced. A new replica joining the network must fetch every operation from genesis to reconstruct the state. As the log grows, this becomes impractical. The database itself cannot determine which operations are "still needed" because, unlike
qmdb::anywhere key overwrites make old operations provably dead, every keyless operation is alive by definition.Only the application knows when old operations become irrelevant. For example, a log consumer might only need the last N commits of history. This PR lets the application declare that knowledge by specifying an inactivity floor at commit time.
The floor is embedded directly in the
Operation::Commitvariant, making it part of the operation log and root digest. Every replica processing the same sequence of operations arrives at the same floor. This means:Changes
Operation enum
Operationgains aFamilygeneric parameter and theCommitvariant now carries aLocation<F>alongside its optional metadata:The
has_floor()method on theOperationtrait now returnsSome(loc)forCommitinstead ofNone. Both the fixed-size codec (big-endianu64) and variable-size codec (varint) are updated to encode the new field.Keyless struct
A new
inactivity_floor_loc: Location<F>field is added to theKeylessstruct. It is maintained through every state transition:init_from_journal: Reads the floor from the last commit operation.apply_batch: Updates the floor from the batch after validating every commit's floor in the chain (see below).rewind: Restores the floor from the rewind target's commit operation, so the post-rewind floor matches what was in effect at that commit.from_sync_result: Extracts the floor from the last commit in the synced range.prune: Now checks againstinactivity_floor_locinstead oflast_commit_loc. The application can only prune up to what it has declared inactive.A new
inactivity_floor_loc()accessor is provided.Batch API
merkleize()takes a newinactivity_floor: Location<F>parameter. The application passes this when building a batch to declare which operations are no longer needed. The floor is embedded in the resultingCommitoperation and stored onMerkleizedBatchfor use byapply_batch.MerkleizedBatchalso stores each unapplied ancestor's floor in a parallelancestor_new_inactivity_floor_locs: Vec<Location<F>>(newest-first, mirroring the existingancestor_batch_ends), soapply_batchcan validate every commit in the chain — not just the tip — before writing to the journal.Per-commit floor invariants
apply_batchenforces two invariants on every unapplied commit (each ancestor's plus the tip's), walking the chain oldest-first before any journal mutation:inactivity_floor_loc.total_size - 1at that point). A floor past the commit would let a laterprune(floor)remove the last readable commit from the journal.Violations return
Error::FloorRegressed(offending, prev)orError::FloorBeyondSize(offending, commit_loc). Since validation runs before any mutation, the database is untouched on floor errors.Finishes resolving #3538