cmux-tui: one-time cleanup of stored terminal output in old registries

Closed
LA
6d ago
+4021-84

Summary

One-time cleanup of the terminal output that older builds stored in the session journal (terminal.output rows and the output records inside sealed segments). On a registry of the size class of Lawrence's (41.6 GB, 14.9M live rows, almost all output), the file goes from 39.5 GiB to 0.26 GiB.

  • Phase A (background, owner running, low priority: background QoS on macOS, nice 19 + idle IO on Linux): a read-only connection copies the non-output records of every sealed segment into workspace-registry.sqlite3.output-cleanup, one segment per transaction, so it resumes after a quit or a kill. Journal sealing pauses while a cleanup is pending.
  • Phase B (next owner start, in WorkspaceRegistry::open, under the session locks): copies every other table with its rowids, the non-output live rows (through the (kind, sequence) index) and their index rows, packs all kept records into new segments over the same sequence range, writes journal_pruned_ranges, checks the result (integrity check, row counts per table, kept + removed per segment and for the live rows, a full journal read-back, an open through the owner's initialize path that reads the workspace layout), keeps the original as .pre-output-cleanup and renames the new file into place.
  • Sequences never change. The journal readers accept a gap only when one pruned range covers it; any other gap is still an error.
  • If the owner does not start on the new registry, the original goes back under the session locks.
  • The agent roster restore at start now uses the indexed no-output read (it read every output row at each start: 650 s before, 41 s after on the cold synthetic registry).

Decisions for Lawrence to check

  • Older builds are degraded, not refused: they open the swapped registry and serve its workspaces, but journal reads, subscriptions and restore previews that cross a removed range end with a gap error. A refusal would stop an older Cloud daemon from starting at all. See docs/cloud-guest-upgrades.md.
  • The failure marker (.output-cleanup-failed) names the build; this build does not try again, a different build does. Temporary errors (busy, locked, full disk, ENOSPC/EBUSY/EAGAIN/EINTR, segments changed) try again at the next start without a marker.
  • Phase A starts 60 s after the owner starts, so it does not compete with session restore.
  • The cleanup writes the original file once: a WAL checkpoint before the copy, so a WAL left by a killed owner is in the copy. The content does not change.
  • .pre-output-cleanup is deleted at the start after one full start (restore done) on the new registry. Until then a failed owner build rolls back, and a person can restore it by hand.

Measurements (rbx, Linux, synthetic registry of the real shape)

6.02M rows in 5,882 sealed segments, 14.93M live rows, about 80k non-output rows, 6 subject index rows per record. Page cache cold.

MeasureResult
File size39.50 GiB -> 0.26 GiB
Owner start with the old file (roster fix in)25-43 s (650.7 s without the roster fix)
Phase A75-90 s, peak RSS 82 MiB
Phase B (the swap start, until the owner answers)7.2 s, peak RSS 82 MiB
Start after the swap0.11 s

Bench: cmux-tui/crates/cmux-tui/tests/output_cleanup_bench.rs (ignored, Linux; builds the registry, runs both phases).

Observed / Not run

Observed (rbx, rust-builder rbx from HQ PR 2727):

  • cargo fmt --check: pass.
  • cargo test --locked -p cmux-tui --test output_cleanup: 6 of 6 pass (swap end to end with every structural table equal including rowids; kill -9 in Phase A then resume; rollback after a failed owner build; rollback after a failed open of the swapped file; rollback at a later start after a swap start that never finished; restore preview across a pruned tail). Red without the fix: the rowid test, the pruned-tail test and the unfinished-start test.
  • legacy_registry_maintenance, registry_recovery, journal_effect_intents, journal_hook_detach: pass.
  • cargo clippy -p cmux-tui-core -p cmux-tui --all-targets -D warnings: red only on base files (cmux-worktree-care/src/store.rs:97; earlier also agent_reports.rs:458, event_shape_tests.rs:148, fixed in #22991).
  • cargo test -p cmux-tui-core --lib and terminal_host_recovery: the failures that remain also fail on base.

Not run:

  • macOS: the QoS call, the bench, start time on a laptop SSD.
  • Lawrence's real registry (not touched, not copied).
  • Phase A while the owner seals segments at the same time.

Changelog

Changed: cmux-tui removes the terminal output that older builds stored in the session registry and shrinks the file, once, in the background.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Adds a one-time cleanup that removes the stored terminal output older builds journaled, shrinking the session registry from tens of GiB to a fraction of a GiB. The cleanup stages non-output records in the background, then swaps the pruned registry in at the next owner start; journal sequences are preserved, and readers treat a gap as an error unless it is covered by a recorded pruned range.

Side effects

  • Older builds still open the swapped registry, but journal reads, subscriptions, and restore previews crossing a pruned range fail with a gap error.
  • The original registry is kept as .pre-output-cleanup until a full start on the new registry, enabling manual rollback. If restoring an interrupted swap fails, the owner refuses to start and names the backup rather than creating an empty registry that would discard it.
  • Agent roster restore now skips output rows, shortening cold startup on such registries from minutes to seconds.