The app died with "max clients reached in session mode - pool_size: 15".
Two causes, both real, neither visible without a live database.
The connection string pointed at the pooler's session mode, which pins one
backend per client and caps at 15 on Supabase. Every query here already runs
inside a transaction and the session context is set transaction-locally, so
transaction mode is not a workaround but the mode this design was written
for. Verified: 20 concurrent transactions, all 852 rows, 0.4s — and still
nothing without a session context.
The second cause was the dev server. Next.js re-evaluates changed modules,
so a module-local `let` was empty afterwards while the previous pool stayed
alive holding its connections. An afternoon of editing exhausted the quota.
The pool now hangs off globalThis, which is inert in production where
nothing reloads.
Documented in .env.example and DEPLOYMENT.md, because a deployment that
picks port 5432 fails this way under load and not before.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>