Metrik yang Perlu Dipantau
-- Cache hit ratio: idealnya di atas 99%
SELECT
sum(heap_blks_hit) * 100.0 / nullif(sum(heap_blks_hit + heap_blks_read), 0)
AS persen_cache_hit
FROM pg_statio_user_tables;
-- Pemakaian koneksi
SELECT count(*) AS terpakai,
current_setting('max_connections')::int AS maksimum,
count(*) FILTER (WHERE state = 'idle in transaction') AS menganggur_dlm_transaksi
FROM pg_stat_activity;
-- Transaksi terlama
SELECT max(now() - xact_start) FROM pg_stat_activity WHERE xact_start IS NOT NULL;
-- Umur transaction ID — untuk alert wraparound
SELECT max(age(datfrozenxid)) FROM pg_database;
-- Deadlock, konflik, berkas temporer
SELECT datname, deadlocks, conflicts, temp_files,
pg_size_pretty(temp_bytes) AS ukuran_temp
FROM pg_stat_database WHERE datname = current_database();
| Metrik | Peringatan | Kritis |
|---|---|---|
| Cache hit ratio | < 98% | < 95% |
| Koneksi terpakai | > 70% | > 85% |
| Transaksi terlama | > 5 menit | > 30 menit |
| Lag replikasi | > 50 MB | > 500 MB |
| Umur XID | > 1 miliar | > 1,5 miliar |
| Disk terpakai | > 75% | > 85% |
PgBouncer
# pgbouncer.ini [databases] toko = host=127.0.0.1 port=5432 dbname=toko [pgbouncer] listen_port = 6432 auth_type = scram-sha-256 pool_mode = transaction # paling efisien max_client_conn = 2000 # dari aplikasi default_pool_size = 25 # ke PostgreSQL reserve_pool_size = 5 server_idle_timeout = 600
| pool_mode | Koneksi dikembalikan setelah | Efisiensi | Batasan |
|---|---|---|---|
session | Klien memutus | Rendah | Tidak ada |
transaction | Setiap COMMIT | Tinggi | Tanpa prepared statement bernama, tanpa SET lintas transaksi, tanpa LISTEN/NOTIFY |
statement | Setiap pernyataan | Tertinggi | Tidak mendukung transaksi multi-pernyataan |
Karena koneksi fisik berpindah-pindah antar klien setiap selesai transaksi, hal-hal yang bergantung pada sesi menjadi berbahaya: SET biasa bocor ke permintaan berikutnya, LISTEN/NOTIFY tidak berfungsi, dan prepared statement bernama menjadi tidak valid.
Driver modern menyediakan penyesuaian untuk ini — misalnya pgx dengan QueryExecModeSimpleProtocol, atau JDBC dengan prepareThreshold=0. Pastikan driver Anda dikonfigurasi sesuai, kalau tidak Anda akan menemui error yang sangat membingungkan di bawah beban.
Daftar Periksa Produksi
Konfigurasi
shared_buffers ≈ 25% RAM | Cache halaman yang memadai |
effective_cache_size ≈ 50–75% RAM | Perencana membuat keputusan yang lebih baik |
work_mem konservatif secara global | Mencegah OOM saat banyak kueri paralel |
max_connections wajar + PgBouncer | Model proses PostgreSQL mahal per koneksi |
statement_timeout disetel | Kueri liar tidak berjalan selamanya |
idle_in_transaction_session_timeout | Mencegah bloat akibat transaksi menggantung |
random_page_cost = 1.1 pada SSD | Bawaan 4.0 mengasumsikan cakram berputar |
Keandalan & Keamanan
fsync = on | Jangan pernah dimatikan pada data sungguhan |
| Backup otomatis + arsip WAL | Memungkinkan pemulihan titik waktu |
| Uji pemulihan berkala | Backup yang belum diuji bukan backup |
| Alert umur XID | Wraparound menghentikan seluruh penulisan |
| Autovacuum disetel per tabel besar | Nilai bawaan tidak cocok untuk tabel besar |
scram-sha-256, bukan md5 | md5 sudah lemah |
| TLS diwajibkan, peran aplikasi bukan superuser | Hak akses terkecil |
pg_hba.conf membatasi sumber | Basis data tidak boleh terbuka ke internet |
1. Tidak ada statement_timeout. Satu kueri analitik yang lupa dibatasi bisa berjalan berjam-jam, menahan kunci, menghambat vacuum, dan membuat replika tertinggal jauh. Setel nilai global yang wajar dan naikkan hanya untuk pekerjaan yang memang butuh.
2. random_page_cost dibiarkan 4.0 pada SSD. Nilai bawaan berasal dari era cakram berputar ketika akses acak jauh lebih mahal daripada berurutan. Pada SSD, selisihnya kecil. Membiarkan nilai lama membuat perencana terlalu enggan memakai indeks dan lebih memilih pemindaian penuh yang sebenarnya lebih lambat.