← Back to blog

· · Updated

Latency Tracing and Capacity Testing

How a 0.048ms handler sat inside a 111.85ms request, and what a historical 900-connection private test and single-client public ladder actually proved.

A fast handler can still serve a slow request

During the September 8, 2026 investigation, one production request spent about 111.85ms inside the server boundary while its handler took only 0.048ms. About 111.78ms elapsed after arrival and before the process read it, including approximately 80.74ms waiting for CPU. Handler-only timing misses that wait.

The initial workload had 100 nine-player tables, 900 simulated connections and 1,000 history, session and statistics queries per second. Of 60,000 HTTP requests, 4,655 returned 503. Hardware, query mix and the generator changed before the final run, so the initial and final figures do not establish a controlled overall speedup.

Separate business work, persistence and measurement

The changes included memory read projections, batched persistence, history indexes and caches, and reuse of public broadcasts. Required balance and settlement writes still depend on database commit; optional analysis must not be confused with durable business records. Current related implementations live in server/src/live_directory.rs, server/src/session_registry.rs, server/src/write_behind.rs and server/src/outbound.rs.

On September 9 we also capped the then-existing post-hand analysis thread and batched connection writes, up to 16 frames at a time. The analysis thread and its quota are historical implementation details, not a claim about today’s version. The current bounded relay is in server/src/ws/socket.rs. Making the load generator yield after eight overdue requests fixed the measuring tool; it was not a player-facing speedup.

What 900 connections established

The September 9 production-host private-network run on web-2026.09.09.5 shared two vCPUs between generator and service. It used 100 nine-player tables, five target action opportunities per table per second and 1,000 realtime HTTP queries per second. About one minute completed 25,030 actions and 688 hands; all 60,000 HTTP requests succeeded.

Measurement boundaryp99
HTTP ingress to egress2.57ms
WS ingress to nine-recipient broadcast egress4.48ms
Scheduled action to all nine recipients63.80ms
HTTP end to end197.69ms

Server and end-to-end latency are different metrics; independently calculated stage p99s cannot be added. The colocated generator, Postgres and service competed for resources, and the private path excluded public networking, TLS and the reverse proxy. This is neither a 900-public-player experience guarantee nor current-version capacity evidence.

The public test path became a limit

On the same day, a single-client public ladder on web-2026.09.09.3 tested a slower pace of one target action per table every 2.5 seconds. At 117 players, two three-minute runs returned 215.48ms and 142.37ms p99; 126 returned 606.36ms. At the rapid pace, 45 players returned 267.41ms over three minutes; two 54-player runs returned 506.12ms and 290.04ms, straddling the 500ms threshold.

Later probes showed query latency rising with that client’s connection count while the game-server process stayed lightly loaded. The full 900-connection public attempts did not complete. These results bound what that client and path could deliver, not the service ceiling. One 225-player run also timed out releasing a seat and failed cleanup acceptance; a final database check found the fixture seats released. The whole run cannot be described as fully passing.

Fresh capacity evidence needs source-matched versions, complete request and action correlation, a calibrated independent generator, and explicit path, workload and acceptance budgets. Button feedback and screen updates on real devices require separate observation.