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          0

43.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     94

Verdict 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).