Benchmarks
2026-10-05 — Acceptance gate 6: soak and overload (Docker Desktop / WSL2)
Gate (handoff §16): load tests demonstrate bounded memory under sustained load and
overload produces backpressure/rejection rather than unbounded queues. Scripts and
criteria: bench/soak.sh, bench/overload.sh, bench/README.md. Host: Docker Desktop on
Windows (WSL2 kernel 6.18), 12 CPUs pinned as in the other benchmark reports (server 0–3,
applications 4–5, load 6–11). Absolute throughput is not representative of bare metal;
the gate is about the shape of memory and the server’s behaviour.
Soak — 30 minutes, every serving path at once — PASS
16 connections per scenario, nine scenarios concurrently:
scenario requests rps p99_ms errors non_2xx
static-small 7177478 3987 9.01 0 0
static-1m 6711366 3728 9.53 0 0
proxy 3597213 1998 15.20 0 0
proxy-new-conn 2920057 1622 18.67 0 0 (no keep-alive: a new connection per request)
cache-hit 7202436 4001 8.97 0 0
tls-h2 7104865 3947 9.06 0 0
php 1694807 941 29.14 0 0
node 3581808 1989 15.24 0 0
python 3239937 1799 17.82 0 043.2 million requests, no errors, no non-2xx responses.
scb-webd resident memory (sampled every 10 s; excerpt):
t (s) 0 180 360 540 721 901 1081 1261 1442 1622 1802
RSS MiB 12.6 24.4 24.8 25.1 25.1 24.4 24.6 24.2 24.4 23.6 23.1
fds 59 240 242 243 232 241 242 238 245 245 94Verdict line: PASS baseline_rss_mib=25.3 final_rss_mib=24.6 peak_rss_mib=25.3 slope_mib_per_h=-4.10 baseline_fds=250 final_fds=250. Memory reaches its working set
within the first minutes and then stays flat (slightly decreasing); descriptors and
threads (6–7) are constant. A 3-minute run earlier gave the same picture (24.5 MiB, slope
0.00).
Overload — 2000 clients vs. a 64-request quota — PASS
Tenant slow: 50 ms application, max_concurrent_requests: 64 (theoretical capacity
≈ 1280 req/s). 60 s with 2000 concurrent clients, while a probe (4 clients) loads the
static tenant bench:
PASS admitted_rps=953 refused=7447042 admitted=57182 codes=200,503 probe_p99_ms=57
rss_first_half_mib=65 rss_second_half_mib=65 p50_before_ms=51.5 p50_after_ms=51.5- Excess load is refused immediately (503 +
Retry-After) — 7.4 M refusals — instead of queueing; the admitted requests keep the application at its capacity. - Memory is flat at 65 MiB during the overload (2000 open connections).
- The other tenant keeps 100 % success with p99 57 ms on shared, saturated CPUs.
- After the overload the slow tenant’s p50 is identical to before (51.5 ms).
- The first run of the script reported 1872 “errors”: all were oha’s “aborted due to deadline” (requests in flight when the load generator’s timer ends), not server errors; both scripts now exclude that category.
What this does not show
- Bare-metal numbers and longer runs (hours/days) — the scripts take any duration.
- Overload by connection count rather than request rate: beyond
max_connections, accept pauses and the kernel backlog holds new connections (covered by integration tests for the global limit and per-source limits, not by this benchmark).