Skip to content

[Storage] Add inactivity floor to qmdb::keyless Commit operation#3624

Merged
roberto-bayardo merged 10 commits into
mainfrom
danlaine/keyless-inactivity-floor
Apr 22, 2026
Merged

[Storage] Add inactivity floor to qmdb::keyless Commit operation#3624
roberto-bayardo merged 10 commits into
mainfrom
danlaine/keyless-inactivity-floor

Conversation

@danlaine
Copy link
Copy Markdown
Collaborator

@danlaine danlaine commented Apr 17, 2026

Summary

qmdb::keyless is 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::any where 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::Commit variant, 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:

  • Replicas agree on the floor without out-of-band coordination.
  • The floor can be used as the lower bound of a sync target, so new nodes only fetch operations from the floor onward.
  • Data below the floor can be pruned.

Changes

Operation enum

Operation gains a Family generic parameter and the Commit variant now carries a Location<F> alongside its optional metadata:

// Before
enum Operation<V> {
    Append(V::Value),
    Commit(Option<V::Value>),
}

// After
enum Operation<F: Family, V> {
    Append(V::Value),
    Commit(Option<V::Value>, Location<F>),
}

The has_floor() method on the Operation trait now returns Some(loc) for Commit instead of None. Both the fixed-size codec (big-endian u64) 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 the Keyless struct. 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 against inactivity_floor_loc instead of last_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 new inactivity_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 resulting Commit operation and stored on MerkleizedBatch for use by apply_batch.

MerkleizedBatch also stores each unapplied ancestor's floor in a parallel ancestor_new_inactivity_floor_locs: Vec<Location<F>> (newest-first, mirroring the existing ancestor_batch_ends), so apply_batch can validate every commit in the chain — not just the tip — before writing to the journal.

Per-commit floor invariants

apply_batch enforces two invariants on every unapplied commit (each ancestor's plus the tip's), walking the chain oldest-first before any journal mutation:

  1. Monotonicity. Floors are non-decreasing across the chain, starting from the database's current inactivity_floor_loc.
  2. Bounded by commit location. Each floor is at most its own commit operation's location (total_size - 1 at that point). A floor past the commit would let a later prune(floor) remove the last readable commit from the journal.

Violations return Error::FloorRegressed(offending, prev) or Error::FloorBeyondSize(offending, commit_loc). Since validation runs before any mutation, the database is untouched on floor errors.

Finishes resolving #3538

@danlaine danlaine self-assigned this Apr 17, 2026
@danlaine danlaine added this to Tracker Apr 17, 2026
@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
commonware-mcp febb61a Apr 17 2026, 10:37 PM

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Apr 17, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
commonware-mcp da9c11a Apr 22 2026, 12:02 PM

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Apr 17, 2026

Deploying monorepo with  Cloudflare Pages  Cloudflare Pages

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

View logs

@danlaine danlaine moved this to In Progress in Tracker Apr 17, 2026
@danlaine danlaine added the breaking-format This PR modifies codec and/or storage formats. label Apr 17, 2026
@danlaine danlaine moved this from In Progress to Ready for Review in Tracker Apr 20, 2026
@danlaine danlaine marked this pull request as ready for review April 20, 2026 18:51
@roberto-bayardo
Copy link
Copy Markdown
Collaborator

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"?

@danlaine
Copy link
Copy Markdown
Collaborator Author

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 inactivity_floor to sync_floor. I prefer that to sync_boundary since it specifies which part of the boundary (the lower bound) it is.

@roberto-bayardo
Copy link
Copy Markdown
Collaborator

We can revisit the naming if needed in the mmb-sync pr.

@roberto-bayardo roberto-bayardo added this pull request to the merge queue Apr 21, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Apr 21, 2026
@roberto-bayardo
Copy link
Copy Markdown
Collaborator

need to update conformance file?

    thread 'qmdb::conformance::test_keyless_mmb_fixed_conf_conformance_' (3949) panicked at conformance/src/lib.rs:260:17:
    Conformance test failed for 'commonware_storage::qmdb::conformance::KeylessMmbFixedConf'.

@danlaine danlaine added this pull request to the merge queue Apr 22, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Apr 22, 2026
@roberto-bayardo roberto-bayardo added this pull request to the merge queue Apr 22, 2026
Merged via the queue into main with commit 94ac1f3 Apr 22, 2026
179 checks passed
@roberto-bayardo roberto-bayardo deleted the danlaine/keyless-inactivity-floor branch April 22, 2026 16:21
@github-project-automation github-project-automation Bot moved this from Ready for Review to Done in Tracker Apr 22, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 22, 2026

Codecov Report

❌ Patch coverage is 98.21429% with 17 lines in your changes missing coverage. Please review.
✅ Project coverage is 95.88%. Comparing base (2a7dd42) to head (da9c11a).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
storage/src/qmdb/keyless/mod.rs 97.84% 3 Missing and 11 partials ⚠️
storage/src/qmdb/immutable/mod.rs 71.42% 0 Missing and 2 partials ⚠️
storage/src/qmdb/keyless/operation/mod.rs 94.44% 1 Missing ⚠️
@@            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     
Files with missing lines Coverage Δ
storage/src/qmdb/conformance.rs 100.00% <ø> (ø)
storage/src/qmdb/keyless/batch.rs 93.10% <100.00%> (+0.29%) ⬆️
storage/src/qmdb/keyless/fixed.rs 100.00% <100.00%> (ø)
storage/src/qmdb/keyless/operation/fixed.rs 100.00% <100.00%> (ø)
storage/src/qmdb/keyless/operation/variable.rs 100.00% <100.00%> (ø)
storage/src/qmdb/keyless/sync/mod.rs 100.00% <100.00%> (ø)
storage/src/qmdb/keyless/variable.rs 97.20% <100.00%> (+0.92%) ⬆️
storage/src/qmdb/mod.rs 98.08% <ø> (ø)
storage/src/qmdb/sync/resolver.rs 46.60% <ø> (ø)
storage/src/qmdb/keyless/operation/mod.rs 97.82% <94.44%> (-2.18%) ⬇️
... and 2 more

... and 4 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 2a7dd42...da9c11a. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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

Labels

breaking-format This PR modifies codec and/or storage formats.

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

2 participants