Kubernetes rollout bez výpadku DB: Ako zastaviť PostgreSQL connection storm
Je typ vypadku, ked je vsetko zelene a aj tak to hori. Spustis rollout, pody su Ready, a o 30 sekund neskor Postgres krici FATAL: too many connections for role "app".
Presne toto sa mi stalo, ked sme isli z 5 na 20 replik. Aplikacia bola ta ista, len rollout bol iny. To je connection storm: zdrave pody robia normalne veci naraz.
Testované na: k3d 5.x, PostgreSQL 15-16, PgBouncer 1.21+, Node.js 20+ a Java 21 aplikácie.
Čo je Connection Storm
Pri Kubernetes rollout deploymente:
- Rolling update - postupne terminuje staré pody a spúšťa nové
- Všetky nové pody sa pokúsia naraz pripojiť k databáze
- Staré pody ešte držia spojenia (graceful shutdown)
- Výsledok: 2x viac spojení než normálne
Normálny stav: 20 podov × 10 connections = 200 connections
Počas rolloutu: 20 starých + 20 nových = 400 connections
PostgreSQL limit: max_connections = 200
Prečo to fungovalo predtým
S 5 replikami:
- Normálne: 5 × 10 = 50 connections
- Rollout: 10 × 10 = 100 connections (pod limitom)
S 20 replikami:
- Normálne: 20 × 10 = 200 connections (na limite)
- Rollout: 40 × 10 = 400 connections BOOM
Reprodukovateľný Lab
Setup s k3d
# Vytvor cluster
k3d cluster create connection-storm --agents 3
# Nainštaluj PostgreSQL
helm repo add bitnami https://charts.bitnami.com/bitnami
helm install postgres bitnami/postgresql \
--set auth.postgresPassword=secret \
--set primary.resources.limits.memory=512Mi \
--set primary.configuration="max_connections=50"
Test Aplikácia
# app-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
replicas: 20
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
template:
spec:
containers:
- name: api
image: your-app:v1
env:
- name: DATABASE_URL
value: "postgres://postgres:secret@postgres:5432/app"
- name: POOL_SIZE
value: "5" # 20 pods × 5 = 100 connections
Spusti Rollout
# Watch connections
watch -n1 "kubectl exec -it postgres-postgresql-0 -- \
psql -U postgres -c 'SELECT count(*) FROM pg_stat_activity'"
# Trigger rollout
kubectl set image deployment/api api=your-app:v2
Výsledok: Počet connections vyskočí na 150-200 a aplikácia začne failovať.
Benchmark: Rollout bez PgBouncer
Test Scenár
20 replik, pool_size=5 per pod
max_connections=100
Rolling update 25% maxSurge
Meranie
# Počet connections počas rolloutu (každú sekundu)
while true; do
kubectl exec -it postgres-postgresql-0 -- \
psql -U postgres -c "SELECT count(*) FROM pg_stat_activity WHERE state != 'idle'" \
2>/dev/null | grep -E "^\s*[0-9]+"
sleep 1
done
Výsledky
| Fáza | Aktívne connections | Status |
|---|---|---|
| Pred rolloutom | 48 | OK |
| Rollout start | 73 | OK |
| Peak (t+15s) | 127 | FAIL (limit 100) |
| Stabilizácia | 48 | OK |
Connection refused errors: 23 za celý rollout
Riešenie 1: PgBouncer
PgBouncer je connection pooler, ktorý:
- Drží persistentné spojenia s PostgreSQL
- Multiplexuje aplikačné pripojenia
- Výrazne redukuje počet reálnych DB spojení
PgBouncer Deployment
# pgbouncer-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: pgbouncer-config
data:
pgbouncer.ini: |
[databases]
app = host=postgres-postgresql port=5432 dbname=app
[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 5432
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 20
min_pool_size = 5
reserve_pool_size = 5
reserve_pool_timeout = 3
server_lifetime = 3600
server_idle_timeout = 600
log_connections = 1
log_disconnections = 1
log_pooler_errors = 1
userlist.txt: |
"postgres" "secret"
---
# pgbouncer-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: pgbouncer
spec:
replicas: 2
template:
spec:
containers:
- name: pgbouncer
image: edoburu/pgbouncer:1.21.0
ports:
- containerPort: 5432
volumeMounts:
- name: config
mountPath: /etc/pgbouncer
resources:
limits:
memory: 128Mi
cpu: 100m
livenessProbe:
tcpSocket:
port: 5432
initialDelaySeconds: 10
readinessProbe:
exec:
command:
- /bin/sh
- -c
- "psql -h localhost -U postgres -c 'SHOW STATS' pgbouncer"
initialDelaySeconds: 5
volumes:
- name: config
configMap:
name: pgbouncer-config
Benchmark s PgBouncer
| Fáza | Client connections | Server connections | Status |
|---|---|---|---|
| Pred rolloutom | 100 | 20 | OK |
| Rollout start | 150 | 25 | OK |
| Peak (t+15s) | 200 | 30 | OK |
| Stabilizácia | 100 | 20 | OK |
Connection refused errors: 0
Riešenie 2: PreStop Hook s Jitter
Aj s PgBouncером môže byť problém ak všetky pody startujú naraz. Riešenie: rozložiť štarty v čase.
PreStop Hook
spec:
containers:
- name: api
lifecycle:
preStop:
exec:
command:
- /bin/sh
- -c
- "sleep 5" # Daj čas na drain
terminationGracePeriodSeconds: 30
Startup Jitter
// V aplikácii - random delay pred prvým DB query
const jitter = Math.random() * 5000; // 0-5 sekúnd
await new Promise(resolve => setTimeout(resolve, jitter));
await db.connect();
Alebo v Kubernetes:
spec:
containers:
- name: api
command:
- /bin/sh
- -c
- |
# Random delay 0-10 sekúnd
sleep $((RANDOM % 10))
exec node server.js
Benchmark s Jitter
| Metrika | Bez jitter | S jitter (0-5s) |
|---|---|---|
| Peak connections | 200 | 140 |
| Connection errors | 3 | 0 |
| Rollout duration | 45s | 52s |
Riešenie 3: Connection Pool Sizing
Výpočet správnej veľkosti poolu
Pravidlo palca:
pool_size = (2 × CPU cores) + disk_spindles
Pre typický cloud workload:
pool_size = 5-10 per pod
Dynamický pool sizing
// Počet connections podľa prostredia
const poolConfig = {
development: { min: 2, max: 5 },
staging: { min: 5, max: 10 },
production: { min: 10, max: 20 }
};
const pool = new Pool({
...poolConfig[process.env.NODE_ENV],
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 5000,
});
Connection Limiting per Pod
# HPA s custom metrics
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api
minReplicas: 5
maxReplicas: 50
metrics:
- type: Pods
pods:
metric:
name: db_connections_active
target:
type: AverageValue
averageValue: 8 # Scale keď avg > 8 connections per pod
Production Checklist
## DB-Safe Kubernetes Deployment Checklist
### Connection Management
- [ ] PgBouncer alebo podobný pooler pred databázou
- [ ] Pool mode: transaction (nie session)
- [ ] max_client_conn > (max_pods × pool_size_per_pod × 2)
- [ ] Server-side pool limit: default_pool_size < max_connections / expected_apps
### Deployment Strategy
- [ ] maxSurge: 25% alebo menej pre DB-heavy apps
- [ ] maxUnavailable: 25% (nie 0 - spôsobuje accumulation)
- [ ] terminationGracePeriodSeconds: min 30s
- [ ] preStop hook: sleep 5-10s
### Application
- [ ] Connection pool s min/max limits
- [ ] Connection timeout: max 5s
- [ ] Idle timeout: 30-60s
- [ ] Startup jitter: random 0-5s delay
### Monitoring
- [ ] Alert: pg_stat_activity count > 80% max_connections
- [ ] Alert: pgbouncer waiting clients > 0 sustained
- [ ] Dashboard: connections over time počas rolloutov
- [ ] Metric: connection_acquire_time_seconds
### Testing
- [ ] Load test s produkčným počtom replik
- [ ] Chaos test: rollout počas peak traffic
- [ ] Verify: zero connection errors počas rollout
Monitoring Setup
PostgreSQL Metrics
-- Aktuálne connections per user/database
SELECT usename, datname, count(*)
FROM pg_stat_activity
GROUP BY usename, datname;
-- Waiting queries (connection starvation)
SELECT count(*) FROM pg_stat_activity
WHERE wait_event_type = 'Client';
PgBouncer Metrics
# Pripoj sa na PgBouncer admin
psql -p 6432 -U postgres pgbouncer
# Štatistiky poolov
SHOW POOLS;
# Aktuálni klienti
SHOW CLIENTS;
# Servery (reálne DB connections)
SHOW SERVERS;
Prometheus Queries
# Connection usage %
pg_stat_activity_count / pg_settings_max_connections * 100
# Spike detection
increase(pg_stat_activity_count[1m]) > 50
# PgBouncer waiting clients
pgbouncer_pools_cl_waiting
Alternatívy k PgBouncer
Odyssey
Yandex fork, lepší pre veľké scale:
# Výhody
- Multi-threaded (PgBouncer je single-threaded)
- Lepší pre 10k+ connections
- TLS passthrough
# Nevýhody
- Menej dokumentácie
- Menšia komunita
pgcat
Cloudflare project:
# Výhody
- Rust (memory safe)
- Built-in sharding support
- Prometheus metrics native
# Nevýhody
- Relatívne nový
- Breaking changes medzi verziami
Záver
Connection storm je zákerný problém - všetko funguje až kým nezačneš škálovať. Kľúčové opatrenia:
- PgBouncer - nikdy sa nepripájaj priamo k PostgreSQL z aplikačných podov
- Transaction pooling - nie session pooling
- Jitter - rozlož štarty v čase
- Monitoring - sleduj connections počas každého rolloutu
- Load testing - testuj s produkčným počtom replik
Investícia do správneho connection managementu sa vráti pri každom deployi.
FAQ
Prečo nestačí zvýšiť max_connections?
PostgreSQL nie je navrhnutý na tisíce aktívnych spojení. Každé spojenie spotrebúva pamäť (~10MB) a context switching degraduje výkon. PgBouncer je efektívnejší.
Môžem použiť connection pooler v aplikácii namiesto PgBouncer?
Aplikačný pool neriešuje problém - každý pod má svoj pool, čo v súčte = rovnaký počet connections. Potrebuješ centralizovaný pooler.
Koľko PgBouncer replik potrebujem?
Pre väčšinu workloadov stačia 2 repliky (HA). PgBouncer je extrémne efektívny - jeden proces zvládne tisíce client connections.
Čo ak používam managed DB (RDS, Cloud SQL)?
Managed databázy majú striktnejšie limity. RDS default je 100-200 connections. Pooler je ešte dôležitejší.
Súvisiace články
- Zero-downtime migrácie PostgreSQL - Ako bezpečne migrovať pri rolling deployments
- CI/CD pre monorepo - Testovanie databázových connection limitov v pipeline
Súvisiace články
Zero-downtime migrácie PostgreSQL: Expand/Contract, backfill a rollback stratégie
Praktický playbook pre bezpečné databázové migrácie v produkcii. Od expand/contract patternu cez online indexy až po monitoring a rollback.
Keď Prepared Statements Spravia PostgreSQL 10× Pomalším: Generic Plan Trap
Rovnaký query, rovnaké parametre, ale prod je pomalý a staging funguje. Ukážem ako reprodukovať generic plan problém s pgBouncer, Java/Go a ako ho fixnúť.
CI/CD pre monorepo: Rýchlosť, cache, selektívne testy a supply-chain bezpečnosť
Kompletný blueprint pre efektívny CI/CD pipeline v monorepo - od path filters cez remote cache až po SBOM a SLSA. Praktické riešenia, nie teória.
Connection Pool Sizing s Little's Law: Matematický Prístup k HikariCP a PgBouncer
Pool size 50 lebo tak to bolo vždy? Ukážem ako použiť Little's Law na výpočet optimálnej veľkosti poolu a dokážem to load testom.
Citujte tento článok
Ak na článok odkazujete, pridajte pôvodnú URL a uveďte autora.