Replace Postgres with SQLite
Ten members and a handful of songs a week never needed a database server,
and the server was the last thing making this a two-container deployment.
modernc.org/sqlite is pure Go, so CGO_ENABLED=0 survives and the dependency
count is unchanged: pgx out, sqlite in.
The port stayed small because the driver matches $1-style placeholders
against argument ordinals exactly as pgx does, so no query needed rewriting
for parameters. What did change:
- timestamptz becomes timestamp holding UTC 'YYYY-MM-DD HH:MM:SS'. The
declared type is what makes the driver return time.Time, and the
fixed-width UTC string is what makes ordering and comparison against
datetime('now') mean what they say.
- interval has no equivalent: sessions.idle_ttl is seconds, and the review
edit window travels as a SQLite date modifier string.
- No stddev_pop, so the divisive and unified boards spell the population
formula out, guarded with max(0.0, ...) because cancellation returns a
tiny negative when every score is identical.
- foreign_keys is off by default, so the cascades only exist because the
pragma is set on every connection.
Drops the postgres service, its healthcheck, the depends_on gate, the
startup retry loop and POSTGRES_PASSWORD. ./storage is now the whole
backup. Tests get a fresh database file per test and run everywhere
instead of skipping without TEST_DATABASE_URL.
This commit is contained in:
@@ -209,3 +209,30 @@ says so.
|
||||
|
||||
The coverage assumption that shaped the earlier sketch was wrong and is corrected in `later.md`:
|
||||
LRCLIB has synced lyrics for a good share of Finnish rock, not almost none.
|
||||
46. **SQLite instead of Postgres — this reverses entries 1 and 3** (2026-08-02). Ten members and a
|
||||
handful of songs a week never needed a database server, and the server was the last thing making
|
||||
this a two-container deployment. `modernc.org/sqlite` is pure Go, so `CGO_ENABLED=0` survives and
|
||||
the dependency count does not change: `pgx` out, `sqlite` in. What it buys: one container, one
|
||||
bind mount that is the entire backup, no `pgdata`, no healthcheck-gated `depends_on`, no startup
|
||||
retry loop, and tests that run anywhere instead of skipping without `TEST_DATABASE_URL`.
|
||||
|
||||
The port was smaller than expected, because the driver matches `$1`-style placeholders against
|
||||
argument ordinals exactly as pgx does — so no query needed rewriting for parameters. What did
|
||||
change:
|
||||
- **`timestamptz` → `timestamp` holding UTC `YYYY-MM-DD HH:MM:SS`.** The declared type is what
|
||||
makes the driver return `time.Time`; the fixed-width UTC string is what makes `order by
|
||||
created_at` and `expires_at > datetime('now')` mean what they say. `_time_format=datetime` and
|
||||
`_timezone=UTC` on the DSN make Go write exactly the shape `datetime('now')` produces, so the
|
||||
two sources of a timestamp are comparable.
|
||||
- **`interval` has no equivalent.** `sessions.idle_ttl` is seconds as an integer, and the review
|
||||
edit window travels as a SQLite date modifier string (`-1800 seconds`).
|
||||
- **No `stddev_pop`.** The divisive and unified boards use the population formula written out,
|
||||
guarded with `max(0.0, …)` because floating-point cancellation returns a tiny negative when
|
||||
every score is identical, and `sqrt` of that is null.
|
||||
- **`foreign_keys` is off by default in SQLite**, so every `on delete cascade` in the schema is
|
||||
decoration without the pragma. It is set on the DSN alongside WAL, `busy_timeout` and
|
||||
`_txlock=immediate`.
|
||||
|
||||
Taken while there was still no data: the tables were recreated rather than converted, same as
|
||||
entry 45. What would reverse this: enough concurrent writers that one writer is a real limit, or
|
||||
wanting the database on a different box from the audio files.
|
||||
|
||||
Reference in New Issue
Block a user