Skip to content

atelet: node-local cache for golden snapshots (OnGolden data resumes re-download the golden every time) #690

Closed

Description

Follow-up from the #683 review (#683): with onResume.fromData: Golden, every resume of a Data snapshot downloads the template's golden snapshot from object storage — including un-pause, which is meant to be the fast, node-local path. The golden's memory image is the dominant transfer, so this adds significant latency per un-pause. The pre-existing golden-fallback boot (fresh actor, no snapshot) has the same characteristic today.

Proposal

Cache golden snapshot files node-locally in atelet, content-addressed by the golden snapshot URI prefix (a golden snapshot is immutable once written, and one golden serves every actor of the template on the node):

  • On a DATA_ON_GOLDEN restore (or golden-fallback boot), download the golden's files once into a shared cache dir (alongside the existing static-files/sandbox-asset cache), then hardlink or copy them into the per-actor restore dir.
  • Subsequent restores on the node skip the download entirely.
  • Eviction: goldens are per-template-version; an LRU or size cap over the cache dir handles template churn. Invalidation is not needed for a given URI (immutable), only cleanup of unused goldens.

Care points: the restore dir must remain private per actor (cloud-hypervisor demand-pages from it for the VM's lifetime, so hardlinks must not allow cross-actor mutation; and the cache write must be atomic (temp + rename) like the asset cache.

🤖 Generated with Claude Code
EOF
)

Activity

  1. dberkov commented on Sep 4, 2026

    @dberkov
    CollaboratorAuthor

    Design proposal: filecache — a generic node-local artifact cache in atelet

    Full design written up as docs/dev/filecache-design.md (PR to follow); summary below. It covers this issue's golden-snapshot case and, in a later milestone, folds the sandbox-asset cache (fetchAsset/fetchGVisorRelease) onto the same library — that path has the same check-then-download race and never deletes anything.

    Shape

    A small library at cmd/atelet/internal/filecache, porting the mechanics internal/imagecache already proved for OCI layers (singleflight dedup, mtime-LRU, two-phase retire) to a generic key→file cache. Two independent store instances: a golden store and (M3) an assets store, each with its own root and byte budget.

    func (s *Store) GetFileTo(ctx, key Key, dst string, fetch FileFetcher) error  // golden files
    func (s *Store) GetFile(ctx, key Key, fetch FileFetcher) (Entry, error)       // in-place single files
    func (s *Store) GetDir(ctx, key Key, fetch DirFetcher) (Entry, error)         // extracted trees (gVisor release)

    Fetchers are injected (the store owns dedup/publication/GC; download policy — zstd+sparse for goldens via ategcs.FetchLocalFileFromGCSWithZstd, sha256+auth-fallback for assets — stays at the call site). Keys are identities, not addresses: URIKey(goldenURI, file) for goldens, SHA256Key(hex) for content-addressed assets.

    Golden restore path (this issue)

    GetFileTo is atomic get-and-link:

    • Miss: concurrent callers for one key join a single flight (singleflight.DoChan); the download runs on context.WithoutCancel + timeout, so one canceled restore never kills the download other resumes are waiting on. Fetch lands in tmp/, is chmod'd 0444, and is atomically renamed into the cache — crash-safe, never a partial file.
    • Hit: stat + hardlink into the actor's restore dir + mtime touch. N concurrent DATA_ON_GOLDEN resumes (or golden-fallback boots) of one golden = one download; every subsequent restore on the node is link-time.

    The hardlink answers the demand-paging care point from the issue description directly: the actor's link shares the inode, so even if GC evicts the cache entry mid-VM-lifetime, cloud-hypervisor's mapping stays valid until the actor dir is wiped. Eviction can only ever cost a re-download, never break a running actor. Cross-actor mutation is excluded by design assumption (restore files are consumed read-only) and enforced mechanically: cache entries are published 0444, so an in-place write fails with EACCES instead of poisoning the shared copy.

    GC

    Pressure-driven only — age alone never removes a file. Per-store budget (--golden-cache-max-bytes, absolute or % of disk capacity, resolved via statfs at startup), LRU by entry mtime, a per-store min-age guard for the just-published window, and preference for entries with Nlink == 1 (evicting a still-linked entry frees nothing until the actor dir is wiped). Removal is two-phase like imagecache's retire: rename to .rm-* inside the key's singleflight (an in-flight fetch or fresh hit vetoes), bulk RemoveAll outside all locks. Startup sweep reaps tmp/* and .rm-* crash debris. The assets store (M3) additionally gets a root set recomputed each pass from the on-disk actor sandbox records, mirroring imagecache's InUse().

    Design assumptions (stated, not open)

    1. Golden objects are immutable under their URI — matches "immutable once written" in the issue; changed goldens must get new URIs, no generation suffix in the key.
    2. Restore consumes checkpoint files read-only — enforced by the 0444 tripwire.

    Milestones

    • M1 — library, link-out mode only: GetFileTo + GC core + concurrency/crash tests.
    • M2 — golden integration in Restore (downloadExternalCheckpoint/downloadCombinedCheckpoint golden halves, incl. the LOCAL-pause un-pause path, plus the golden manifest.json fetch) + GC driver loop. Exit criterion: two concurrent resumes from one golden on a node → one download.
    • M3 — GetFile/GetDir + root set; migrate sandbox assets and prewarm onto the assets store.
    • M4 — (conditional, metrics-gated) one shared GC coordinator owning the node-wide disk signal across golden/assets/image caches; v1 deliberately reacts only to per-store budgets.
    • M5 — metrics registry entries, e2e under eviction pressure.

    🤖 Generated with Claude Code

  2. dberkov commented on Oct 20, 2026

    @dberkov
    CollaboratorAuthor

    Closing: what this issue asked for, no re-download of a shared snapshot on a node, is on main, under today's names (tags, sharing) rather than the DATA_ON_GOLDEN the issue was written against, which #2105 removed.

    Milestones from the design comment, as landed:

    Not part of this issue, tracked separately: M4, one eviction coordinator over the node's disk, is #2987, open for discussion and not committed to. Two performance follow-ups that came out of #2796's review are #2948 (links instead of copies for micro-VM restores) and #2974 (keeping shared snapshot images resident outside actor cgroups).

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

Metadata

Metadata

Labels

area/storagekind/featureAn enhancement / feature request or implementation

Type

No type

Projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions