@@ -43,8 +43,40 @@ it**. Two mitigations are therefore built in:
4343 ` --timeout 0 ` is required — a worker mid-import does not heartbeat, and gunicorn's
4444 default 30s watchdog would kill it. ` --keep-alive 5 ` and ` --forwarded-allow-ips '*' `
4545 preserve the previous uvicorn keep-alive and proxy-header behavior.
46+ ` --max-requests 500 ` (+jitter 100) recycles workers to bound slow memory growth under
47+ sustained load; a recycling worker re-imports for ~ 3 minutes, halving instance
48+ capacity while it does.
4649- Candidate deploys set ` --cpu-boost ` to shorten the import.
4750
51+ ## Runtime shape and scaling (from the Stage 2 qualification)
52+
53+ Values measured and justified in
54+ [ ` history/pr4-stage2-runtime-timing.md ` ] ( history/pr4-stage2-runtime-timing.md ) :
55+
56+ - ** ` WEB_CONCURRENCY=2 ` ** — the engine is GIL-bound; two worker processes materially
57+ outperform one on the 4-vCPU instance. Set via the deploy script's env vars.
58+ - ** ` --concurrency 4 ` ** — admission control is what keeps the two-worker instance
59+ memory-safe in 16Gi; excess load queues at the platform and fails as client timeouts
60+ rather than dying in-instance. ** Both knobs are pinned explicitly on every deploy** :
61+ gcloud inherits unspecified template fields (a mid-campaign CI deploy once shipped an
62+ inherited test concurrency), and ` --concurrency default ` resolves to the * platform*
63+ default (640), not the historical 80.
64+ - ** Scaling pins live in ` push.yml ` per job** : production ` max-instances 4 ` (peak real
65+ traffic ~ 11 RPS, mostly cached/light; ~ 1–2 concurrent uncached calculates per
66+ instance), staging ` min 0 / max 1 ` .
67+ - ** Warm capacity is a service-level setting made manually, once** — CI keeps
68+ revision-level ` --min-instances 0 ` , because revision-level minimums keep a warm 16Gi
69+ instance alive per accumulated ` stage3-* ` tag:
70+
71+ ``` bash
72+ gcloud run services update policyengine-api \
73+ --project policyengine-api --region us-central1 \
74+ --min-instances 1
75+ ```
76+
77+ Rationale: the user-facing scale-from-zero wake was measured at 282.8s — 17s under
78+ the 300s request timeout — so a cold start must never sit on a public request path.
79+
4880Consequences to keep in mind:
4981
5082- An instance is "started" (port bound) before the app can answer. Requests arriving in
0 commit comments