mirror of
https://github.com/kvcache-ai/AgentENV.git
synced 2026-11-05 01:17:36 +00:00
[GH-ISSUE #364] [Design Proposal] Online snapshot garbage collection and terminal history retention #93
Labels
No labels
bug
documentation
enhancement
enhancement
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
kvcache-ai/AgentENV#93
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @guozy18 on GitHub (Oct 12, 2026).
Original GitHub issue: https://github.com/kvcache-ai/AgentENV/issues/364
Affected component
Snapshot / template builder / shared snapshot-volume content retention
Problem statement
Part of the parent proposal, depending on the data model and API and Sandbox resource holds. This issue covers online reclamation over layer dependencies, physical artifact deletion, and terminal history. Activation during version coexistence is covered by the database upgrade proposal.
Independent snapshots continuously create new identities; a preparation cache cannot eliminate most first registrations in a low-reuse workload. Keeping every terminal snapshot payload/reference and Closed H forever would make SQL history grow with cumulative publications and uses. Deleting rows based only on age would let delayed Publish/Pin requests recreate identities that have already ended.
We need to reclaim unused bytes and safely removable history without letting deletion overtake legitimate consumers.
Use case
Snapshot creation, restore, and publication continue while background cleanup runs. Long-running VMs, paused sandboxes, uploads, and device I/O must retain protection regardless of the cleanup window. Maintenance must not require users to pause these operations.
Capacity planning must include many independent snapshots with little reuse, not assume that a few popular templates dominate reads.
Current behavior and workarounds
The Repository manages snapshot records and artifacts in OSS/POSIX, while node-local files and caches have their own lifecycles. Snapshot deletion removes catalog entries and snapshot-specific artifacts; shared layers require separate cleanup.
Repository metadata, local references, and cache state each describe part of resource lifetime. SQL adds an explicit dependency graph and shared usage records so online reclamation can account for all three. Completed usage periods and retired snapshots then become database history that also needs a safe retention policy.
Desired behavior
Proposed approach
S identifies a public snapshot and H one shared usage period. The data model defines fixed-content entries, heads, immutable layer UUID/predecessor relationships, and their constraints. Volume content participates in the same retention mechanism without treating its mutable business ID as a fixed backing version.
Mark candidates from SQL; decide deletion from current references
Use mark-and-sweep over the database's protected content entries, heads, and predecessor relationships. A GC round need not fetch every OSS manifest. Marks identify candidates, not permission to delete: publication and acquisition may continue during marking, and sweep must recheck current references under the same arbitration used to establish new references.
Each layer has at most one immutable predecessor. A content head or a valid successor protects that layer, which in turn protects its predecessor and required bytes. Reclaiming an unreachable branch therefore removes eligible leaves before their predecessors. Retired content remains a root until its actual usage responsibilities have ended. Existing H retries and valid warm reuse continue under the established protection. Predecessor protection may end only after new references are atomically fenced, no actual reader still needs the layer, and remaining deletion work does not need to read its predecessor. State predicates and foreign-key handling must distinguish this released dependency from a retained terminal audit record; the latter must not protect predecessors forever.
Keep retirement, head release, layer reclamation, and physical deletion separate
For example:
Retiring S1 leaves C/B/A protected while an H remains. After final Release, S1's head can be removed and C reclaimed. S2 continues to protect D/B/A. A snapshot and a volume can protect the same prefix in exactly the same way. When a volume switches backing, old users still protect their old heads; the current volume pointer alone is not a sufficient GC root inventory.
If publication wins reference arbitration, GC must retain the layer. If reclamation has already fenced the layer against new references, publication must follow the defined conflict path. This arbitration concerns the relevant identities and physical targets; it does not pause all new snapshot use or rewrite every ancestor of a reused prefix.
A count of zero followed by an uncoordinated OSS DELETE is unsafe. If a commit result is unknown, reconcile the original operation and exact target before issuing deletion. Failed or uncertain physical deletion leaves persistent deleting state and retries the same target. Duplicate workers must be safe; external DELETE need not execute exactly once. An absent target can confirm completion only under a protocol that also accounts for delayed requests and target reuse.
VM state, startup artifacts, and registry publication retention roots remain part of the protected inventory. They must not be omitted or given artificial layer parents simply to fit the forest model.
Distinguish progress, safety state, and candidate discovery
These responsibilities do not mandate the former
refs_releasedorgc_pendingfield layout. If a persistent hint is used, clearing it must arbitrate with later reference removal so a concurrent clear cannot lose work. Reference relations remain the source of truth; a persisted ref_count is not a prerequisite for this design.Resolve identity and key reuse before activating dependent GC
Whether a deleted layer UUID or physical key can be reused is an explicit design choice, not settled by this proposal. A no-resurrection policy must state which terminal records are retained and account for their storage cost. A reuse policy must isolate the new artifact from a delayed DELETE authorized for the old artifact. A database connection ending or a timeout does not prove that an external storage request ended.
Do not enable reclamation that relies on either policy before its admission, retry, and late-request behavior is specified and tested. This issue does not introduce a second layer UUID or silently select a storage-generation scheme.
Clear large payloads before pruning identities
Retained database size includes active resources, permanent aliases, legacy identities, and the layer/deletion records required by the chosen policy, as well as history waiting to be pruned.
A monotonic floor F for first identity admission
New S/H use UUIDv7. First admission uses its immutable generation timestamp,
birth(id), rather than businesscreated_at. F is a durable value in a fixed Catalog control record alongside schema metadata and only increases. This control record does not grow per S/H and does not imply a fixed business-table count. F governs snapshot/hold admission and eligible history only; it does not govern layer UUIDs, mutable volume IDs, or external I/O completion.Classification must happen inside the same transaction. Every entry point that can first INSERT/upsert S/H, including create, Publish, Pin, Delete, and Release, takes a shared F lock before following the business identity, content-root, and layer lock order specified by the Catalog transactions. It retains the F lock until commit. An existence check outside the transaction cannot justify skipping the barrier.
The background task advances
F=max(F, reference_time-W)in a short transaction. PostgreSQL usesFOR UPDATE NOWAITand skips the attempt if busy; ordinary admission usesFOR SHARE. The advancement transaction scans no business tables and performs no external I/O. SQLite uses the equivalent ordering within its serialized write transactions. An unknown commit reply requires an authoritative read of F.Separate batches then prune only Closed H with birth <= confirmed F, and S with birth <= confirmed F that is retired, fully unlinked, alias-free, and has no remaining heads, required dependencies, or active H. These batches do not acquire F after business-row locks. F never moves backward; restoring an old backup must not reopen a previously closed admission range.
Ensure background work makes progress
A controlled maintenance job runs online without a separate service or leader election. It scans pending content retirement/head-release work, layer candidates, and persistent physical-deletion work separately. Marking and traversal are bounded database work; they are not a full manifest download pass. Each round captures a primary-key upper bound and uses keyset pagination, proceeds past temporarily blocked candidates, then starts another round from the beginning. S/H history pruning also revisits rows that were previously ineligible.
A layer that still has references is retained; later head or successor removal makes it eligible for another check. A content entry previously blocked by a hold, or a layer marked behind the current cursor, is revisited in the next round rather than being starved by repeatedly processing the first page. Any hint optimization must preserve that progress guarantee.
Clean up unregistered uploads only after their writes/commits have ended, or reliable isolation establishes that cleanup cannot race them without stopping user operations. Otherwise retain them. F is not evidence that an upload, VM, or storage request has finished.
Review and validation
Compatibility and operational impact
This adds F, codecs for compact terminal rows, and new admission-error semantics. All writers and final Release paths must be compatible, and historical data import must have ended, before pruning is enabled. W limits time from identity generation to first registration, not active H lifetime. A capture that has never been published may eventually return Expired; callers must not silently generate another ID to repeat a side-effecting operation.
F adds shared-lock work on one control record. NOWAIT advancement can lag, so W is not a hard bound on retained history. Measure lock waits, actual F lag, cleanup backlog, and database space-maintenance cost. Low-reuse workloads still pay for Pin and eventual Release on each first preparation.
Aliases remain reserved after retirement, complete historical descriptions are removed after unlinking, and pruned identities no longer reproduce exact historical Conflict results. Asynchronous
memory_startupattachment must register its additional dependencies atomically and retain producer responsibility until handoff; include that path in GC validation.Alternatives considered
Non-goals
Do not expire active H, use S/H age to prune layer/deletion records, add a session system for orphan uploads, or change volume mount/read-write ownership or the external registry's own GC contract. This proposal includes shared content protection for volumes and registry retention roots, but does not preselect an identity/key-reuse policy.
Contribution
I can provide more design details
Pre-submission checklist
@dushulin commented on GitHub (Oct 13, 2026):
@guozy18 hi, May I ask when this capability will be available? We urgently need it internally. We use JFS as the snapshot storage backend and access it via the POSIX protocol. The number of sandbox snapshots we need to save each day is quite large (at the PB level), and we clean them up on a weekly cycle (so the total capacity is at the 10 PB level). However, we are not sure how to delete the layers data.
@yingdi-shan commented on GitHub (Oct 20, 2026):
We are working on it, but this is a large refactor. So this may take some time.
@dushulin commented on GitHub (Oct 20, 2026):
@yingdi-shan Thank you for your reply. Also, I noticed an earlier P2P issue (#12), which mentioned that snapshots would be stored in an iroh P2P cluster while the data itself stays local. I'd like to ask: before the P2P capability is implemented, what network storage underlies the previously claimed snapshot writes and reads for 1.5 million concurrent sandboxes? What are the QPS and throughput requirements for those operations? And if further scaling is still desired, would it then be constrained by the network storage's performance?
@yingdi-shan commented on GitHub (Oct 20, 2026):
Snapshot writes and reads go through cloud object storage (e.g., S3-compatible services). These services scale horizontally and are designed for very high aggregate throughput, so storage performance has not been a bottleneck at our current scale, and we expect it to remain so as we scale further.