fix(P1): postgres-js pool reliability — F8

During the audit the dev server twice entered a stuck state where every
query 500'd with `write CONNECT_TIMEOUT` while the DB was healthy (1/100
connections used, queryable from psql immediately). The Docker bridge can
silently drop TCP sockets and postgres-js holds the stale handles until
max_lifetime expires.

- connect_timeout: 10 → 5  (fail fast)
- max_lifetime: 30min → 10min  (recycle before staleness accumulates)
- onnotice: surface NOTICE/WARNING for visibility

Reduces the window of stuck state. Full recovery still requires a
restart if the pool hard-fails. pgbouncer in production is the proper
long-term answer; this is the safe one-file change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-14 22:40:24 +02:00
parent e469b2b6a6
commit 2c57082d8d

View File

@@ -29,11 +29,31 @@ const connectionString = process.env.DATABASE_URL!;
// during clients-page fanout without log-storm.
const POOL_MAX = process.env.NODE_ENV === 'development' ? 30 : 20;
// Pool reliability hardening (post-audit F8):
// During the audit the dev server twice entered a stuck state where every
// query 500'd with `write CONNECT_TIMEOUT` while the DB was healthy
// (1 of 100 connections used, queryable from psql immediately).
// The Docker bridge can silently drop TCP sockets and postgres-js's pool
// holds onto the stale handles until max_lifetime expires.
// - connect_timeout: 5s so failures surface fast instead of stalling
// requests for 10s before erroring.
// - max_lifetime: 10min so connections recycle before stale sockets
// accumulate. Was 30min — too long for the Docker socket-drop pattern.
// - onnotice: surfaces postgres NOTICE/WARNING messages that we'd
// otherwise miss (extension warnings, deprecation hints).
const queryClient = postgres(connectionString, {
max: POOL_MAX,
idle_timeout: 20,
connect_timeout: 10,
max_lifetime: 60 * 30,
connect_timeout: 5,
max_lifetime: 60 * 10,
onnotice: (notice) => {
// postgres-js types `notice` as `unknown`; the runtime shape is
// { severity, code, message, ... }. Only surface WARNING+.
const n = notice as { severity?: string; message?: string };
if (n.severity && n.severity !== 'NOTICE') {
console.warn(`[postgres ${n.severity}] ${n.message ?? ''}`);
}
},
connection: {
// ms values per postgres.js types; these become Postgres GUC settings
// applied at session start.