Summary
On a freshly booted sandbox whose overlaybd rootfs is cold on the local node (registryfs backend, block cache empty, any registry-mirror / P2P upstream may or may not be warm), the first metadata-heavy workspace scan run from inside the guest takes ~2 orders of magnitude longer than the same command re-run seconds later in the same sandbox.
By "metadata-heavy workspace scan" we mean anything that has to stat/open a large fraction of the application checkout and its VCS working directory: a full tree walk, a full diff against HEAD, a full clean pass, etc. — the workloads a batch of AI-agent rollouts routinely emit as their very first step per sandbox.
In a rollout-style eval (many short-lived sandboxes, one first-step scan each) this single class of first-time operation is the dominant driver of per-command wall-clock budget overruns, even when no individual command's logical work should exceed that budget on a bare Linux host.
Reproduction sketch
- Overlaybd rootfs image with a non-trivial application checkout on it (a typical "AI agent working on an open-source repo" template).
- Boot a fresh sandbox on a node whose local overlaybd cache is cold for that tag.
- Inside the sandbox, run one metadata-heavy workspace-scan command. Time it.
- Immediately re-run the same command in the same sandbox. Time it.
The ratio, on our NVMe-backed nodes with a warm registry-mirror upstream but an empty local overlaybd block cache, is a two-digit multiplier.
Where the time goes (from instrumenting the cold path)
Two structural properties, not an obviously-fixable hot path:
- Registryfs_v2 serves reads at a 64 KiB block / 256 KiB refill granularity, over the network, into a cache that starts empty. A metadata scan induces thousands of them.
- Effective concurrency of those fetches while a single guest command is running is essentially the guest's own outstanding-lookup depth.
PrefetchConfig.concurrency exists (default 16) but is only used when a trace-based prefetcher is active.
Once the metadata blocks land in the guest kernel page cache, the second run has ~no IO to do. The problem is that nothing pulls ahead of the guest during the first run.
What already exists in-tree but is not wired
The overlaybd code already ships the trace-prefetch primitive under storage/overlaybd/src/prefetch.rs (PrefetchMode::Record | Replay | Disabled), matching the design containerd/accelerated-container-image uses. On our reading of storage/overlaybd/src/image/image_file.rs::init_image_file (around L322–L340), the prefetcher is only constructed when the image config declares acceleration_layer or when a record_trace_path file already exists on disk. Neither is produced by the sandbox launch path today, so this code effectively never runs in production.
Separately, Firecracker Diff snapshot / resume already preserves the guest kernel page cache across pause / resume — so a deliberate "boot → warm → pause → publish, later resume users into it" pipeline would sidestep the cold path entirely for on-node warm resumes. Nothing in the current template-builder flow does this as a warm-up step.
Ask
We would like guidance on which direction upstream considers the right long-term fix, so we can contribute rather than fork:
- Wire trace prefetch into the sandbox launch path. Either an explicit "warm-up hook" the template builder runs once to record a trace against the target rootfs, or an auto-record-on-first-launch mode that emits the trace next to the image and replays on subsequent cold starts. The mechanism already exists; there is just no producer for the trace input.
- Formalize a "pre-warm then snapshot" step in the template builder so an image ships with metadata blocks already in a Diff-snapshot memory image, and cold creates route through it. Complementary to (1): (1) helps ad-hoc images, (2) helps the fleet-wide case where the same image is used many times.
- Raise on-demand concurrency for registryfs_v2 during cold periods independent of trace prefetch (bounded read-ahead below the guest's request rate). Smallest change, smallest expected effect — only worth it if (1)/(2) are both non-goals.
Happy to run measurements against any candidate patch on the same workload if that is useful, and to prototype (1) if the direction is agreed.
Summary
On a freshly booted sandbox whose overlaybd rootfs is cold on the local node (registryfs backend, block cache empty, any registry-mirror / P2P upstream may or may not be warm), the first metadata-heavy workspace scan run from inside the guest takes ~2 orders of magnitude longer than the same command re-run seconds later in the same sandbox.
By "metadata-heavy workspace scan" we mean anything that has to stat/open a large fraction of the application checkout and its VCS working directory: a full tree walk, a full diff against HEAD, a full clean pass, etc. — the workloads a batch of AI-agent rollouts routinely emit as their very first step per sandbox.
In a rollout-style eval (many short-lived sandboxes, one first-step scan each) this single class of first-time operation is the dominant driver of per-command wall-clock budget overruns, even when no individual command's logical work should exceed that budget on a bare Linux host.
Reproduction sketch
The ratio, on our NVMe-backed nodes with a warm registry-mirror upstream but an empty local overlaybd block cache, is a two-digit multiplier.
Where the time goes (from instrumenting the cold path)
Two structural properties, not an obviously-fixable hot path:
PrefetchConfig.concurrencyexists (default 16) but is only used when a trace-based prefetcher is active.Once the metadata blocks land in the guest kernel page cache, the second run has ~no IO to do. The problem is that nothing pulls ahead of the guest during the first run.
What already exists in-tree but is not wired
The overlaybd code already ships the trace-prefetch primitive under
storage/overlaybd/src/prefetch.rs(PrefetchMode::Record | Replay | Disabled), matching the design containerd/accelerated-container-image uses. On our reading ofstorage/overlaybd/src/image/image_file.rs::init_image_file(around L322–L340), the prefetcher is only constructed when the image config declaresacceleration_layeror when arecord_trace_pathfile already exists on disk. Neither is produced by the sandbox launch path today, so this code effectively never runs in production.Separately, Firecracker
Diffsnapshot / resume already preserves the guest kernel page cache across pause / resume — so a deliberate "boot → warm → pause → publish, later resume users into it" pipeline would sidestep the cold path entirely for on-node warm resumes. Nothing in the current template-builder flow does this as a warm-up step.Ask
We would like guidance on which direction upstream considers the right long-term fix, so we can contribute rather than fork:
Happy to run measurements against any candidate patch on the same workload if that is useful, and to prototype (1) if the direction is agreed.