Documented the database connection pooling in the Website service.

This commit is contained in:
2026-08-11 01:35:43 +02:00
parent 19fa9d8d8c
commit 8303598244
3 changed files with 13 additions and 4 deletions
+6 -1
View File
@@ -49,10 +49,15 @@ DATABASE_NAME=loud
# Provide the real password via the environment or a secret — never commit it.
DATABASE_PASSWORD=loud
# Maximum pooled connections per event loop, one loop per core — an 8-core host
# can open 8 times this, and every replica that many again.
DATABASE_POOL_MAX_PER_EVENT_LOOP=4
# Port of the database to connect to.
DATABASE_PORT=5432
# TLS posture when connecting: off | prefer | require (use `require` in production).
# TLS posture when connecting: off | prefer | require. Plaintext is the usual
# choice over a private container network; use `require` across one you share.
DATABASE_TLS=off
# Username of the database to connect as.
+6 -3
View File
@@ -63,7 +63,7 @@ Configuration is read through [swift-configuration](https://github.com/apple/swi
5. Built-in defaults
The two files play different roles:
- **`.env`** (git-ignored) holds your deployment values, and is the file the Makefile and Compose read for their `${VAR}` placeholders it typically selects the PostgreSQL backend.
- **`.env`** (git-ignored) holds your deployment values — including the database password — and is the file the Makefile and Compose read for their `${VAR}` placeholders; it typically selects the PostgreSQL backend. Keep it out of version control and off shared machines: Compose passes its values to the container as environment variables, so anything in it is readable through `docker inspect` and by every process in the container.
- **`.env.local`** (tracked) holds the local development overrides: in-memory database, `debug` logging. Sitting *above* `.env`, it keeps a direct launch (`swift run` or a debugger) on the local values even when `.env` points at a deployment. Compose never reads it, and the production image does not ship it.
The Makefile `include`s `.env` and exports every value, so a target launched through `make` runs with the deployment configuration rather than the `.env.local` one: `make site-run` uses the backend `.env` selects, a bare `swift run Website` the in-memory one. And because a makefile assignment outranks an inherited environment variable, overriding a value for a single invocation takes a command-line variable *after* the target (`make site-mount DATABASE_DRIVER=postgres`) — an environment prefix is silently discarded.
@@ -112,6 +112,8 @@ A dotted config key maps to an environment variable by upper-casing, splitting c
| `database.tls` | `DATABASE_TLS` | `prefer` | TLS posture when connecting: `off`, `prefer`, or `require`. Ignored for `inMemory`. |
| `database.pool.maxPerEventLoop` | `DATABASE_POOL_MAX_PER_EVENT_LOOP` | `4` | Maximum pooled connections per event loop. Ignored for `inMemory`. |
> **Connection budget:** the pool holds `database.pool.maxPerEventLoop` connections *per event loop*, and the event loop group runs one loop per core. An 8-core instance can therefore open 32, and each replica that many again — three replicas exhaust PostgreSQL's default `max_connections` of 100. Size this against the server's limit, not against the number alone.
See [Persistence](#persistence-1) below for the workflow.
### Paths
@@ -169,7 +171,7 @@ make site-run # run locally with hot reload (hb watch)
make site-mount # docker compose up --build --detach
make site-unmount # docker compose down + remove the local image
```
Unlike a direct `swift run`, these targets inherit the exported `.env` values (see [Configuration](#configuration)), so they run against whichever backend `.env` selects. `make help` lists every available target.
Unlike a direct `swift run`, these targets inherit the exported `.env` values (see [Configuration](#configuration)), so they run against whichever backend `.env` selects. The one exception is `DATABASE_HOST` under `make site-mount`: the local Compose override pins it to the `postgres` service name, since the `.env` value addresses the database from the host rather than from inside the container. `make help` lists every available target.
## Persistence
The service persists data through Fluent and selects its backend at runtime with `database.driver`.
@@ -287,8 +289,9 @@ The Makefile and Compose files read these from `.env` (or the environment). Prov
| `HTTP_SERVER_NAME` | Runtime server name (default `LoudWebsite`). |
| `SECURITY_STRICT_TRANSPORT_SECURITY` | HSTS header value (default `max-age=31536000; includeSubDomains`). |
| `DATABASE_DRIVER` | `inMemory` or `postgres`. The production Compose file defaults it to `postgres`; the local override defaults back to the in-memory backend. |
| `DATABASE_HOST`, `DATABASE_PASSWORD` | **Mandatory** — the Compose files carry no default for either, since none can be correct: `localhost` inside the container is the container itself, and a blank password authenticates as nobody. Compose refuses to start without them rather than booting a website that serves 503s. Provide the password via a secret. |
| `DATABASE_HOST`, `DATABASE_PASSWORD` | **Mandatory** — the production Compose file carries no default for either, since none can be correct: `localhost` inside the container is the container itself, and a blank password authenticates as nobody. It refuses to start without them rather than booting a website that serves 503s. Both come from `.env`, which is git-ignored — never commit the password. Compose interpolates each file before merging, so both must be set for a local `docker compose up` too, even though the override pins the host. |
| `DATABASE_PORT`, `DATABASE_NAME`, `DATABASE_USERNAME` | The rest of the PostgreSQL connection (when `DATABASE_DRIVER=postgres`); these do default (`5432`, `loud`, `loud`). |
| `DATABASE_POOL_MAX_PER_EVENT_LOOP` | Pooled connections per event loop (default `4`) — see the [connection budget](#persistence) before scaling out. |
| `DATABASE_TLS` | TLS posture: `off`, `prefer`, or `require`. The production Compose file defaults it to `require`, which refuses a server offering no TLS; the local override defaults it to `off` for the plaintext development container. `prefer` continues in plaintext when the upgrade is stripped, handing over the password — so it is not a safe production posture. |
Run the migrations against the production database once before (or during) rollout: `docker compose -f docker-compose.yml run --rm website --database-migrate`.
+1
View File
@@ -27,6 +27,7 @@ services:
DATABASE_USERNAME: ${DATABASE_USERNAME:-loud}
DATABASE_PASSWORD: ${DATABASE_PASSWORD:?DATABASE_PASSWORD is required}
DATABASE_TLS: ${DATABASE_TLS:-require}
DATABASE_POOL_MAX_PER_EVENT_LOOP: ${DATABASE_POOL_MAX_PER_EVENT_LOOP:-4}
healthcheck:
test: ["CMD", "curl", "--fail", "--silent", "--show-error", "http://127.0.0.1:8080/health"]
interval: 30s