Postgres Connection Pooling Pattern
Overview
Section titled “Overview”All services that talk to PostgreSQL must use a shared connection pool instead of opening a new raw pg client per request or per Cloud Run instance. This prevents exhausting the database’s max connections and aligns with the pattern used by apps/webapp.
Current Implementation in apps/webapp
Section titled “Current Implementation in apps/webapp”apps/webapp establishes a pool using the pg library (or the Neon serverless driver) encapsulated in packages/db/src/client.ts:
import { Pool } from 'pg';
const pool = new Pool({ connectionString: process.env.DATABASE_URL, // Neon serverless pools automatically reuse connections. // For CloudSQL we set: max: 10, idleTimeoutMillis: 30000});
export const query = (text: string, params?: any[]) => pool.query(text, params);The pool is a singleton imported wherever a query is needed. The configuration is driven by environment variables, allowing the same code to run on Neon (serverless) or Cloud SQL (PgBouncer).
Recommended Pattern for apps/ops-mcp
Section titled “Recommended Pattern for apps/ops-mcp”- Create a shared pool module similar to
packages/db/src/client.ts. - Do not instantiate a pool inside request handlers – import the singleton.
- Configure via
DATABASE_URL. When running on Neon, the URL includes the serverless endpoint; for Cloud SQL use the PgBouncer host. - Use
pool.connect()only for transactions and release the client promptly withfinally { client.release(); }. - Set sensible limits in production Cloud SQL deployments (e.g.,
max: 5per instance) to stay well below the instance’s total connection quota.
Example Usage in apps/ops-mcp
Section titled “Example Usage in apps/ops-mcp”import { Pool } from 'pg';
const pool = new Pool({ connectionString: process.env.DATABASE_URL, // Adjust for CloudSQL if needed max: Number(process.env.PG_POOL_MAX) || 5, idleTimeoutMillis: 30000,});
export const query = (sql: string, params?: any[]) => pool.query(sql, params);import { query } from './db';
export const getUser = async (userId: string) => { const { rows } = await query('SELECT * FROM users WHERE id = $1', [userId]); return rows[0];};Enforcement
Section titled “Enforcement”- Static analysis: search for
new Pool(ornew Client(inapps/ops-mcp. Ensure only the shared module is used. - CodeRabbit rule:
SQL identifier interpolation– verify that dynamic identifiers are validated.
References
Section titled “References”apps/webapp/src/lib/api/base-handler.ts– shows how the shared pool is used for request‑level DB access.packages/db/src/client.ts– the canonical pool implementation.