feat(infra): add PgBouncer connection pooling for production PostgreSQL
Introduces PgBouncer as a connection pooler between the API service and PostgreSQL in docker-compose.prod.yml, reducing connection overhead and improving concurrency under production load. - Add PgBouncer service (edoburu/pgbouncer:1.23.1-p2) with transaction pool mode, max_client_conn=200, default_pool_size=20 - Route API DATABASE_URL through PgBouncer (port 6432), keep direct connection (DATABASE_URL_DIRECT) for Prisma migrations/introspection - Create infra/pgbouncer/ config: pgbouncer.ini, userlist template, and entrypoint script with runtime env-var substitution - Update prisma.config.ts to prefer DATABASE_URL_DIRECT for migrations - Add K6 load test (e2e/load/pgbouncer-pool-test.js) with ramp-up to 200 VUs, pool exhaustion detection, and p95 < 2s threshold - Add PgBouncer env vars to .env.example Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
31
infra/pgbouncer/entrypoint.sh
Executable file
31
infra/pgbouncer/entrypoint.sh
Executable file
@@ -0,0 +1,31 @@
|
||||
#!/bin/sh
|
||||
# =============================================================================
|
||||
# PgBouncer entrypoint — render userlist from environment variables and start.
|
||||
# =============================================================================
|
||||
set -eu
|
||||
|
||||
USERLIST_TEMPLATE="/etc/pgbouncer/userlist.txt.template"
|
||||
USERLIST="/etc/pgbouncer/userlist.txt"
|
||||
|
||||
if [ -z "${DB_USER:-}" ] || [ -z "${DB_PASSWORD:-}" ]; then
|
||||
echo "ERROR: DB_USER and DB_PASSWORD must be set" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Render userlist from template, substituting env vars
|
||||
envsubst < "$USERLIST_TEMPLATE" > "$USERLIST"
|
||||
chmod 600 "$USERLIST"
|
||||
|
||||
echo "PgBouncer userlist rendered for user: ${DB_USER}"
|
||||
echo "Starting PgBouncer on port 6432 (pool_mode=transaction, pool_size=${PGBOUNCER_POOL_SIZE:-20})..."
|
||||
|
||||
# Override pool settings via env vars if provided
|
||||
if [ -n "${PGBOUNCER_POOL_SIZE:-}" ]; then
|
||||
sed -i "s/^default_pool_size = .*/default_pool_size = ${PGBOUNCER_POOL_SIZE}/" /etc/pgbouncer/pgbouncer.ini
|
||||
fi
|
||||
|
||||
if [ -n "${PGBOUNCER_MAX_CLIENT_CONN:-}" ]; then
|
||||
sed -i "s/^max_client_conn = .*/max_client_conn = ${PGBOUNCER_MAX_CLIENT_CONN}/" /etc/pgbouncer/pgbouncer.ini
|
||||
fi
|
||||
|
||||
exec pgbouncer /etc/pgbouncer/pgbouncer.ini
|
||||
69
infra/pgbouncer/pgbouncer.ini
Normal file
69
infra/pgbouncer/pgbouncer.ini
Normal file
@@ -0,0 +1,69 @@
|
||||
;; =============================================================================
|
||||
;; PgBouncer Configuration for GoodGo Platform
|
||||
;; Docs: https://www.pgbouncer.org/config.html
|
||||
;; =============================================================================
|
||||
|
||||
[databases]
|
||||
;; Route all connections to the upstream PostgreSQL container.
|
||||
;; AUTH_USER is handled via userlist.txt; DB credentials are injected at runtime
|
||||
;; via environment variable substitution in the entrypoint.
|
||||
* = host=postgres port=5432
|
||||
|
||||
[pgbouncer]
|
||||
;; ── Listening ────────────────────────────────────────────────────────────────
|
||||
listen_addr = 0.0.0.0
|
||||
listen_port = 6432
|
||||
unix_socket_dir =
|
||||
|
||||
;; ── Authentication ───────────────────────────────────────────────────────────
|
||||
auth_type = md5
|
||||
auth_file = /etc/pgbouncer/userlist.txt
|
||||
|
||||
;; ── Pool Mode ────────────────────────────────────────────────────────────────
|
||||
;; "transaction" is recommended for short-lived, stateless web/API workloads.
|
||||
;; Each server connection is returned to the pool after every transaction.
|
||||
;; NOTE: session-level features (LISTEN/NOTIFY, prepared statements in older PG,
|
||||
;; advisory locks held across transactions) will NOT work in this mode.
|
||||
pool_mode = transaction
|
||||
|
||||
;; ── Pool Sizing ──────────────────────────────────────────────────────────────
|
||||
;; max_client_conn — total client connections PgBouncer will accept.
|
||||
;; default_pool_size — server connections per user/database pair.
|
||||
;; min_pool_size — pre-warmed connections kept open even when idle.
|
||||
;; reserve_pool_size — extra connections allowed when the pool is exhausted.
|
||||
;; reserve_pool_timeout — seconds to wait before using reserve connections.
|
||||
max_client_conn = 200
|
||||
default_pool_size = 20
|
||||
min_pool_size = 5
|
||||
reserve_pool_size = 5
|
||||
reserve_pool_timeout = 3
|
||||
|
||||
;; ── Timeouts ─────────────────────────────────────────────────────────────────
|
||||
;; server_connect_timeout — abort if backend doesn't accept within N seconds.
|
||||
;; server_idle_timeout — close idle backend connections after N seconds.
|
||||
;; server_lifetime — recycle backend connections after N seconds.
|
||||
;; client_idle_timeout — disconnect idle clients after N seconds (0 = off).
|
||||
;; query_timeout — cancel queries running longer than N seconds (0 = off).
|
||||
;; query_wait_timeout — error if a client waits this long for a server.
|
||||
server_connect_timeout = 15
|
||||
server_idle_timeout = 600
|
||||
server_lifetime = 3600
|
||||
client_idle_timeout = 0
|
||||
query_timeout = 0
|
||||
query_wait_timeout = 120
|
||||
|
||||
;; ── Logging ──────────────────────────────────────────────────────────────────
|
||||
log_connections = 1
|
||||
log_disconnections = 1
|
||||
log_pooler_errors = 1
|
||||
stats_period = 60
|
||||
|
||||
;; ── Admin Console ────────────────────────────────────────────────────────────
|
||||
admin_users = pgbouncer_admin
|
||||
stats_users = pgbouncer_stats
|
||||
|
||||
;; ── TLS (disabled — traffic stays within Docker network) ─────────────────────
|
||||
;; Uncomment and configure if PgBouncer is exposed outside the Docker network.
|
||||
;; client_tls_sslmode = prefer
|
||||
;; client_tls_key_file = /etc/pgbouncer/tls/server.key
|
||||
;; client_tls_cert_file = /etc/pgbouncer/tls/server.crt
|
||||
12
infra/pgbouncer/userlist.txt.template
Normal file
12
infra/pgbouncer/userlist.txt.template
Normal file
@@ -0,0 +1,12 @@
|
||||
;; =============================================================================
|
||||
;; PgBouncer userlist — injected at container startup
|
||||
;;
|
||||
;; This is a TEMPLATE file. The entrypoint script substitutes environment
|
||||
;; variables to produce the real /etc/pgbouncer/userlist.txt at runtime.
|
||||
;;
|
||||
;; Format: "username" "password"
|
||||
;; Passwords can be plaintext, md5, or scram-sha-256 hashed.
|
||||
;; =============================================================================
|
||||
"${DB_USER}" "${DB_PASSWORD}"
|
||||
"pgbouncer_admin" "${PGBOUNCER_ADMIN_PASSWORD}"
|
||||
"pgbouncer_stats" "${PGBOUNCER_STATS_PASSWORD}"
|
||||
Reference in New Issue
Block a user