import { Pool, PoolConfig } from "pg"; import logger from "@server/logger"; export function createPoolConfig( connectionString: string, maxConnections: number, idleTimeoutMs: number, connectionTimeoutMs: number ): PoolConfig { return { connectionString, max: maxConnections, idleTimeoutMillis: idleTimeoutMs, connectionTimeoutMillis: connectionTimeoutMs, // TCP keepalive to prevent silent connection drops by NAT gateways, // load balancers, and other intermediate network devices (e.g. AWS // NAT Gateway drops idle TCP connections after ~350s) keepAlive: true, keepAliveInitialDelayMillis: 10000, // send first keepalive after 10s of idle // Allow connections to be released and recreated more aggressively // to avoid stale connections building up allowExitOnIdle: false }; } export function attachPoolErrorHandlers(pool: Pool, label: string): void { pool.on("error", (err) => { // This catches errors on idle clients in the pool. Without this // handler an unexpected disconnect would crash the process. logger.error( `Unexpected error on idle ${label} database client: ${err.message}` ); }); pool.on("connect", (client) => { // Set a statement timeout on every new connection so a single slow // query can't block the pool forever client.query("SET statement_timeout = '30s'").catch((err: Error) => { logger.warn( `Failed to set statement_timeout on ${label} client: ${err.message}` ); }); }); } export function createPool( connectionString: string, maxConnections: number, idleTimeoutMs: number, connectionTimeoutMs: number, label: string ): Pool { const pool = new Pool( createPoolConfig( connectionString, maxConnections, idleTimeoutMs, connectionTimeoutMs ) ); attachPoolErrorHandlers(pool, label); return pool; }