HARDEN: Postgres pool config + dependency-aware /ready#161
Conversation
Two production-readiness gaps: 1. The Postgres client is instantiated without options — postgres.js then defaults to max=10, idle_timeout=0 (never close idle connections), no statement_timeout. Under sustained load idle connections accumulate until the database refuses new ones, and a runaway query can pin a pool slot for hours. Set explicit max/idle_timeout/connect_timeout/max_lifetime, and bind `statement_timeout` on every new connection so the database itself kills queries past the wall-clock budget. All five values are env- tunable with conservative defaults. 2. /health pinged neither dependency, so an orchestrator (Render, k8s) would route traffic to a replica whose pool was saturated or whose Redis client had silently disconnected. Split: /health stays cheap (liveness — process answering HTTP), and a new /ready pings Postgres + Redis in parallel with a 2 s ceiling per probe, returning 503 on either failure. Orchestrators should route on /ready and reserve /health for restart decisions. Tests cover the happy path on both endpoints plus the underlying ping helpers against the live test database and Redis. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Free Run ID: 📒 Files selected for processing (5)
WalkthroughThis pull request introduces a readiness check system for the server. New configuration parameters control database pool sizing and connection timeouts ( Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Note 🎁 Summarized by CodeRabbit FreeYour organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting https://app.coderabbit.ai/login. Comment |
Summary
Two production-readiness gaps closed:
1. Postgres pool was running on defaults.
postgres()was instantiated with no options, so postgres.js's defaults ofmax=10,idle_timeout=0(never close idle connections), and nostatement_timeoutapplied. Under sustained load idle connections accumulated until the database refused new ones, and a runaway query could pin a pool slot for hours. This PR sets explicitmax/idle_timeout/connect_timeout/max_lifetime, and bindsstatement_timeouton every new connection so PostgreSQL itself kills runaway queries. All five values are env-tunable (DB_POOL_MAX,DB_IDLE_TIMEOUT_SEC,DB_CONNECT_TIMEOUT_SEC,DB_MAX_LIFETIME_SEC,DB_STATEMENT_TIMEOUT_MS) with conservative defaults.2. /health pinged neither dependency. An orchestrator (Render, k8s) would route traffic to a replica whose pool was saturated or whose Redis client had silently disconnected, because
/healthonly checks that the HTTP listener responds. Now split:/healthstays cheap (liveness — "process is up enough to answer HTTP"). Orchestrators should use this only for restart decisions./readypings Postgres + Redis in parallel with a 2 s ceiling per probe, returning 503 on either failure. Orchestrators should gate routing on this.Both
pingDatabase(apps/server/src/db/index.ts) andRedisBridge.pingclear their timeout timers on the fast path so a healthy probe doesn't leave a 2 s timer pinned to the event loop.No new external dependencies. Pure hardening.
Test plan
bun run typecheck(apps/server) — cleanbun run test(apps/server) — 222 pass, 0 fail (was 218; +4 from newhealth.test.ts)curl /health→ 200{status: "ok"}regardless of dep state.curl /readyagainst healthy stack → 200{status: "ready", database: true, redis: true}.curl /ready→ 503{status: "degraded", database: false, redis: true}within ~2 s.DB_STATEMENT_TIMEOUT_MS=100, run a slow query through any handler, confirm Postgres returnscanceling statement due to statement timeoutinstead of pinning the pool.🤖 Generated with Claude Code
Note
Medium Risk
Changes database connection pool and query timeout behavior and adds a new readiness gate, which could affect production traffic routing or cause long-running queries to fail if tuned too aggressively.
Overview
Adds a new readiness endpoint (
GET /ready) that pings Postgres and Redis with short timeouts and returns503when either dependency is unhealthy, while keeping/healthas a cheap liveness check.Hardens Postgres usage by making pool sizing/timeouts and
statement_timeoutconfigurable via new env vars, and introduces boundedpingDatabase/RedisBridge.pinghelpers plus tests covering/health,/ready, and the ping functions.Reviewed by Cursor Bugbot for commit 0ea0ee9. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by CodeRabbit
Release Notes
New Features
Tests