Serverless-first. Built for cold starts, tight memory, and one binary.Deploy guide →

Serverless MicroVM Benchmark: Cold-Start Telemetry vs. 5M Requests at 500 Concurrency

Most serverless benchmarks either mix cold starts into steady-state percentiles or obscure what is actually happening inside the host orchestrator versus inside the guest VM.

To obtain an honest, reproducible look at both wake-up overhead and raw sustained throughput, we ran a two-phase benchmark across four backend JavaScript runtimes (Fairyfly (ff), Bun, Deno, and Node.js) running inside isolated microVMs configured with:

  • 2 vCPUs
  • 1 GB RAM
  • Linux kernel with container/microVM hypervisor isolation

The benchmark was executed in two decoupled phases:

  1. Phase 1 (Isolated Cold Start): A single first request executed prior to load generation to dissect the exact host orchestrator overhead, network phases, and inside-VM application initialization time to bind and listen on the port.
  2. Phase 2 (High-Concurrency Load): 5,000,000 requests across 500 concurrent connections via oha to measure maximum sustained throughput (RPS) and full tail latency distribution (p10 to p99.9).

Phase 1: Isolated Cold-Start Breakdown

Phase 1 Isolated First Request Telemetry

When benchmarking serverless cold starts, reporting only end-to-end (E2E) time hides whether latency came from DNS resolution, host hypervisor dispatch, or the runtime bootstrapping its JavaScript engine.

Here is the exact telemetry for the first request:

Metricffbundenonode
Total E2E Time36.14 ms118.51 ms98.54 ms183.54 ms
DNS Lookup9.33 ms35.63 ms15.89 ms61.53 ms
TCP Connect0.39 ms0.32 ms0.21 ms0.42 ms
Total TTFB (Wake)26.41 ms82.55 ms82.43 ms121.56 ms
Host Orchestrator Tax16.00 ms (warm VM handoff)2.00 ms3.00 ms3.00 ms
Inside-VM App Cold Start~10.41 ms ⚡80.55 ms79.43 ms118.56 ms
Response Payload27 B (2 headers)24 B (3 headers)25 B (4 headers)22 B (4 headers)

Key Cold-Start Insights

  • ~10 ms App Cold Start: Inside the microVM, Fairyfly takes just 10.4 ms to boot QuickJS, parse the handler, and bind its 512-slot HTTP listener to the port. That is ~8x faster than Bun (80.5 ms) and Deno (79.4 ms), and over 11x faster than Node (118.6 ms).
  • Sub-30ms Total Internal Wake: Even with 16 ms of host orchestrator handoff tax, Fairyfly's total TTFB was 26.41 ms.
  • The V8 & JSC Bootstrap Tax: For Bun, Deno, and Node, host orchestration took only 2–3 ms; 96%–98% of their entire wake latency was pure runtime initialization inside the VM (allocating isolate heaps, loading snapshot blobs, and bootstrapping C++ runtime shims).

Phase 2: Warm High-Concurrency Stress Test (5M Requests @ 500 Concurrency)

Phase 2 Warm High-Concurrency Load Results

Once warm, we drove 5,000,000 HTTP requests across 500 concurrent connections using oha against the same 2 vCPU / 1 GB microVM instances.

Telemetry Summary

RuntimeWarm RPSAvg Latencyp50p95p99Max OutlierSuccess Rate
ff164,966.9 /s3.017 ms2.926 ms3.921 ms5.006 ms25.547 ms100% (0 drops / 5M)
bun163,610.1 /s3.043 ms2.944 ms4.020 ms5.294 ms21.803 ms100% (0 drops / 5M)
deno163,566.5 /s3.043 ms2.931 ms3.949 ms5.271 ms117.160 ms100% (0 drops / 5M)
node15,522.8 /s32.193 ms32.327 ms33.523 ms34.173 ms7,982.917 ms100% (0 drops / 5M)

Analysis: Why the Numbers Look Like This

1. The I/O Dominance Effect

Conventional wisdom assumes that JIT-compiled engines (V8 in Node/Deno, JavaScriptCore in Bun) must always beat interpreted engines (QuickJS in Fairyfly) on raw requests per second.

In real-world network services, this assumption fails. When processing high-concurrency HTTP:

  • Handlers spend the vast majority of CPU cycles in kernel context switches, socket readiness notification, header framing, and memory copies.
  • Fairyfly's networking engine is written in native Zig with libxev (io_uring / kqueue / epoll), a 512-slot Structure-of-Arrays (SoA) connection table, packed 2-byte connection flags, and refcounted header buffers.
  • Because the Zig layer handles the socket lifecycle and staging buffers with near-zero allocation, QuickJS only executes the tiny JavaScript callback. As a result, ff achieves 164.9k RPS, matching or slightly edging out Bun (163.6k RPS) and Deno (163.5k RPS).

2. Tail Latency & Memory Pressure (The 1 GB RAM Bottleneck)

At 500 concurrent connections on a tight 1 GB RAM footprint:

  • Node.js dropped to 15.5k RPS (10.6x slower). The combination of V8 heap allocations per connection, garbage collection compaction pauses, and event loop starvation resulted in average latencies of 32 ms and an extreme tail spike reaching 7,982 ms (~8 seconds).
  • Deno sustained 163.5k RPS, but suffered a 117.16 ms outlier spike.
  • Bun and Fairyfly demonstrated exceptional tail stability: Bun capped its worst request at 21.8 ms, and Fairyfly clamped at 25.5 ms, with p99 holding at 5.00 ms.
  • All 5,000,000 requests finished with a 100% success rate (0 dropped requests) on Fairyfly.

Takeaways for Serverless Architecture

  1. True Scale-to-Zero is Now Viable: If guest runtime cold start is ~10 ms instead of ~80–120 ms, serverless platforms can scale down to 0 instances aggressively without sacrificing user perceived responsiveness on the first request.
  2. Dense Packing on Smaller VMs: On a 2 vCPU / 1 GB machine, Fairyfly can sustain over 160,000 requests/sec across 500 concurrent sockets right inside its 512-slot table without thrashing the allocator or running out of memory.
  3. Auditability Without Speed Penalty: You do not need a multi-million line C++ engine to achieve top-tier server throughput. A clean ~24k line Zig codebase with QuickJS can match or outperform the heaviest runtimes on the web.