Back to article

Slow receivers and API latency: experiment data

Measured on 2026-11-02. Article statistics use only the main measurements.

Conditions

Linux 6.12 / aarch64 / glibc 2.36, CPython 3.11.2, Gunicorn 23.0.0, NGINX 1.22.1 (Debian 1.22.1-9+deb12u10).

Files and aggregation

Latency-chart points are medians of three runs; bars show min–max. Write/receive bars are medians of six responses with two slow connections; dots show individual app write times. Time-series plots select the run with median p95 for each route. Each figure states its aggregation unit.

Reproduction

The standard-library measurement code needs Linux systemd user services and cgroup v2. prepare downloads and extracts Debian bookworm-security arm64 indexes and distributions into a dedicated directory; it does not install host packages. As it uses the current distribution index, a rerun may fetch a different NGINX package version. For exact versions, check URLs and SHA-256 hashes in dependencies.json.

This example assumes a Linux host matching the original conditions and sufficient spare resources. It is not a procedure for changing an existing service or cluster.

lab_root=$(mktemp -d /tmp/kamui-slow-client-XXXXXXXX)
cp app.py experiment.py "$lab_root/"
python3 "$lab_root/experiment.py" --root "$lab_root" --stage prepare
lab_unit="$(basename "$lab_root")-controller.service"
systemd-run --user --wait --pipe --collect --unit "$lab_unit" \
  -p CPUQuota=20% -p CPUQuotaPeriodSec=10ms -p MemoryMax=128M \
  -p MemorySwapMax=0 -p TasksMax=24 -p RuntimeMaxSec=720 -p TimeoutStopSec=3 \
  /usr/bin/python3 "$lab_root/experiment.py" --root "$lab_root" \
  --stage main --controller-unit "$lab_unit"

The code stops each run's servers in finally blocks. Collect results and verify that no dedicated experiment services or processes remain before removing only the dedicated temporary directory you created.

Exclusions and limitations

Exploratory pilots at 8MiB/1MiB/s and 16MiB are excluded from the main statistics. Moving from 8MiB/1MiB/s to 16MiB/2MiB/s changes both response size and read rate; the difference cannot be attributed to size alone. A host-wide CPU threshold stopped the main measurements once. The incomplete run was excluded; after 17 completed runs, the remaining 13 resumed without changing safety thresholds or application conditions.

receive_s ends after read-pacing waits, including the wait after the final socket read. Approximately eight seconds therefore includes pacing and is not pure real-network transfer performance. The gthread supplement changes worker model, process count, and concurrent capacity together; it does not isolate model effects or determine an optimal worker count.

The application is handwritten WSGI without a framework. There is no comparison of Flask, Django, FastAPI, asynchronous execution, or actual PaaS serving configurations. Individual TCP-buffer occupancy and worker stacks were not collected; observed write times, API latency, and temporary files must be distinguished from specification-based explanations.

This is not a maximum-throughput experiment. Individual OS TCP-buffer capacities, TCP_INFO, real Internet paths, TLS, HTTP/2/3, SSE, WebSockets, slow uploads, long durations, many connections, and disk exhaustion were not measured. Results do not guarantee performance for other web servers or PaaS platforms.

Markdown