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:
fabi
2026-08-11 22:44:48 +02:00
parent 32dfe6874a
commit 0c0d5d5981
6 changed files with 344 additions and 66 deletions

View File

@@ -344,7 +344,7 @@ COMPRESSION_WORKER_CONCURRENCY=2
| Styling | Tailwind CSS | Utility-first, mobile-first; zero runtime CSS overhead |
| Backend | Rust + Axum | Developer preference; memory safety, single-binary deploy |
| Async Runtime | Tokio | De-facto Rust async runtime; Axum is built on it |
| Database Driver | SQLx | Async PostgreSQL with compile-time query checking; automatic prepared statements |
| Database Driver | SQLx | Async PostgreSQL; automatic prepared statements. **Queries use the runtime `sqlx::query()` API, not the checked macros** — see the note under "Schema changes" |
| Database | PostgreSQL 16 | Robust, relational; straightforward to back up |
| Auth | Custom JWT (`jsonwebtoken` crate) | No external service needed; name + PIN is the full auth model |
| Image Compression | `image` crate + `oxipng` | Lossless PNG compression; JPEG preview generation |
@@ -1206,7 +1206,7 @@ of the media volume alone silently loses every generated keepsake.
|-------|---------|
| `axum` | Web framework |
| `tokio` | Async runtime |
| `sqlx` | Async PostgreSQL driver; compile-time query checking; prepared statements; migrations |
| `sqlx` | Async PostgreSQL driver; prepared statements; migrations embedded at compile time. Queries are runtime-checked (`sqlx::query()`), **not** macro-checked |
| `jsonwebtoken` | JWT sign / verify |
| `bcrypt` | PIN + admin password hashing |
| `uuid` | UUID v7 (time-sortable) |