Database Disk Full
PostgreSQL 18 persists users, trip configurations and stages (JSONB) per ADR-022. Since the PG split (ADR-060) the app database (PG-app, service database of the prod stack) is separate from the shared PG-reference (pg-reference project). Both share the VM boot volume (Oracle Always Free: 200 GB of block storage in total) with Docker images, the Valhalla tiles and every preview stack, so disk pressure is the most likely "silent killer".
In production, run the commands below with the dc alias from README.md instead of docker compose.
Symptoms
- PHP container logs:
SQLSTATE[53100](disk full) orcould not extend file /api/healthanswers 503 withdeps.postgres.status = "down"(deps.postgres_referencefor PG-reference, which does not fail readiness)- Writes fail (POST
/tripsreturns 5xx) but reads still succeed for a few minutes df -h /over SSH shows the boot volume above 90 % (no host monitoring is deployed in beta)
Diagnosis
Check VM disk usage first:
df -h /
docker system df
Open a psql session on PG-app and measure (the container knows its own user and database):
docker compose exec database sh -c 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
SELECT pg_size_pretty(pg_database_size(current_database())) AS db_size;
SELECT relname,
pg_size_pretty(pg_total_relation_size(c.oid)) AS total,
pg_size_pretty(pg_relation_size(c.oid)) AS table,
pg_size_pretty(pg_total_relation_size(c.oid) - pg_relation_size(c.oid)) AS indexes_toast
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname = 'public' AND c.relkind = 'r'
ORDER BY pg_total_relation_size(c.oid) DESC
LIMIT 10;
SELECT relname, n_dead_tup, n_live_tup
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC
LIMIT 10;
Check for stuck long-running queries:
SELECT pid, now() - query_start AS duration, state, query
FROM pg_stat_activity
WHERE state <> 'idle'
ORDER BY duration DESC NULLS LAST
LIMIT 10;
Procedure
-
Free obvious wins on disk (run on the VM, not in the container):
docker image prune -af # unused images; a rollback re-pulls them from GHCR docker container prune -f sudo journalctl --vacuum-size=200M docker compose ls # stale pr-<n> preview stacks each hold a PG-app volumeClose stale PRs to reclaim their preview stacks:
teardown-previewindeploy.ymlremoves the stack and its volumes when a PR is closed. -
Reclaim PostgreSQL bloat on the top offender(s). Prefer non-blocking first:
VACUUM (VERBOSE, ANALYZE) stage; VACUUM (VERBOSE, ANALYZE) trip_request;If bloat persists and a brief lock is acceptable (writes blocked on that table):
VACUUM FULL VERBOSE stage;The Messenger transport is Redis-backed (
redis://.../messages), not Doctrine, so there is nomessenger_messagestable to truncate here — queue pressure is handled inredis-out-of-memory.md. -
Resize the boot volume on Oracle Cloud (last resort, requires VM reboot):
- OCI console → Compute → Instances → select VM → Boot volume → "Edit" → raise size (free tier ceiling: 200 GB total block storage across all volumes)
- Reboot, then check
df -h /: the Ubuntu image grows the root partition at boot (cloud-initgrowpart). If it did not, grow it by hand (lsblkto find the device, thensudo growpart <disk> <partition>andsudo resize2fs <root device>) - After reboot, the stack comes back up via the Docker
restart: unless-stoppedpolicy; if not, rundc up -d(thebtp-composealias, see README.md)
Verification and follow-up
- Re-check
pg_database_size(current_database())anddf -h /. - Confirm
/api/healthreportsdeps.postgres.status = "ok". - If
VACUUM FULLwas used, capture downtime in the incident issue. - Schedule a follow-up: enable autovacuum tuning (
autovacuum_vacuum_scale_factor) or add an archival job for old trip computations. - If Oracle volume was resized, update the infrastructure note in ADR-019.
References
- ADR-019 — Deployment infrastructure (Oracle Always Free volume limits)
- ADR-022 — Persistent storage strategy (PostgreSQL JSONB)