Ketika proses archive_command pada PostgreSQL gagal, proses archiver internal tidak akan menghapus atau mendaur ulang berkas WAL di direktori pg_wal. Segmen WAL akan terus menumpuk hingga kapasitas penyimpanan habis. Saat disk 100% penuh, PostgreSQL langsung melakukan panic shutdown untuk mencegah korupsi data, memicu downtime tak terencana.

Penyebab Utama dan Anatomi Disk Bloat pada pg_wal

PostgreSQL mempertahankan berkas WAL sampai segmen tersebut sukses diarsipkan oleh proses eksternal. Apabila perintah keluar dengan status non-zero (gagal), PostgreSQL akan terus mencoba ulang segmen yang sama. Penyebab umum mencakup:

  • Kegagalan autentikasi atau token expired pada object storage (S3, GCS).
  • Limitasi bandwidth jaringan atau latensi tinggi ke storage target.
  • Kesalahan konfigurasi CLI tool seperti salah path biner atau variabel environment tidak terbaca.
  • Kapasitas bucket object storage penuh atau terkena rate limit API.

Konfigurasi WAL-G untuk Archiving Cepat

WAL-G adalah tool archiving modern berbasis Golang yang mendukung kompresi paralel (lz4, zstd) dan upload langsung ke object storage. Contoh konfigurasi pada postgresql.conf:

wal_level = replica
archive_mode = on
archive_timeout = 60
archive_command = '/usr/local/bin/wal-g wal-push %p'

WAL-G memerlukan variabel environment storage (misalnya WALG_S3_PREFIX, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY) yang harus diekspos ke environment proses PostgreSQL via systemd drop-in file atau wrapper script.

Observability: Monitoring Lag Segmen WAL via Prometheus

Metrik utama kegagalan archiving dapat dipantau langsung dari system catalog PostgreSQL pg_stat_archiver. Gunakan prometheus-community/postgres-exporter untuk mengekspos metrik ini.

Kueri Custom pg_stat_archiver untuk postgres_exporter

Tambahkan file kueri kustom queries.yaml pada postgres_exporter:

pg_stat_archiver:
  query: "SELECT archived_count, failed_count, (SELECT count(*) FROM pg_ls_waldir()) AS current_wal_segments, CAST(EXTRACT(EPOCH FROM (now() - last_failed_time)) AS INT) AS seconds_since_last_fail FROM pg_stat_archiver;"
  metrics:
    - archived_count:
        usage: "COUNTER"
        description: "Total WAL segments successfully archived"
    - failed_count:
        usage: "COUNTER"
        description: "Total WAL segments failed to archive"
    - current_wal_segments:
        usage: "GAUGE"
        description: "Total WAL segments currently in pg_wal"
    - seconds_since_last_fail:
        usage: "GAUGE"
        description: "Time elapsed since last archive failure"

Alerting Rules di Prometheus Alertmanager

Buat ambang batas kritis agar tim dapat mengambil tindakan sebelum disk terisi penuh:

groups:
  - name: postgres_wal_archive_alerts
    rules:
      - alert: PostgresWALArchivingFailing
        expr: increase(pg_stat_archiver_failed_count[5m]) > 3
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "PostgreSQL WAL Archiving is failing"
          description: "Database instance {{ $labels.instance }} failed to archive WAL segments multiple times in the last 5 minutes."

      - alert: PostgresWALDiskExhaustionRisk
        expr: pg_stat_archiver_current_wal_segments > 200
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "High number of pending WAL segments in pg_wal"
          description: "Instance {{ $labels.instance }} has {{ $value }} segments pending in pg_wal, approaching disk limit."

Graceful Auto-Rollback dan Circuit Breaker Pattern

Ketika sistem monitoring mendeteksi penumpukan WAL yang berisiko mematikan klaster database, langkah operasional darurat harus dieksekusi secara otomatis atau semi-otomatis melalui script monitoring lokal.

Peringatan Keamanan Data: Mengabaikan kegagalan archiving mengorbankan titik Point-in-Time Recovery (PITR). Pendekatan ini adalah trade-off demi mencegah downtime total database produksi.

Implementasi Wrapper Script Circuit-Breaker

Gunakan script wrapper sebagai perantara archive_command. Script ini akan fallback ke mode discard (exit code 0) hanya jika disk lokal berada di ambang batas ekstrim (>90%):

#!/usr/bin/env bash
set -euo pipefail

WAL_FILE="$1"
THRESHOLD=90

# Eksekusi WAL-G push
if /usr/local/bin/wal-g wal-push "$WAL_FILE"; then
    exit 0
fi

# Jika WAL-G gagal, cek penggunaan partisi disk pg_wal
USAGE=$(df --output=pcent /var/lib/postgresql/data/pg_wal | tr -dc '0-9')

if [ "$USAGE" -ge "$THRESHOLD" ]; then
    logger -t postgres-archiver -p user.emerg "CRITICAL: Disk full ($USAGE%). Skipping WAL $WAL_FILE to prevent DB crash. PITR gap introduced!"
    # Keluar dengan 0 agar PostgreSQL menganggap file terarsip dan mendaur ulangnya
    exit 0
else
    logger -t postgres-archiver -p user.err "ERROR: wal-g failed for $WAL_FILE. Disk at $USAGE%. Retrying later."
    exit 1
fi

Ubah konfigurasi postgresql.conf untuk memanggil wrapper tersebut:

archive_command = '/opt/scripts/wal_circuit_breaker.sh %p'

Jalankan SELECT pg_reload_conf(); untuk menerapkan perubahan konfigurasi tanpa restart database.

Verifikasi Backup Terjadwal untuk Mencegah Replikasi Macet

Archiving yang gagal sering menyebabkan WAL-G tidak dapat membuat basebackup yang konsisten atau menyebabkan standby database lag karena perbedaan LSN (Log Sequence Number). Lakukan verifikasi integritas backup secara berkala:

  1. Otomatisasi Pengecekan Backup: Jalankan cron job untuk memeriksa manifest backup WAL-G:
    /usr/local/bin/wal-g backup-list --detail --json
  2. Dry-Run Restore: Buat worker ephemeral (container atau VM terpisah) setiap minggu untuk menguji proses restore menggunakan wal-g backup-fetch dan menjalankan WAL replay hingga titik akhir.
  3. Validasi WAL Hole: Periksa apakah ada segment yang terlewat dalam bucket menggunakan fitur verifikasi WAL bawaan WAL-G:
    /usr/local/bin/wal-g wal-verify integrity
    Perintah ini mendeteksi apakah segmen kontinuitas terputus yang berpotensi menggagalkan crash recovery pada replica.