Skip to content

feat(storage): reduce persistent disk usage with lossless cold-session compression #6109

Open

Description

@testikun

Problem

Long-running Maka sessions retain large amounts of persisted conversation and tool-result JSON. Much of this history is no longer being written, but it continues to occupy the same disk space as active sessions. Deleting history is an undesirable workaround when users still need complete transcripts, replay, and tool evidence.

We would like to reduce persistent storage usage while preserving the complete stored data and existing read behavior.

Desired outcome

Introduce hot/cold storage for persisted session content, with lossless compression for cold data:

  • Treat a Session as cold after 7 days without an actual local content write. Reading history does not reset this clock; imported/restored content starts a new local cooling period.
  • Initially cover completed text, thinking, tool-call arguments, and complete persisted tool responses, including signatures and provider metadata. Keep streaming partials, indexes, and control state directly accessible.
  • Compress in bounded, low-priority background work after Host readiness, yielding to execution and frontend reads. Keep original data when compression has insufficient net benefit or resources are constrained.
  • Read compressed and uncompressed records transparently. Continuing a conversation appends ordinary records while existing compressed history stays compressed.
  • Use a versioned representation with corruption checks, safe publication, and bounded restoration to the original bytes. Future codec/policy changes must remain compatible with existing records.
  • Measure read latency, event-loop responsiveness, memory, and actual filesystem space reclaimed separately. SQLite free pages alone are not space returned to the OS; database reclamation must be explicitly coordinated with the work tracked in design(storage): decide where runtime.sqlite's one-time full VACUUM runs before converting it to incremental vacuum #6000.

The initial rollout should keep the compression writer disabled until correctness, migration, and performance criteria have been reviewed. Benchmark on disposable copies rather than user databases.

Alternatives or workarounds

Manual history cleanup, keeping all content as plain JSON, and compressing a whole Session archive. Per-record compression is a candidate because it can retain indexed access and append new content without rewriting the complete old transcript. Retention and cleanup remain useful independent controls.


AI-assisted issue prepared with OpenAI Codex at @testikun's request.

Activity

  1. testikun commented on Oct 15, 2026

    @testikun
    ContributorAuthor

    take

  2. testikun commented on Oct 19, 2026

    @testikun
    ContributorAuthor

    @liugddx could we get your guidance on the existing-database boundary for this cold-storage work, in light of your latest direction in #6000?

    The connection between the two issues is that lossless compression frees pages inside SQLite, but an existing auto_vacuum=NONE database cannot return those pages to the filesystem incrementally. We explored that dependency in #6115. We have paused code changes to clarify the design direction.

    The current prototype opens the Host status endpoint first, then performs conversion before Ready and before opening ordinary business stores. It retains the Root owner, initializes the schema, closes the process-local operational database connection, and runs in-place VACUUM plus integrity_check in a worker with synchronous=FULL. Desktop displays the actual upgrade stage and elapsed time. The shared readiness helper gives upgrading_storage a separate 30-minute budget, while ordinary startup keeps its existing budget. The free-space precheck requires approximately 2x the current database file size plus 64 MiB in additional free space. The parent retains ownership until the worker exits.

    Compression itself is bounded background work after Ready, for completed large records after seven days without an actual local content write. Reads keep the stored representation compressed.

    We measured the following on native macOS, Apple M2 Pro, 16 GiB RAM, Node 24.20.0, using a historical synthetic fixture with 185,930 RuntimeEvents / 883,500,249 bytes of original event JSON. All writes used disposable copies; the source remained unchanged. The disk reductions below measure compression followed by reclamation.

    Experiment NONE → INCREMENTAL upgrade Main database before compression → after compression and reclamation
    Complete historical database copy, retaining all source tables 9.89 s 1,148,002,304 → 512,114,688 bytes; 55.39% reduction, with the same reduction in allocated file blocks
    Exact event corpus replayed into a fresh current-schema database configured as NONE 58.52 s 1,082,785,792 → 446,894,080 bytes; 58.73% reduction

    The complete historical file was 1,158,852,608 bytes before conversion alone. The replay measures a different database layout, rather than a full source-schema migration. These separate runs do not establish a before/after speedup or a dependable seconds-per-GB estimate. Sampled parent event-loop maximum gaps during the upgrades were approximately 9.7 ms and 29.7 ms; this does not qualify every startup stage.

    Additional checks and costs:

    • On the replay fixture, 3,000 warm paging samples per phase gave p95 21.23 ms raw → 19.11 ms compressed → 20.36 ms after reclamation. Timing includes storage, decoding and page projection, excluding UI/RPC. OS caches were not purged. The complete-database run used only 30 repeated pages per phase as a correctness smoke check.
    • All 185,930 event bodies retained their exact JSON bytes/digest. Real Electron progress stayed responsive, and quit/restart preserved history. A process-kill/retry test exists; hardware power loss and dedicated Windows/Linux conversion qualification remain outstanding.
    • Full-corpus negative search has a cost: roughly 2.4–3.3 s raw → 3.3–4.2 s compressed. Its candidate sets matched; the scan uses a separate maintenance worker.

    The prototype still has unresolved boundaries that matter to this decision:

    • A fence covering all secondary require_current openers has not been established by the Root/process-local checks.
    • The CLI managed-service verifier still has a 45 s deadline and can stop a long conversion. Schema/index preparation is also synchronous before the worker phase.
    • Schema commits precede conversion, so interruption does not restore compatibility with older binaries. Killed attempts can retry from the beginning. The prototype's automatic conversion and enabled compression default also need alignment with explicit user control and this issue's initial rollout plan.

    Would you consider controlled pre-Ready in-place conversion worth evaluating once those coordination and lifecycle gaps are addressed, or should we separate legacy conversion from this cold-compression work and pursue the VACUUM INTO staging/promotion investigation under #6000? In particular, we would appreciate your view on whether an extended upgrade startup budget is itself an unacceptable Desktop tradeoff. We want to settle that direction before resuming implementation changes.

    AI assistance: OpenAI Codex helped prepare the implementation, experiments and this comment at @testikun's request.

  3. youlemei2022-web commented on Oct 20, 2026

    @youlemei2022-web

    Thanks @testikun — the measurements and the caveats are both useful, and I think they already answer the first question.

    1. Separate legacy conversion from this work. Keep #6115 to compression + bounded reclamation only: no automatic conversion, no schema commit that makes the file incompatible with older binaries. The conversion belongs to #6000, pursued as VACUUM INTO staging plus a promotion step at the next start, not as an in-place upgrade on the startup path.

    2. An extended upgrade budget is not an acceptable default. 64–86 s for 3 GiB is already past the 75 s deadline before integrity_check and before slower disks; a 30-minute upgrading_storage window only converts "startup fails" into "app appears hung for minutes", which is worse for Desktop. If we ever support in-place conversion, it has to be explicit and user-visible: opt-in, a hard cap, real progress with elapsed time, and a documented fallback to the old binary. Not a silent pre-Ready step.

    3. What the staging design needs before it can be evaluated.

    • Fence every opener of runtime.sqlite, not just Root/process-local — including the CLI managed-service verifier, whose 45 s deadline must not be able to kill a conversion worker.
    • Promotion is atomic and fail-closed: durable marker, then validate schema version + integrity_check + expected size, then swap; a crash at any point leaves the original file usable and retryable.
    • A defined user-visible state while conversion is pending (status endpoint + Desktop copy), and a downgrade story.
    • Evidence: time VACUUM INTO and the promotion step themselves, at least one run on native macOS, plus disk-full and mid-promotion crash cases.

    So: don't resume the pre-Ready conversion path in #6115. If you want to take the staging design, #6000 is the place, and the fencing + promotion-failure matrix is the first deliverable — I'd review that before any implementation.

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions