fix(global-db): restore measured WAL reclaim to registered checkpoint (Plan 38 §6) - #534
Conversation
|
198a5c2 to
16c6b88
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 909e581ddb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| WalReclaimPlan::Truncate => { | ||
| self.truncate_database_wal().await?; |
There was a problem hiding this comment.
Wire truncation into a production maintenance journey
For the live daemon store, the existing shutdown calls in src/mcp/server/connection.rs run with daemon authority, so an oversized WAL selects RequiresExclusiveMaintenance and checkpoint() discards that receipt. A repo-wide search finds no maintenance-scoped production caller of checkpoint_result(); the truncating branch is reached only by checkpoint_tests.rs, leaving the reported 415 MiB production WAL unable to reclaim its high-water file. Wire this pass into an offline maintenance lifecycle or provide an authorized daemon reclaim path.
AGENTS.md reference: AGENTS.md:L75-L77
Useful? React with 👍 / 👎.
| let wal_bytes_before = wal_file_bytes(&wal_path)?; | ||
| self.checkpoint_database().await?; | ||
| // A successful checkpoint proves the writable scope, so the role read | ||
| // cannot race a mode downgrade. | ||
| let reclaim = match wal_reclaim_plan(wal_bytes_before, self.write_authority_role()?) { |
There was a problem hiding this comment.
Serialize the WAL size sample with the checkpoint
Because wal_bytes_before is measured before checkpoint_database() acquires the process-local writer mutex, another task holding a cloneable RegisteredGlobalDbLeaseV1 can append while this call waits. For example, a 31 MiB sample can grow beyond 32 MiB before the checkpoint runs, after which the stale sample selects BelowTrigger, retains the oversized high-water file, and publishes a receipt inconsistent with the post-checkpoint size. Measure and decide within the same serialized checkpoint/maintenance lane.
Useful? React with 👍 / 👎.
Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
909e581 to
0b25be3
Compare
dc3e3f8
into
codex/tracedecay-total-redesign-plan
Summary
Plan 38 §6 WAL checkpoint/compaction for
crates/tracedecay-global-db, targeting the live symptom of a 415 MB WAL that never checkpoints down:RegisteredGlobalDb::checkpoint_result()now runs a measured checkpoint/compaction pass with a typed receipt, surfaces a PASSIVE-busy WAL as a typed failure, and truncates the drained file to zero bytes under exclusive maintenance authority. Rebased onto the current #421 tipd6a273c9(contains #531; not stacked on #537).Root cause (verified on this branch)
194781603(refactor(runtime): enforce owner-bound database lifetimes, Aug 16),checkpoint_result()issued a realPRAGMA wal_checkpoint(TRUNCATE)through the serialized writer (WalCheckpointExecutor) and failed typed onbusy/incomplete frames.Database::checkpoint()— the retained runtime's PASSIVE-only lane, which returns a vacuousOkbelow its 32 MiB soft budget even while a live reader pins the WAL, and never truncates at any size.Database::truncate_wal_for_offline_maintenancehad zero callers; the rusqlite-runtime writer'strigger_maintenance(MaintenanceCheckpointMode::Truncate)permit lane is implemented but unmounted). PASSIVE backfill bounds WAL content once readers release, but the-walfile keeps its high-water size forever — the 415 MB debris. The crate's own busy-contract test was red on the base tip (called Result::unwrap_err() on an Ok value,checkpoint_tests.rs:50).Change (this crate only)
checkpoint_result()returnsRegisteredWalCheckpointReceiptV1 { wal_bytes_before, wal_bytes_after, reclaim }— file-level-walmeasurements per Plan 38's size-evidence rule; a missing sidecar is a typed zero.wal_reclaim_plan) of measured size × write-authority role: below the 32 MiB trigger (aligned with the runtime's soft checkpoint budget; a fresh schema install alone leaves several MiB) the warm WAL is left alone; at/above it, the file is truncated via the runtime's exclusive-maintenance facade when this client holds theMaintenancerole (real journeys: embedded/CLI lifecycle underenter_owned_maintenance_database_scope, profile-storage commands, core doctor); daemon-role clients get the typedRequiresExclusiveMaintenancereceipt.checkpoint()keeps its shutdown-safe signature for existing callers (MCP shutdown, host admission).tracedecay-runtime-coredev-features gaintest-transport, exposing the maintenance-authority fixture publisher the truncation proof needs.Test evidence (isolated temp stores only — no live profile, no daemon attach)
All in
crates/tracedecay-global-db(cargo nextest run -p tracedecay-global-db -E 'test(checkpoint_tests) or test(registered_maintenance::tests)'— 6/6 pass ond6a273c9):checkpoint_resultfail typed (…remains pending); after release the receipt reports the measured high-water bytes (pressured_checkpoint_reports_pinned_reader_and_reclaims_after_release).enter_maintenance_database_scope→DatabaseAuthority::for_runtime=Maintenancerole) with the same pinned >32 MiB WAL still fails typed while pinned — maintenance authority never bypasses reader safety — then, after release,checkpoint_resulttruncates the file to 0 bytes (Truncatedreceipt,wal_bytes_after == 0, on-disk sidecar measured 0) and the store keeps serving reads/writes (maintenance_truncate_drains_passive_busy_wal_to_zero_bytes).BelowTriggerreceipt with measured nonzero bytes, file untouched (below_trigger_checkpoint_reports_measured_wal_bytes).RequiresExclusiveMaintenance,wal_bytes_after == wal_bytes_before, plus drain evidence (next write rewinds instead of appending past high-water). Pure-function coverage of all three plan arms acrossDaemon/Maintenance/Testroles (registered_maintenance::tests).Gates:
cargo fmt --check,cargo clippy --all-targets --all-features -D warningsclean on the crate;cargo check -p tracedecay --libclean (root callers compile against the receipt). Full-crate nextest has 12 failing tests unrelated to this change — all 12 reproduce identically with the crate checked out at the clean base tip in this VM (daemon-scope-revocation family; environmental, pre-existing).Boundary finding (per task rule: say so instead of editing other crates)
The daemon-lane file truncation belongs to
tracedecay-runtime-core/tracedecay-rusqlite-runtime, not this crate: the writer's TRUNCATE command verifies the daemon-allowedQueryintent, but no public runtime-core surface exposes it to daemon-role registered clients since194781603, and theExclusiveMaintenancePermit-gatedtrigger_maintenance(Truncate)writer lane is unreachable from here. This PR mounts reclaim for every journey whose authority the runtime already accepts (exclusive maintenance) and types the daemon gap in the receipt; a daemon-safe truncate mount is a runtime-core follow-up, deliberately not attempted here.max_open ownership
max_open: 8is daemon-only: it lives insrc/daemon/store_runtime/session_registry/mounts.rs(GraphDbRegistryConfig), not in this crate. Not changed here, per coordination. (Sizing note for the daemon owner: 4 projects need ~10 by the 2×n+2 formula.)