Drop the pooler from the MVP: point everything straight at Postgres
.env.example and the README still told people to use pgCat on :6432, the exact setup that hangs on every parameterised query. With no prod and one backend instance there is nothing for a pooler to multiplex, and postgres-js already pools in-process, so DATABASE_URL now points at Postgres on :5432 in .env.example, dev-env.sh and drizzle.config.ts. prepare:false is kept in src/db/index.ts on purpose, so adding PgBouncer later (tested working) is a config change rather than a code change. The long measurement narrative in that file's comment is cut to the decision and the pointer to PLAN.md, where the numbers now live.
This commit is contained in:
+9
-20
@@ -2,28 +2,17 @@ import { drizzle } from "drizzle-orm/postgres-js";
|
||||
import postgres from "postgres";
|
||||
import * as schema from "./schema";
|
||||
|
||||
// `prepare: false` because this is meant to sit behind a transaction-mode
|
||||
// pooler, where a named statement prepared on one server connection is not
|
||||
// there on the next one. `max` is low for the same reason: the pooler
|
||||
// multiplexes, so a high per-instance count buys nothing and starves peers.
|
||||
// No pooler in front of this for the MVP: DATABASE_URL points straight at
|
||||
// Postgres, and postgres-js already pools connections in-process (`max`).
|
||||
//
|
||||
// Measured 2026-10-20, and the reason to read this before assuming the
|
||||
// deployment works: against pgCat 1.2.0 this client cannot run at all.
|
||||
// Through that pooler an unparameterised `select 1` succeeds, and so does
|
||||
// `select count(*) from organisations`, but *any* query carrying a bind
|
||||
// parameter hangs forever - with `prepare: false`, with `prepare: true`, with
|
||||
// `fetch_types: false`, and inside an explicit transaction alike. The same
|
||||
// queries straight at Postgres are fine, and psql's `\bind` through the same
|
||||
// pool is fine, so it is specific to how postgres-js frames those messages,
|
||||
// not to extended protocol in general. Every Drizzle query binds parameters,
|
||||
// so there is no subset of this app that works there.
|
||||
// `prepare: false` is kept deliberately so a transaction-mode pooler can be
|
||||
// added later without touching this file. Which one matters - measured
|
||||
// 2026-10-20: through pgCat 1.2.0 any query with a bind parameter hangs
|
||||
// forever (every Drizzle query has one), while PgBouncer 1.22 in transaction
|
||||
// mode runs the same queries and passes scripts/e2e-authz.sh. See PLAN.md.
|
||||
//
|
||||
// So `prepare: false` is necessary but nowhere near sufficient, and running
|
||||
// behind pgCat does not work. PgBouncer 1.22 in transaction mode does: the
|
||||
// same queries pass, 40 concurrent ones multiplex over a pool of 2 server
|
||||
// connections, and scripts/e2e-authz.sh passes all 68 checks through it.
|
||||
// Migrations go straight at Postgres regardless - drizzle.config.ts has no way to pass
|
||||
// `prepare: false`, and drizzle-kit hung at commit for the same reason.
|
||||
// Migrations go straight at Postgres either way: drizzle.config.ts cannot
|
||||
// pass `prepare: false`, and drizzle-kit hung at commit through pgCat.
|
||||
const connectionString = process.env.DATABASE_URL;
|
||||
if (!connectionString) {
|
||||
throw new Error("DATABASE_URL is not set");
|
||||
|
||||
Reference in New Issue
Block a user