Created Started Finished
Let a postgresql suite have a real read replica ## Why An application that offloads reads onto a read replica is an application whose reads can be older than its writes, and nothing here could say so. The server this library starts has no replica, so a read that must not be answered by one looks exactly like a read that may be, and the difference only shows up in production. That is not hypothetical: it is where this came from. A cache pointed its read-only pool at an Aurora reader endpoint and started answering 404 for content it had just accepted, because a read that decided a write was being answered by a replica that had not caught up. Every test passed, because in the tests that pool was the primary. ## What A real standby, rather than a stand-in for one. `postgresqlStandbySetupFunc` runs `pg_basebackup` into a directory of its own, creates a replication slot, and starts the copy in hot standby, torn down with the suite. It replays the same write-ahead log a production replica does, in the same order, and refuses writes because postgres refuses them, not because a test says so. `recovery_min_apply_delay` is what makes being behind a fact rather than a race: without it a local standby follows fast enough that whether a read sees a just-committed write depends on the machine. `replicatedPoolsSetupFunc` gives a test the pair such an application is configured with — a fresh database, a pool to it on the primary, and a pool to the same database on the standby. Waiting for the database to appear on the standby is part of it, because until its creation is replayed there is no database there to connect to. ## What it needed from the rest of the library `tmp-postgres`' `defaultConfig` asks for `wal_level = minimal`, which writes too little to the log for anything to replay, and at which postgres refuses to start with a nonzero `max_wal_senders`. So the server configuration becomes the caller's to choose, through `postgresqlServerSetupFuncWith` and `persistPostgresqlAdminSpecWith`, with `replicationPrimaryConfig` for the case this is all for. The existing entry points are unchanged and defined in terms of the new ones. `testDatabaseSetupFunc` exposes the connection options alongside the pool, because a second connection to the same database — which is what the standby pool is — needs to know which database that is. ## One thing deliberately missing How far behind the standby is belongs to the cluster, not to a database. Tests sharing a server share their standby's lag, so a test cannot pause replay or change the delay for itself alone without stalling every other test's setup. The one control that is safe to hand a single test is waiting, so `awaitStandbyCaughtUp` is the only one there is. ## Tests `StandbySpec` asserts the three things that make it a replica rather than a copy: a write is not visible while it is behind, it is visible once caught up, and a write *through* the standby is refused by postgres (SQLSTATE 25006). The second of those caught a bug in the first draft of `awaitStandbyCaughtUp`: it waited on `pg_current_wal_lsn()`, the write position. Under `synchronous_commit = off`, which is how a server meant for tests is usually run, a commit is acknowledged before its log record has been written out, so that position can be behind a transaction that has already committed, and the wait was waiting for something that had already happened. `pg_current_wal_insert_lsn()` counts the records still in the buffers. It passed at half a second of lag, where the gap is too small to land in, and failed at twenty milliseconds.