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:
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
PORT=3000
|
PORT=3000
|
||||||
|
|
||||||
# Postgres, routed through pgCat/PgBouncer
|
# Postgres, directly - no pooler for the MVP (see src/db/index.ts)
|
||||||
DATABASE_URL=postgres://continuum:continuum@localhost:6432/continuum
|
DATABASE_URL=postgres://continuum:continuum@localhost:5432/continuum
|
||||||
DATABASE_POOL_MAX=10
|
DATABASE_POOL_MAX=10
|
||||||
|
|
||||||
# Valkey (state + streams), NOT Redis - Redis 7.4+ is RSALv2/SSPL and 8.0+
|
# Valkey (state + streams), NOT Redis - Redis 7.4+ is RSALv2/SSPL and 8.0+
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
# continuum-backend
|
# continuum-backend
|
||||||
|
|
||||||
Cloud control plane for Continuum. Bun + ElysiaJS, PostgreSQL (via pgCat/PgBouncer,
|
Cloud control plane for Continuum. Bun + ElysiaJS, PostgreSQL (with pgvector for frame embeddings), Valkey Streams, and Cloudflare R2 storage.
|
||||||
with pgvector for frame embeddings), Valkey Streams, and Cloudflare R2 storage.
|
|
||||||
|
|
||||||
## Run
|
## Run
|
||||||
|
|
||||||
@@ -48,7 +47,7 @@ backend and records what a gateway sends, for refreshing the fixtures in
|
|||||||
## Layout
|
## Layout
|
||||||
|
|
||||||
- `src/db/` - Drizzle schema (`printers`, `farms`, `print_jobs`, `frame_embeddings`)
|
- `src/db/` - Drizzle schema (`printers`, `farms`, `print_jobs`, `frame_embeddings`)
|
||||||
and client, pointed at pgCat. `src/db/validators.ts` derives Elysia/TypeBox
|
and client, pointed straight at Postgres. `src/db/validators.ts` derives Elysia/TypeBox
|
||||||
request validators straight from the Drizzle tables via `drizzle-typebox`,
|
request validators straight from the Drizzle tables via `drizzle-typebox`,
|
||||||
so the Postgres schema and the API validators come from one definition
|
so the Postgres schema and the API validators come from one definition
|
||||||
instead of two hand-synced ones.
|
instead of two hand-synced ones.
|
||||||
|
|||||||
+2
-3
@@ -6,9 +6,8 @@
|
|||||||
# (`ioredis` here, the `redis` crate in continuum-ai-worker) are unchanged.
|
# (`ioredis` here, the `redis` crate in continuum-ai-worker) are unchanged.
|
||||||
# See continuum-common/docs/LICENSES.md.
|
# See continuum-common/docs/LICENSES.md.
|
||||||
#
|
#
|
||||||
# pgCat/PgBouncer is deliberately not here - DATABASE_URL in .env.example
|
# No pooler anywhere for the MVP: DATABASE_URL points straight at Postgres on
|
||||||
# points at :6432 for production, but talking straight to Postgres on :5432
|
# :5432 here and in .env.example. See src/db/index.ts for why not pgCat.
|
||||||
# is one less moving part locally.
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
valkey:
|
valkey:
|
||||||
|
|||||||
+3
-4
@@ -5,10 +5,9 @@ export default defineConfig({
|
|||||||
out: "./drizzle",
|
out: "./drizzle",
|
||||||
dialect: "postgresql",
|
dialect: "postgresql",
|
||||||
dbCredentials: {
|
dbCredentials: {
|
||||||
// Matches scripts/dev-env.sh's DEV_DATABASE_URL. It pointed at 6432 (a
|
// Matches scripts/dev-env.sh's DEV_DATABASE_URL and .env.example: straight
|
||||||
// pooler) where nothing listens in this environment, so a bare db:migrate
|
// at Postgres. Migrations must never go through a pooler - drizzle-kit
|
||||||
// failed confusingly. Migrations want Postgres directly in any case: see
|
// cannot pass `prepare: false`.
|
||||||
// the note in src/db/index.ts about parameterised queries through pgCat.
|
|
||||||
url: process.env.DATABASE_URL ?? "postgres://continuum:continuum@localhost:5432/continuum",
|
url: process.env.DATABASE_URL ?? "postgres://continuum:continuum@localhost:5432/continuum",
|
||||||
},
|
},
|
||||||
verbose: true,
|
verbose: true,
|
||||||
|
|||||||
+3
-4
@@ -35,10 +35,9 @@ cd "$REPO_ROOT"
|
|||||||
PG_VERSION=16
|
PG_VERSION=16
|
||||||
PG_PORT=5432
|
PG_PORT=5432
|
||||||
VALKEY_PORT=6379
|
VALKEY_PORT=6379
|
||||||
# Straight at Postgres, not pgCat: .env.example points at the pooler for
|
# Straight at Postgres: there is no pooler for the MVP. Do not point this at
|
||||||
# production, and neither backend here runs one. Keep it that way even where a
|
# pgCat even where one is reachable - parameterised queries from postgres-js
|
||||||
# pooler is reachable - parameterised queries from postgres-js hang through
|
# hang through 1.2.0. See src/db/index.ts.
|
||||||
# pgCat 1.2.0, and drizzle-kit cannot migrate through it. See src/db/index.ts.
|
|
||||||
DEV_DATABASE_URL="postgres://continuum:continuum@localhost:${PG_PORT}/continuum"
|
DEV_DATABASE_URL="postgres://continuum:continuum@localhost:${PG_PORT}/continuum"
|
||||||
DEV_VALKEY_URL="redis://localhost:${VALKEY_PORT}"
|
DEV_VALKEY_URL="redis://localhost:${VALKEY_PORT}"
|
||||||
|
|
||||||
|
|||||||
+9
-20
@@ -2,28 +2,17 @@ import { drizzle } from "drizzle-orm/postgres-js";
|
|||||||
import postgres from "postgres";
|
import postgres from "postgres";
|
||||||
import * as schema from "./schema";
|
import * as schema from "./schema";
|
||||||
|
|
||||||
// `prepare: false` because this is meant to sit behind a transaction-mode
|
// No pooler in front of this for the MVP: DATABASE_URL points straight at
|
||||||
// pooler, where a named statement prepared on one server connection is not
|
// Postgres, and postgres-js already pools connections in-process (`max`).
|
||||||
// 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.
|
|
||||||
//
|
//
|
||||||
// Measured 2026-10-20, and the reason to read this before assuming the
|
// `prepare: false` is kept deliberately so a transaction-mode pooler can be
|
||||||
// deployment works: against pgCat 1.2.0 this client cannot run at all.
|
// added later without touching this file. Which one matters - measured
|
||||||
// Through that pooler an unparameterised `select 1` succeeds, and so does
|
// 2026-10-20: through pgCat 1.2.0 any query with a bind parameter hangs
|
||||||
// `select count(*) from organisations`, but *any* query carrying a bind
|
// forever (every Drizzle query has one), while PgBouncer 1.22 in transaction
|
||||||
// parameter hangs forever - with `prepare: false`, with `prepare: true`, with
|
// mode runs the same queries and passes scripts/e2e-authz.sh. See PLAN.md.
|
||||||
// `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.
|
|
||||||
//
|
//
|
||||||
// So `prepare: false` is necessary but nowhere near sufficient, and running
|
// Migrations go straight at Postgres either way: drizzle.config.ts cannot
|
||||||
// behind pgCat does not work. PgBouncer 1.22 in transaction mode does: the
|
// pass `prepare: false`, and drizzle-kit hung at commit through pgCat.
|
||||||
// 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.
|
|
||||||
const connectionString = process.env.DATABASE_URL;
|
const connectionString = process.env.DATABASE_URL;
|
||||||
if (!connectionString) {
|
if (!connectionString) {
|
||||||
throw new Error("DATABASE_URL is not set");
|
throw new Error("DATABASE_URL is not set");
|
||||||
|
|||||||
Reference in New Issue
Block a user