GK/SPEC-2026
DOC GK-003 TYPE ENGINEERING LOG SUBJECT MAYFLY GUEST

The fault count is the thesis. The milliseconds are not.

Mayfly Guest is a snapshot-optimised Linux guest image and host tooling for ephemeral agent sandboxes on Firecracker. A trimmed conventional guest: kernel config, static PID 1, small rootfs, and open restore-to-usable harnesses. Not a from-scratch kernel, not a unikernel. Apache 2.0. Source on GitHub.

01The number I refused to claim

Every timing in this project was measured on WSL2, under nested virtualisation. That inflates absolute latency on exactly the path being measured. I could have published the numbers anyway; microVM restore figures are the kind of thing people quote without checking the conditions.

Instead the caveat is the first thing in the README, above the results table, in capitals. Trust the ratios, the fault counts, and the bytes copied. Treat wall-clock milliseconds as ordering hints on this host only. Revalidation off WSL2 is a named, outstanding stage, not a footnote I hope nobody reads.

This costs me the headline. A number like "30 ms restore" travels; "ratios only, pending revalidation" does not. But a benchmark whose conditions are buried is not a measurement, it is marketing, and the second someone runs it on real hardware the gap becomes my credibility problem.

02Three backends, and why 2M wins twice

The harness measures restore-to-usable: not "the API returned" but "the guest answered a vsock echo". That distinction matters, because a VM that has resumed but cannot yet do work is not restored in any sense a caller cares about.

File restore is the simple path. UFFD at 4K faults pages in on demand and pays for it: 1563 faults and 6.1 MiB copied before the guest is usable. UFFD with 2M hugepages collapses that to 25 faults, at the cost of copying 50 MiB, because each fault drags a far larger page.

So 2M is not simply faster; it trades bytes for fault count. On this host it wins both, but the reason to prefer it is the fault count, which is a property of the design rather than of my laptop. That is the number I would still stand behind on different hardware.

03The optimisation that would not have worked

The intuitive way to make a guest snapshot smaller is to make the rootfs smaller. I measured before assuming, across every rootfs size down to an initramfs floor, and the cold Full-snapshot non-zero working set stayed at roughly 33 MB, kernel-dominated.

That single measurement redirected the whole project. Effort spent shaving the userspace image would have produced almost nothing; the kernel config is where the working set is decided. Stage 3 went into kernel configuration experiments instead, and honestly recorded that several of them produced no net gain either. ACPI, for instance, turned out to be required and could not be removed.

Recording the experiments that failed is the point of a lab notebook. Anyone repeating this now knows which two doors are already closed.

04A benchmark that cannot fail is not a benchmark

The harness has a mode that deliberately corrupts guest memory at the load address before restoring. The probe is then required to fail. If it passes, the ruler is not touching the thing it claims to measure, and every number it has produced is worthless.

Every measurement harness needs this check and most do not have one. It is very easy to write a probe that reports success because it is quietly testing the host, the cache, or nothing at all. Proving your instrument can detect a broken system is what earns the right to believe it when it reports a working one.

05What broke, and what I would change

What broke: on WSL hosts Firecracker could not open /dev/userfaultfd even after the sysctl and ACL work, so I built a privileged Docker fallback to get UFFD running at all. Those timings are not host-fair, and rather than quietly folding them into the table I marked them retracted in the results doc and use them for fault counts only.

What I would change: the honest answer is that the whole measurement environment is wrong for the claims people will want to make, and I knew that from the first run. Stage 6 revalidation on bare metal should have been scoped at the start rather than deferred, because the deferral means the headline table can only ever support relative statements.

What it does not do: Firecracker snapshots remain a developer preview, and restored clones share machine IDs, UUIDs, entropy pools, and anything else present at pause time. That is fine for restore benchmarks and unsafe for multi-tenant production without a dedicated reseal story. This is a research artifact and the README says so in those words.

Harnesses, kernel configs, and the raw stage findings are all in the repository. The write-up the results come from is docs/findings-post.md.