feat(infra): finish PgBouncer — SCRAM auth, health-gated cutover, measurement window - #80
Conversation
Task 1 of the pgbouncer plan. Audit finds only 3 direct-required consumers: the two LISTEN clients (already direct) and the migration scripts (DDL in one long transaction + CREATE INDEX CONCURRENTLY — fixed to prefer DATABASE_URL_DIRECT). All SET LOCAL uses are inside BEGIN/COMMIT so they are transaction-scoped and poolable. No advisory locks anywhere. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ytTRmZoRpCSnhKA3xJURj
…erlist, verified install Task 2 of the pgbouncer plan. PgBouncer was never installed (the earlier cutover pointed DATABASE_URL at a port nothing listened on). Two correctness fixes found before install: (1) auth_type was md5 but every role password is stored SCRAM-SHA-256 — an md5 userlist authenticates clients then fails every SERVER connection; switched to scram-sha-256 with plaintext userlist (0600, now gitignored). (2) the generator only emitted 'postgres'; the least-privilege roles gen-env writes role env files for would all fail — now emitted too. setup-pgbouncer.sh installs the binary, disables the conflicting distro unit, generates the userlist, starts our unit, and PROVES a real query + admin console work through :6432 — all while the app stays on :5432 direct. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ytTRmZoRpCSnhKA3xJURj
…XEC) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ytTRmZoRpCSnhKA3xJURj
…efore restart Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ytTRmZoRpCSnhKA3xJURj
Task 3. gen-env writes DATABASE_URL=:6432 only when USE_PGBOUNCER=1 AND a live SELECT 1 through :6432 succeeds; otherwise it falls back to direct :5432 with a loud warning. Worst case becomes 'no pooling', never 'no database' (the 2026-07-24 outage). DATABASE_URL_DIRECT stays :5432 for LISTEN clients and migrations. Smoke gate asserts the pooler only when the cutover is actually on. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ytTRmZoRpCSnhKA3xJURj
…opened 2026-07-24) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ytTRmZoRpCSnhKA3xJURj
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (10)
📝 WalkthroughWalkthroughPgBouncer now uses SCRAM authentication, runs as a dedicated PostgreSQL service, and is provisioned with role credentials. Environment generation supports fail-safe pooled cutover while preserving direct connections for migrations. Deployment checks and operational documentation are updated accordingly. ChangesPgBouncer authentication and provisioning
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant gen-env.sh
participant oper-pgbouncer
participant Application
participant MigrationScripts
participant PostgreSQL
gen-env.sh->>oper-pgbouncer: verify SELECT 1 on :6432
gen-env.sh->>Application: emit DATABASE_URL on :6432
Application->>oper-pgbouncer: use pooled DATABASE_URL
oper-pgbouncer->>PostgreSQL: forward pooled query
MigrationScripts->>PostgreSQL: use DATABASE_URL_DIRECT on :5432
Possibly related PRs
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Executes plan
2026-07-24-pgbouncer-and-index-audit.mdTasks 1–4 (Task 5 is gated on a ≥7-day window — opened today).Task 1 — session-dependence audit. Only 3 consumers require a direct connection: the two LISTEN clients (already direct) and the migration scripts (one long DDL transaction +
CREATE INDEX CONCURRENTLY— fixed to preferDATABASE_URL_DIRECT). All fourSET LOCAL statement_timeoutuses are insideBEGIN/COMMIT, so they're transaction-scoped and poolable. No advisory locks anywhere.Task 2 — PgBouncer actually up. It was never installed (which is why the earlier cutover pointed at a dead port). Found and fixed three blockers before it would run:
auth_type = md5but every role password is stored SCRAM-SHA-256 → an md5 userlist authenticates clients then fails every server connection. Switched toscram-sha-256+ plaintext userlist (0600, postgres-owned, now gitignored).postgres; the least-privilege roles in the role env files would all have failed → now emits all 6./usr/bin/pgbouncer(Debian ships/usr/sbin, →203/EXEC) andUser=root(PgBouncer refuses to run as root) → fixed, plusreset-failedto clear crash-loop rate limits.Task 3 — health-gated cutover.
gen-env.shwrites:6432only whenUSE_PGBOUNCER=1AND a liveSELECT 1through the pooler succeeds; otherwise direct:5432with a warning. Proven: with the pooler stopped and the flag on, it fell back — the exact 2026-07-24 condition is now harmless. Smoke gate additionally fails ifDATABASE_URLsays:6432while the pooler is down.Task 4 — measurement window opened.
oper-pg-stat.timerenabled + first snapshot captured (182 index rows, 50 statement rows). Index drops are gated until ≥2 weekly snapshots (~2026-07-31), documented in the runbook.Live on prod: cutover ON, health
db:up,SHOW POOLSshows real multiplexing (oper_worker 3 clients → 1 server conn), LISTEN clients still direct.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Security
Documentation