PostgreSQL Replication Slot Bloat: Ako Jeden Neaktívny Slot Naplnil 500GB Disk
Disk usage rastol a vinnik bol jeden zabudnuty slot. “Disk na 95%, databáza bude read-only za 30 minút.” Skontrolujem pg_wal: 400GB WAL súborov. Jeden neaktívny replication slot ich drží 5 dní.
Replication sloty sú nevyhnutné pre logical replication, ale jeden zabudnutý slot môže zhodiť tvoju databázu.
Testované na: PostgreSQL 16.1, logical replication do Debezium/Kafka
Ako Replication Slots Fungujú
WAL Retention
Normálny WAL lifecycle:
1. Transakcia → WAL zapísaný
2. Checkpointer označí WAL ako kompletný
3. Staré WAL súbory vymazané (podľa wal_keep_size)
S replication slotom:
1. Transakcia → WAL zapísaný
2. Slot sleduje "Potrebujem WAL od pozície X"
3. PostgreSQL drží VŠETOK WAL od pozície X
4. Aj keď je slot neaktívny celé dni!
Typy Slotov
-- Physical replication slot (streaming replication)
SELECT pg_create_physical_replication_slot('replica1');
-- Logical replication slot (Debezium, pg_logical, atď.)
SELECT pg_create_logical_replication_slot('debezium', 'pgoutput');
-- Oba bránia WAL cleanup!
Problém
Scenár: Debezium Spadne
Deň 1, 00:00: Debezium normálne konzumuje zmeny
Deň 1, 14:00: Debezium pod crashne, nevšimnuté
Deň 1, 14:00 → Deň 5: Replication slot prestane pokračovať
Deň 5, 03:00: Disk na 95%, alerty sa spustia
Nahromadený WAL: 400GB
Čas do katastrofy: 30 minút
Kontrola Stavu Slotu
-- Zobraz všetky replication sloty
SELECT
slot_name,
slot_type,
database,
active,
restart_lsn,
confirmed_flush_lsn,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS lag_size,
age(now(), pg_last_xact_replay_timestamp()) AS lag_time
FROM pg_replication_slots;
-- Príklad výstupu (problémový slot):
-- slot_name | active | lag_size
-- -----------+--------+---------
-- debezium | f | 412 GB ← Neaktívny, masívny lag!
Prevencia
1. max_slot_wal_keep_size (PostgreSQL 13+)
-- postgresql.conf
-- Maximum WAL držaného slotmi (v MB)
max_slot_wal_keep_size = '50GB'
-- Keď prekročené:
-- - Slot je zneplatnený
-- - WAL je uvoľnený
-- - Consumer musí re-sync (plný snapshot)
2. Monitoring Slot Lag
-- Prometheus query pre slot lag
SELECT
slot_name,
pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) AS lag_bytes
FROM pg_replication_slots;
# Alert rule
groups:
- name: replication
rules:
- alert: ReplicationSlotLagHigh
expr: pg_replication_slot_lag_bytes > 10737418240 # 10GB
for: 30m
labels:
severity: warning
annotations:
summary: "Replication slot {{ $labels.slot_name }} lag > 10GB"
- alert: ReplicationSlotInactive
expr: pg_replication_slot_active == 0
for: 1h
labels:
severity: critical
annotations:
summary: "Replication slot {{ $labels.slot_name }} neaktívny 1h"
3. Slot Timeout (Custom Riešenie)
-- Žiadny built-in slot timeout, ale môžeš implementovať cez cron
-- Vytvor helper funkciu
CREATE OR REPLACE FUNCTION drop_inactive_slots(max_inactive_hours int)
RETURNS TABLE(dropped_slot text) AS $$
DECLARE
r RECORD;
BEGIN
FOR r IN
SELECT slot_name
FROM pg_replication_slots
WHERE NOT active
AND age(now(), pg_last_xact_replay_timestamp()) > make_interval(hours => max_inactive_hours)
LOOP
PERFORM pg_drop_replication_slot(r.slot_name);
dropped_slot := r.slot_name;
RETURN NEXT;
END LOOP;
END;
$$ LANGUAGE plpgsql;
-- Spusti cez pg_cron
SELECT cron.schedule('drop-stale-slots', '0 * * * *',
$$SELECT drop_inactive_slots(24)$$);
Recovery Playbook
Okamžite: Uvoľni Disk
-- 1. Identifikuj problémový slot
SELECT slot_name, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS lag
FROM pg_replication_slots
ORDER BY pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) DESC;
-- 2. Dropni slot (consumer bude potrebovať plný resync!)
SELECT pg_drop_replication_slot('debezium');
-- 3. Spusti checkpoint na urýchlenie WAL cleanup
CHECKPOINT;
-- 4. Over že WAL sa čistí
-- (počkaj na ďalší checkpoint cyklus)
SELECT count(*), pg_size_pretty(sum(size))
FROM pg_ls_waldir();
Consumer Recovery
Po dropnutí slotu:
1. Debezium/Consumer potrebuje PLNÝ snapshot
2. Toto môže trvať hodiny pre veľké databázy
3. Plánuj na:
- Zvýšenú záťaž databázy počas snapshotu
- Dočasný lag dát v downstream systémoch
- Možné duplicitné spracovanie (idempotencia!)
Debezium Špecifické
# Debezium config pre nový snapshot
{
"connector.class": "io.debezium.connector.postgresql.PostgresConnector",
"snapshot.mode": "initial", # Plný snapshot pri štarte
"slot.drop.on.stop": "false", # Ponechaj slot pri zastavení (opatrne!)
# Alternatíva: Čistý reštart
"snapshot.mode": "initial_only" # Snapshot, potom stop
}
Vylepšenia Architektúry
1. Separátny Replikačný User
-- Dedikovaný user pre replikáciu
CREATE USER replication_user REPLICATION LOGIN;
GRANT USAGE ON SCHEMA public TO replication_user;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO replication_user;
-- Monitoruj spojenia tohto usera
SELECT * FROM pg_stat_replication WHERE usename = 'replication_user';
2. Health Check Endpoint
// health_check.go
func checkReplicationSlots(db *sql.DB) error {
var slotName string
var lagBytes int64
rows, err := db.Query(`
SELECT slot_name, pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)
FROM pg_replication_slots
WHERE NOT active
`)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
rows.Scan(&slotName, &lagBytes)
if lagBytes > 10*1024*1024*1024 { // 10GB
return fmt.Errorf("slot %s has %d bytes lag", slotName, lagBytes)
}
}
return nil
}
3. Kubernetes Liveness Probe
# Pre Debezium deployment
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: debezium
livenessProbe:
httpGet:
path: /connectors/postgres-connector/status
port: 8083
initialDelaySeconds: 60
periodSeconds: 30
failureThreshold: 3
Monitoring Dashboard
Kľúčové Metriky
# Veľkosť WAL adresára
pg_wal_directory_size_bytes
# Slot lag (per slot)
pg_replication_slot_wal_lsn_diff{slot_name=~".*"}
# Počet neaktívnych slotov
count(pg_replication_slot_active == 0)
# WAL generation rate
rate(pg_wal_lsn_diff(pg_current_wal_lsn())[5m])
Grafana Panel
{
"panels": [
{
"title": "Replication Slot Lag",
"type": "timeseries",
"targets": [
{
"expr": "pg_replication_slot_wal_lsn_diff",
"legendFormat": "{{ slot_name }}"
}
]
},
{
"title": "WAL Directory Size",
"type": "stat",
"targets": [
{
"expr": "pg_wal_directory_size_bytes"
}
]
}
]
}
Checklist
## Replication Slot Management
### Prevencia
- [ ] Nastav max_slot_wal_keep_size (PostgreSQL 13+)
- [ ] Alert na slot lag > 10GB
- [ ] Alert na neaktívne sloty > 1 hodina
- [ ] Implementuj automatické cleanup starých slotov
### Monitoring
- [ ] Dashboard so slot lag per slot
- [ ] Sledovanie veľkosti WAL adresára
- [ ] Consumer health checks (Debezium status)
### Recovery Plán
- [ ] Zdokumentuj slot drop procedúru
- [ ] Poznaj resync čas consumera pre plný snapshot
- [ ] Testuj recovery procedúru v stagingu
### Consumer Setup
- [ ] Liveness probes pre Debezium/consumers
- [ ] Auto-restart pri zlyhaní
- [ ] Alerting na zlyhania consumera
Záver
Replication sloty sú tichí zabijaci:
- Jeden neaktívny slot môže naplniť tvoj disk
- Nastav max_slot_wal_keep_size ako safety limit
- Alert na neaktívne sloty do 1 hodiny
- Maj recovery plán - consumer bude potrebovať plný resync
Slot na ktorý si zabudol je ten čo zhodí produkciu.
Súvisiace články
- PostgreSQL Autovacuum SLO - Database maintenance
- K8s PostgreSQL Connection Storm - Database v Kubernetes
Súvisiace články
Logical Replication Slot WAL Bloat: Keď Subscribery Odídu Offline
Disk sa plní WAL súbormi. Príčina: logical replication slot consumer odišiel offline a PostgreSQL drží všetok WAL odvtedy pretože by mohol byť potrebný.
PostgreSQL Idle in Transaction: Núdzový Playbook pre Zaseknuté Spojenia
Autovacuum nemôže bežať, table bloat rastie, všetko kvôli jednému 'idle in transaction' spojeniu. Tu je detekcia a kill playbook.
PostgreSQL Read Replica Konflikty: Prečo sa vaše dotazy rušia
Dotazy na read replikách zlyhávajú s 'canceling statement due to conflict with recovery'. Riešenie závisí od toho, ktorý z 5 typov konfliktov máte - tu je návod ako diagnostikovať a vyriešiť každý z nich.
pg_waldump WAL Forenzika: Rekonštrukcia Čo Sa Stalo s Tvojimi Dátami
Niečo zmazalo riadky z produkcie ale nikto nepriznáva že spustil DELETE. Použi pg_waldump na analýzu WAL súborov a rekonštruuj presne čo sa stalo a kedy.
Citujte tento článok
Ak na článok odkazujete, pridajte pôvodnú URL a uveďte autora.