Skip to content

vm: mmap wake pays first-touch faults from the VM's quota — dense hosts must trade memory density against wake latency #188

Description

@CMGS

Summary

#187 correctly moved provisioning outside the child's ceiling: Prepare(..., deferQuota=true) holds cpu.max=max through the paused window and Arm installs the finite quota after the memory load, before resume. That fixes the copy-mode half.

#186 already called the mmap half out and consciously left it as a doc note:

Option 1 also covers mmap's deferred first-touch cost only if migration happens after warm-up, which it can't reasonably — so for mmap the residual is inherent and worth a doc note either way.

The note duly landed in docs/vm.md:

The mmap restore mode's deferred first-touch faults are inherent guest-lifetime work and do pay the VM's quota — on dense hosts prefer copy mode for latency-critical wakes or grant quota headroom.

This issue argues the residual is worth more than a caveat, because it binds hardest in exactly the regime the cgroup work was built to enable, and because our current measurements structurally cannot see it.

Why the caveat is load-bearing rather than cosmetic

Restore mode and quota are chosen for opposite reasons, and both point at the same hosts:

  • mmap is what makes dense hosts affordable — sibling clones of one golden share clean pages through the host page cache, so memory stops scaling with VM count.
  • A deliberately low quota is what makes dense hosts affordable on CPU — a Burstable shape (--cpu 2 --cpu-weight 25, per docs/vm.md) lets low-priority VMs yield under contention.

Take both and the wake path is throttled by the very knob that made the density possible. The documented escape hatches each surrender one of the two goals:

  • copy mode → gives up the page-cache sharing that motivated mmap in the first place;
  • "grant quota headroom" → gives up the CPU ceiling, i.e. the tail bound the scope exists to provide;
  • --cpu-burst-us → the kernel requires burst <= quota (docs/cli.md), so at a low quota the burst credit is small by construction. It cannot fund a first-touch storm.

So on an oversubscribed host there is currently no setting that keeps memory density, a bounded CPU tail, and a fast wake at the same time.

Why our benchmarks do not show it

Arm fires before resume, so the cost lands after the clone/restore call returns. Every clone-latency number we publish — including #186's table, where mmap under the 20-VM storm looks healthy at 114/198/230 ms p50/P90/max against copy's 516/1032/1348 — stops the clock before the guest has faulted in its working set. mmap looking good there is partly the measurement boundary, not only the mechanism.

The user-visible metric is claim to useful work, and that window is precisely where the deferred faults are throttled. I have not measured this — that is the first thing this issue asks for.

Proposal

1. Measure it. Extend the clone bench past the return: claim -> first exec -> steady state, under the #186 storm shape, mmap x {Guaranteed-at-N, Burstable low-weight, small explicit quota}, with nr_throttled / throttled_usec sampled from cpu.stat across the window. That table decides whether anything below is worth building.

2. If it is real: stage the quota instead of arming it once. Arm already establishes the primitive — a scope whose ceiling changes at a lifecycle point, idempotent, with the guest never running uncapped. The gap is that it is a single step-up-to-final. A wake-window policy would arm an elevated ceiling at resume and step down to the configured quota once the working set is resident, gated on a signal rather than a timer where possible:

  • simplest: elapsed time (--cpu-wake-quota-us + --cpu-wake-ms), trivially bounded, crude;
  • better: step down when cpu.stat throttling stops rising, or when the guest agent reports ready;
  • the step-down must be as convergent as Arm is today — a missed transition has to fail closed to the configured quota, never leave a VM permanently uncapped.

3. Cheap partial, independent of the above. copy vs mmap is currently an operator decision made once per clone. Given #187 exempts provisioning from the ceiling, copy's cost now lands in the exempt window while mmap's lands in the charged one — an inversion worth documenting explicitly in the restore_mode guidance, because the right default for a dense host may now differ from the right default for a quiet one.

Notes

Metadata

Metadata

Assignees

No one assigned

    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