diff --git a/apps/realtime/src/database/operations.ts b/apps/realtime/src/database/operations.ts index 9e2b4d1ddb..2a0bf28d91 100644 --- a/apps/realtime/src/database/operations.ts +++ b/apps/realtime/src/database/operations.ts @@ -23,6 +23,10 @@ import { env } from '@/env' const logger = createLogger('SocketDatabase') const connectionString = env.DATABASE_URL +/** + * Server-side safety net for runaway queries and abandoned transactions. + * See `packages/db/index.ts` for rationale. + */ const socketDb = drizzle( postgres(connectionString, { prepare: false, @@ -30,6 +34,9 @@ const socketDb = drizzle( connect_timeout: 20, max: 30, onnotice: () => {}, + connection: { + options: '-c statement_timeout=90000 -c idle_in_transaction_session_timeout=90000', + }, }), { schema } ) diff --git a/packages/db/index.ts b/packages/db/index.ts index 186b076aba..bc77b234a6 100644 --- a/packages/db/index.ts +++ b/packages/db/index.ts @@ -10,12 +10,27 @@ if (!connectionString) { throw new Error('Missing DATABASE_URL environment variable') } +/** + * Server-side safety net for runaway queries and abandoned transactions: + * - `statement_timeout=90000` kills any single statement still running + * after 90s. Protects against pathological queries. + * - `idle_in_transaction_session_timeout=90000` kills a session that has + * opened a transaction and gone idle for 90s. Protects against + * transactions that hold row locks while waiting on external I/O. + * + * These are last-resort caps — application code should never approach + * them. Migrations or admin scripts that legitimately need longer limits + * must construct their own client with overrides. + */ const postgresClient = postgres(connectionString, { prepare: false, idle_timeout: 20, connect_timeout: 30, max: 30, onnotice: () => {}, + connection: { + options: '-c statement_timeout=90000 -c idle_in_transaction_session_timeout=90000', + }, }) export const db = drizzle(postgresClient, { schema })