Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
3.3 KiB
R-762 second half — wger under gunicorn, measured (2026-10-06 night, bench 9401)
What was measured. wger 2.7 (wger/server:2.7, the catalog template on main d63ea35: DJANGO_DEBUG=False and the
wger-files nginx), the template's limit 384M (deploy.resources.limits.memory: 384M; the file server 32M). Three
runs, each on fresh volumes, on test bench VM 9401 (demo-hp): Django runserver (today's template), and gunicorn with
1 and 2 workers. No traefik: ports on the bench's loopback (wger 18000, the file server 18080). Script and compose:
bench/run.sh, bench/compose.yml; raw outputs bench/out-*/.
How the worker count is set. The image's entrypoint runs gunicorn wger.wsgi:application --preload --bind 0.0.0.0:$PORT when WGER_USE_GUNICORN=True — no -w and no config file in the image, so gunicorn's own default
applies: WEB_CONCURRENCY (unset = 1). Control from a different channel — the container's own log
(bench/out-*/server-lines.txt): 1 × „Booting worker" for gunicorn1, 2 × for gunicorn2, „Starting development server"
for runserver.
| Mode | Workers | anon peak (MB) | % of 384M | oom_kill | restarts | ready after | login page (10 × seq.) | its CSS via wger-files | 20 GETs, 10 parallel |
|---|---|---|---|---|---|---|---|---|---|
| runserver (today) | — | 201 | 52 % | 0 | 0 | 81 s | 200, avg 32 ms | 200, 1 ms | 200 ×20, avg 394 ms |
| gunicorn | 1 | 165 | 43 % | 0 | 0 | 75 s | 200, avg 31 ms | 200, 1 ms | 200 ×20, avg 241 ms |
| gunicorn | 2 | 157 | 41 % | 0 | 0 | 72 s | 200, avg 43 ms | 200, 1 ms | 200 ×20, avg 124 ms |
- anon peak: the highest
anonin the container'smemory.stat, sampled every 0.5 s from start to the end of the load (MB = MiB).memory.peakread 384 MiB in all three runs — the file cache fills the limit (collectstatic and the sqlite file); it is not the app's memory and caused no kill (memory note:memory.peakcounts the page cache). - With
--preload, the two workers share the app's pages with the master, so 2 workers did not cost moreanonthan 1 here (157 vs 165 MB; the difference is run-to-run noise, not a saving). - ready after: from
compose upto the first 200 on the login page — dominated by bootstrap, collectstatic and migrate on a fresh volume, not by the server.
Not measured: a signed-in page. The scripted sign-in did not take (the form came back 200; the dashboard answered 302 to the login) and the brief said „if cheap". Nothing else was tried.
Pick the numbers support: gunicorn with 2 workers (WGER_USE_GUNICORN=True, WEB_CONCURRENCY=2): 41 % of the
limit, no kill, and half the wait of 1 worker when ten requests arrive together. 1 worker also fits; runserver is the
heaviest and is a development server.
Teardown. Bench 9401 was stopped when found; started 21:16, stopped again after the runs (pct status: stopped).
The project's containers, volumes and network were removed by docker compose down -v on the project r762 only
(0 left by name); /root/r762 deleted. The two images were already on the bench and stay. No prune of any kind. No
Docker command ran on DooPlex. Host: nothing created (the copy files in /tmp were deleted). Hub: nothing.
The image's default admin password appears in its own bootstrap log line; it is redacted in these files.