fix(db): raise the default statement timeout to 60s

The 15s default cancelled the live-aggregate analytics fallback on a coverage
miss over a large account (Postgres 57014), surfacing as a 500. Sixty seconds
still bounds a runaway query well before it can starve the pool while leaving
headroom for the heaviest legitimate read.
This commit is contained in:
Marc-André Bombeck committed 2026-06-20 17:30:59 +02:00
1 parent 16759b4df8
commit 32296c901c
2 files changed
+12 -11

No files matched your search

+6 -6
View File
@@ -80,8 +80,8 @@ describe("getStatementTimeoutMs", () => {
}
});
it("defaults to 15000ms when the env var is unset", () => {
expect(getStatementTimeoutMs()).toBe(15_000);
it("defaults to 60000ms when the env var is unset", () => {
expect(getStatementTimeoutMs()).toBe(60_000);
});
it("honours a positive override", () => {
@@ -96,9 +96,9 @@ describe("getStatementTimeoutMs", () => {
it("falls back to the default on a malformed or negative value", () => {
process.env.DATABASE_STATEMENT_TIMEOUT_MS = "not-a-number";
expect(getStatementTimeoutMs()).toBe(15_000);
expect(getStatementTimeoutMs()).toBe(60_000);
process.env.DATABASE_STATEMENT_TIMEOUT_MS = "-5";
expect(getStatementTimeoutMs()).toBe(15_000);
expect(getStatementTimeoutMs()).toBe(60_000);
});
});
@@ -116,8 +116,8 @@ describe("buildSessionOptions", () => {
it("emits both session timeouts as libpq startup options by default", () => {
delete process.env.DATABASE_STATEMENT_TIMEOUT_MS;
const opts = buildSessionOptions();
expect(opts).toContain("-c statement_timeout=15000");
expect(opts).toContain("-c idle_in_transaction_session_timeout=15000");
expect(opts).toContain("-c statement_timeout=60000");
expect(opts).toContain("-c idle_in_transaction_session_timeout=60000");
});
it("returns undefined (no options) when the timeout is disabled", () => {
+6 -5
View File
@@ -48,10 +48,11 @@ export function getPoolMax(): number {
* pooled session inherits them.
*
* Env-overridable via `DATABASE_STATEMENT_TIMEOUT_MS`. Set to `0` to disable
* (legacy unbounded behaviour). Default 15 s — generous for the rollup-backed
* read paths while still bounding a runaway query well before it can starve the
* pool. Heavy admin one-shots (drain/backfill) run on the worker, not the web
* pool, and can dial their own timeout if needed.
* (legacy unbounded behaviour). Default 60 s — bounds a genuinely runaway query
* (the A-1 goal: never hold a pool slot forever) while leaving ample headroom
* for the heaviest legitimate read, the live-aggregate analytics fallback on a
* coverage miss over a large account, which can run well past 15 s. Heavy admin
* one-shots (drain/backfill) can still dial their own timeout if needed.
*/
export function getStatementTimeoutMs(): number {
const raw = process.env.DATABASE_STATEMENT_TIMEOUT_MS;
@@ -59,7 +60,7 @@ export function getStatementTimeoutMs(): number {
const parsed = Number.parseInt(raw, 10);
if (Number.isFinite(parsed) && parsed >= 0) return parsed;
}
return 15_000;
return 60_000;
}
/**