d49d4a1025
Adds an opt-in load benchmark suite (`-DrunLoadBenchmark=true`)
covering: held-open WebSocket count, single + concurrent EVENT
publish throughput, and live-event fan-out latency to N
subscribers on one connection.
Numbers from a small VM (4 GiB RAM, 4 vCPU, ulimit -n 4096):
Connections (raw WS, one REQ each)
100 : 100 OK, 0.7 s
500 : 500 OK, 1.2 s
1000 : 1000 OK, 2.0 s
2000 : 1993 OK (hits the 4096-fd ceiling: 2 fds per WS)
Single-publisher serial publish + OK
10000 events, 13.2 s, 760 EPS (round-trip latency bound)
Concurrent publishers (each on its own WS)
parallel=2 : 807 EPS
parallel=4 : 1452 EPS
parallel=8 : 1989 EPS ← knee
parallel=16 : 1704 EPS ← SQLite single-writer ceiling
parallel=32 : 1778 EPS
Fanout (one publish, N active subs on a single WS)
100 subs : 67 ms last
500 subs : 52 ms last
1000 subs : 51 ms last
2000 subs : 111 ms last
Bumped SESSION_OUTGOING_BUFFER from 1024 → 8192. The 1024 cap was
hit by the 2000-sub fanout test (2000 outbound frames into one
session's queue), causing the relay to drop the connection as a
slow-consumer protection. 8192 fits the realistic upper bound for
a high-fan-out client (a few thousand subs on one WS) and caps
per-session memory at ~2 MiB before we drop.
Numbers also confirm:
- The SQLite single-writer plateau is around 2000 EPS on this
box. Production behind WAL + a faster disk should beat that.
- Each WebSocket consumes 2 file descriptors. Operators must
raise `ulimit -n` to ~3× their target connection count.
- Fan-out scales sub-linearly (2000 subs ≈ 2× the latency of
100 subs) — the bottleneck is shared (broadcast + SQLite
write), not per-sub.
Total :quartz-relay tests: 99 (4 new benchmarks, opt-in), 0 failures.