From 63a6ed7f8dd989a527d52c0b1d7d2f80acd0808f Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Thu, 21 May 2026 21:26:30 -0400 Subject: [PATCH] fix: Apply `statement_timeout` on request-handling processes (#12422) * fix: Apply Postgres statement_timeout on request-handling processes Sets `statement_timeout` to REQUEST_TIMEOUT on the Sequelize connection pool when the process handles HTTP requests (web/api/collaboration/ websockets/admin) and does not also run worker/cron. Prevents a single runaway query from saturating the shared Postgres instance and cascading into timeouts across all endpoints. Co-Authored-By: Claude Opus 4.7 * Drop dead `api` service check Co-Authored-By: Claude Opus 4.7 * Only apply statement_timeout in forked cluster workers Skips the timeout in the master process so startup migrations driven from `checkPendingMigrations` are not cancelled mid-execution. Co-Authored-By: Claude Opus 4.7 --------- Co-authored-by: Claude Opus 4.7 --- server/storage/database.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/server/storage/database.ts b/server/storage/database.ts index c8e335a98d..4925f12425 100644 --- a/server/storage/database.ts +++ b/server/storage/database.ts @@ -1,3 +1,4 @@ +import cluster from "node:cluster"; import path from "node:path"; import type { InferAttributes, InferCreationAttributes } from "sequelize"; import sequelizeStrictAttributes from "sequelize-strict-attributes"; @@ -45,6 +46,22 @@ const poolMin = env.DATABASE_CONNECTION_POOL_MIN ?? 0; const databaseConfig = env.DATABASE_CONNECTION_POOL_URL || getDatabaseConfig(); const schema = env.DATABASE_SCHEMA; +// Request-handling processes get a Postgres `statement_timeout` matching the +// HTTP request timeout, so a single slow query cannot hold a connection past +// the point at which its response could be delivered. Worker/cron processes +// are exempted because background jobs may legitimately run long queries. +// Only applied in forked cluster workers so that startup work driven from +// the master process (notably migrations) is not subject to the timeout. +const isApiProcess = + (env.SERVICES.includes("web") || + env.SERVICES.includes("collaboration") || + env.SERVICES.includes("websockets") || + env.SERVICES.includes("admin")) && + !env.SERVICES.includes("worker") && + !env.SERVICES.includes("cron"); +const statementTimeout = + isApiProcess && cluster.isWorker ? env.REQUEST_TIMEOUT : undefined; + export function createDatabaseInstance( databaseConfig: string | object, input: { @@ -68,6 +85,7 @@ export function createDatabaseInstance( logQueryParameters: env.isDevelopment, dialectOptions: { application_name: getConnectionName(), + statement_timeout: statementTimeout, ssl: env.isProduction && !isSSLDisabled ? {