fix(deploy): give Postgres a CPU floor that Docker actually honours
`deploy.resources.reservations.cpus` was doing nothing. Outside Swarm, `docker compose up` silently drops it — verified by inspecting a running container, where CpuShares, CpuQuota and CpusetCpus were all unset while `limits.cpus` and `reservations.memory` came through as NanoCpus and MemoryReservation. So the comment calling it "the piece that actually protects the database" described a guarantee the box never had. It matters on the CX22 the runbook targets: the ceilings sum to 1.2 + 0.6 + 0.5 = 2.3 on 2 vCPU, so the other services can oversubscribe the machine, and with every container on the default weight Postgres competed on equal footing with two image resizes and an ffmpeg poster. Replaced with `cpu_shares`, which does survive the translation — db 2048, caddy 1024, app 512, frontend 256 — so the weighting only binds when the CPU is actually saturated, which is the moment the database must not lose. The Caddyfile gains a 10s header-read timeout: there was no read timeout anywhere, so a client could hold a connection, a tokio task and a `.tmp` file open indefinitely by sending one byte a minute, and the upload sweeper is keyed on mtime precisely so a live upload never ages out. Body reads stay unbounded — a 500 MB video over cellular legitimately takes minutes, and a body timeout would fail exactly the uploads this product exists to collect. .env.example documents that estimated_guest_count is a live input to the quota divisor rather than the inert setting both it and the runbook previously implied. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
21
Caddyfile
21
Caddyfile
@@ -1,3 +1,24 @@
|
||||
{
|
||||
servers {
|
||||
timeouts {
|
||||
# Slowloris defence, at the layer that can actually apply it.
|
||||
#
|
||||
# There was no read or write timeout anywhere, so a client could open a POST, send one
|
||||
# byte a minute, and hold a connection, a tokio task and a `.tmp` file indefinitely —
|
||||
# and the upload sweeper is keyed on mtime precisely so a live upload never ages out,
|
||||
# so ten such connections consumed disk the upload gate could not see.
|
||||
#
|
||||
# read_header is tight: a legitimate client sends its headers in one go.
|
||||
read_header 10s
|
||||
# read_body is NOT set, and idle is generous: a guest pushing a 500 MB video over
|
||||
# cellular legitimately takes many minutes, and a body timeout would fail exactly the
|
||||
# uploads this product exists to collect. The header timeout is what stops the cheap
|
||||
# attack; a slow *body* still has to actually send bytes.
|
||||
idle 5m
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{$DOMAIN} {
|
||||
# Compress everything EXCEPT the SSE stream — gzip buffering delays
|
||||
# "real-time" likes/comments until the ~30s keep-alive tick.
|
||||
|
||||
Reference in New Issue
Block a user