Pukul 10:32 pagi, seorang developer menjalankan UPDATE tanpa WHERE di tabel pelanggan. Semalam pukul 02:00, backup harian terakhir sukses dibuat. Backup itu bisa memulihkan data — tetapi delapan jam transaksi ikut terhapus. Yang dibutuhkan bukan sekadar backup, melainkan mesin waktu: memutar cluster kembali ke 10:31:59, satu detik sebelum kecelakaan itu terjadi. PostgreSQL menyediakan mekanisme itu dan namanya sesuai literal: point-in-time recovery (PITR) — kombinasi satu base backup plus barisan WAL terarsip yang direplay sampai titik waktu pilihan Anda. Chapter ini membedah seluruh rangkaiannya: pengarsipan WAL, alur pemulihan, penentuan target lewat recovery_target_time atau recovery_target_lsn, hingga konsep timeline yang bercabang setelah setiap pemulihan — langsung dari konsep WAL di chapter sebelumnya.
Konsep: Base Backup + Replay Arsip WAL #
Rumus PITR sederhana dan kaku: base backup yang baik + arsip WAL tanpa celah + target yang tepat. Base backup memberi kondisi awal; setiap segmen WAL yang terarsip setelah backup memberi satu langkah maju; server pemulihan lalu berjalan menyusuri barisan itu dan berhenti persis di titik yang Anda tentukan — waktu, LSN, atau nama restore point. Dua syarat turunannya tidak bisa dinegosiasi: arsip WAL harus berjalan sebelum base backup diambil dan tidak boleh putus satu segmen pun, karena satu celah membuat replay macet di tengah jalan. Dari sisi bisnis, PITR-lah yang menentukan RPO terbaik Anda: dengan arsip kontinu, data yang hilang hanyalah transaksi yang terjadi setelah detik kejadian — bukan seluruh hari kerja.
Status Arsip di Server Latihan #
Pengarsipan WAL dikendalikan tiga parameter dan dipantau lewat satu view katalog. File berikut membaca semuanya dari server latihan:
-- Status pengarsipan WAL di server latihan.
SHOW archive_mode;
SHOW archive_command;
SHOW archive_timeout;
-- Statistik proses archiver: berkas terarsip vs gagal.
SELECT archived_count, failed_count FROM pg_stat_archiver;Output yang Dihasilkan:
archive_mode
--------------
off
(1 row)
archive_command
-----------------
(disabled)
(1 row)
archive_timeout
-----------------
0
(1 row)
archived_count | failed_count
----------------+--------------
0 | 0
(1 row)Kita bedah empat hasil itu:
archive_mode = off: server latihan tidak mengarsipkan WAL — jujur kita akui: skenario PITR penuh tidak bisa dijalankan di lab ini tanpa restart server (parameter ini berkontekspostmaster), dan mengubah konfigurasi global server latihan berada di luar batas chapter. Bagian-bagian berikut karena itu membedakan dengan tegas antara yang kita uji langsung dan yang didokumentasikan sebagai sketsa konfigurasi.archive_command = (disabled): teks(disabled)muncul karenaarchive_modemati; bila aktif, kolom ini berisi perintah pengarsipan yang sesungguhnya.archive_timeout = 0: tidak ada pemaksaan pengarsipan berkala. Server rendah lalu (toko online kecil di malam hari) bisa mengalami satu segmen WAL baru penuh berhari-hari;archive_timeout = '60s'menutup celah itu dengan menutup segmen lebih cepat walau belum penuh.archived_count = 0, failed_count = 0: statistikpg_stat_archiver— angka yang akan Anda pantau di produksi;failed_countyang naik adalah alarm pertama bahwa arsip WAL sedang bocor.
Mengaktifkan arsip di server produksi membutuhkan dua baris di postgresql.conf (atau postgresql.auto.conf) dan satu restart:
archive_mode = on
archive_command = 'test ! -f /var/wal_archive/%f && cp %p /var/wal_archive/%f'Dua placeholder di dalamnya punya makna tetap: %p adalah path absolut segmen WAL yang sedang diarsipkan, %f adalah nama filenya saja. Kontrak archive_command juga kaku: exit code 0 hanya bila segmen benar-benar tersimpan — uji test ! -f di depan mencegah penimpaan arsip lama. Selama archiver gagal, PostgreSQL menahan segmen itu dan tidak mendaur ulang pg_wal, sehingga disk perlahan membengkak — alarm, bukan kegagalan total, tapi alarm yang tidak boleh diabaikan.
Parameter Recovery dan Konteksnya #
Seluruh tombol PITR hidup sebagai parameter server. File berikut memetakan keduanya sekaligus — parameter arsip dan parameter target pemulihan:
-- Parameter arsip dan pemulihan beserta konteks pengaturannya.
SELECT name, setting, context
FROM pg_settings
WHERE name LIKE 'archive%'
OR name LIKE 'recovery_target%'
OR name = 'restore_command'
ORDER BY name;
SHOW recovery_target_action;
SHOW recovery_target_inclusive;Output yang Dihasilkan:
name | setting | context
---------------------------+------------+------------
archive_cleanup_command | | sighup
archive_command | (disabled) | sighup
archive_library | | sighup
archive_mode | off | postmaster
archive_timeout | 0 | sighup
recovery_target | | postmaster
recovery_target_action | pause | postmaster
recovery_target_inclusive | on | postmaster
recovery_target_lsn | | postmaster
recovery_target_name | | postmaster
recovery_target_time | | postmaster
recovery_target_timeline | latest | postmaster
recovery_target_xid | | postmaster
restore_command | | sighup
(14 rows)
recovery_target_action
------------------------
pause
(1 row)
recovery_target_inclusive
---------------------------
on
(1 row)Tiga pengamatan penting dari tabel itu:
archive_modeberkontekspostmaster— mengubahnya menuntut restart;archive_commanddanarchive_timeoutberkontekssighup— cukup reload. Rencanakan jendela restart sebelum menyalakan arsip di produksi.- Seluruh
recovery_target_*kosong danrestore_commandkosong — inilah wujud “server normal”: parameter target memang hanya diisi pada data directory yang sedang dipulihkan, bukan di server yang sehat. recovery_target_action = pause— perilaku bawaan saat target tercapai: server berhenti sejenak dalam keadaan konsisten dan menunggu keputusan Anda (detail di bagian verifikasi).
Satu catatan versi penting: di PostgreSQL 16 dan sebelumnya, parameter ini bernama pause_at_recovery_target. PostgreSQL 17 menggantinya dengan recovery_target_action yang menawarkan tiga pilihan (pause, promote, wait). Parameter lama itu sudah tidak ada — file berikut membuktikannya dengan error nyata:
-- PostgreSQL 17 menghapus pause_at_recovery_target.
SHOW pause_at_recovery_target;Output yang Dihasilkan:
psql:pause-error.sql:2: ERROR: unrecognized configuration parameter "pause_at_recovery_target"Alur Pemulihan PITR #
PITR adalah prosedur berurutan yang tidak boleh diacak. Tabel ini merangkum alurnya dari awal sampai promote:
| Langkah | Tindakan | Catatan |
|---|---|---|
| 1 | Matikan server yang rusak (atau sisihkan datanya) | jangan pernah pulihkan di atas data directory asli |
| 2 | Siapkan data directory baru, salin base backup ke sana | format plain tinggal dipakai; format tar diekstrak dulu |
| 3 | Buat file recovery.signal di akar data directory | file kosong ini menyalakan mode pemulihan |
| 4 | Isi restore_command + recovery_target_* di postgresql.auto.conf | arahkan ke arsip WAL dan titik henti |
| 5 | Jalankan server dengan pg_ctl start | server menarik segmen WAL dari arsip dan mereplay-nya |
| 6 | Server mencapai target, lalu pause | verifikasi data secara read-only |
| 7 | SELECT pg_wal_promote(); | server resmi jadi primary baru di timeline baru |
Bentuk nyata langkah 3 dan 4 pada data directory yang dipulihkan — didokumentasikan sebagai sketsa konfigurasi, karena menjalankannya menuntut skenario arsip penuh:
# di dalam data directory hasil restore base backup:
touch recovery.signal
# tambahkan ke postgresql.auto.conf di data directory itu:
restore_command = 'cp /var/wal_archive/%f %p'
recovery_target_time = '2026-10-14 10:31:59+07'
recovery_target_action = 'pause'Perhatikan bahwa restore_command adalah kebalikan archive_command: %f nama file di arsip, %p path tujuan di pg_wal lokal. Target waktu mengikuti format stempel waktu berzona — zona wajib eksplisit agar tidak bergantung pada zona waktu server yang memulihkan.
Menentukan Titik Pemulihan #
Empat jenis target tersedia, masing-masing cocok untuk situasi berbeda:
| Target | Bentuk | Cocok saat |
|---|---|---|
recovery_target_time | 2026-10-14 10:31:59+07 | kejadian diketahui waktunya dari log aplikasi/server |
recovery_target_lsn | 2/BB868168 | posisi persis diketahui dari pg_waldump atau laporan |
recovery_target_name | sebelum-migrasi | restore point dibuat sadar lewat pg_create_restore_point() |
recovery_target_xid | ID transaksi | mengejar/menghindari transaksi spesifik — paling jarang |
Parameter recovery_target_inclusive (bawaan on) menentukan nasib transaksi yang jatuh tepat di target: disertakan (on) atau dibuang (off) — satu detik batas bisa memutuskan apakah UPDATE yang celaka ikut terpulihkan atau tidak. Untuk mencari LSN atau waktu tepat, alat andalannya adalah pg_waldump (ada di direktori biner server): membaca segmen WAL mentah dan mencetak record-nya beserta LSN, timestamp, dan jenis perubahan — sehingga Anda bisa menemukan baris terakhir sebelum kecelakaan, lalu memilih target satu record sebelumnya.
Timeline: Sejarah yang Bercabang #
Begitu pemulihan selesai dan server dipromosikan menjadi primary baru, PostgreSQL tidak melanjutkan sejarah lama begitu saja — server membuat timeline baru. Pemulihan pertama dari timeline 1 menghasilkan timeline 2, dan setiap cabang berikutnya bernomor lebih tinggi. Nomor itu bukan hiasan: WAL yang ditulis server baru diberi prefiks timeline (00000002...), dan file pg_wal/00000002.history mencatat kapan dan di LSN mana percabangan terjadi. Alasannya murni keselamatan — dua server yang menulis WAL berbeda di timeline yang sama saling menimpa sejarah; timeline terpisah membuat salinan-salinan itu jelas berbeda asal-usulnya. Parameter recovery_target_timeline (bawaan latest) mengatur cabang mana yang diikuti bila Anda memulihkan dari data directory yang sudah pernah dipromosikan.
Server latihan kita belum pernah menjalani pemulihan, jadi posisinya masih di timeline awal — dua query berikut membuktikannya:
-- Timeline aktif server latihan.
-- Server yang belum pernah dipromosikan berjalan di timeline 1.
SELECT pg_is_in_recovery();
SELECT timeline_id FROM pg_control_checkpoint();Output yang Dihasilkan:
pg_is_in_recovery
-------------------
f
(1 row)
timeline_id
-------------
1
(1 row)pg_is_in_recovery() bernilai f — server berjalan normal sebagai primary, bukan sedang mereplay WAL — dan timeline_id bernilai 1. Setelah PITR sungguhan dan promote, dua query yang sama di server hasil pemulihan akan menjawab f dan 2: tanda lahirnya cabang sejarah baru.
Verifikasi Sebelum Promote #
Bawaan recovery_target_action = pause bukan kebetulan; pause adalah pintu verifikasi. Saat server berhenti di target, seluruh data bisa dibaca secara read-only: hitung baris tabel kritis, jalankan query checksum seperti md5(string_agg(...)), cek keberadaan baris yang seharusnya belum terhapus. Bila target meleset, matikan server, ulangi prosedur dari base backup dengan target baru. Bila semua cocok, promote — di PostgreSQL 17 dari sesi SQL biasa lewat SELECT pg_wal_promote();, atau dari luar dengan pg_ctl promote. Promote menandai akhir pemulihan: timeline baru dibuat, server menerima tulisan, dan jam mesin waktu berhenti bekerja untuk kabar baik — data Anda kembali hidup di titik yang Anda pilih.
Mampu memulihkan ke titik waktu mana pun berarti beban berikutnya jadi operasional: base backup rutin, arsip WAL yang dipantau, retensi yang disiplin. Semua jadwal itu kita susun di chapter Backup Scheduling .
Referensi #
- Continuous Archiving and Point-in-Time Recovery
- Write-Ahead Logging (WAL)
- Write Ahead Log Configuration
- Backup and Restore
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
parameter-recovery.sql
Unduh berkas-- Parameter arsip dan pemulihan beserta konteks pengaturannya.
SELECT name, setting, context
FROM pg_settings
WHERE name LIKE 'archive%'
OR name LIKE 'recovery_target%'
OR name = 'restore_command'
ORDER BY name;
SHOW recovery_target_action;
SHOW recovery_target_inclusive;
pause-error.sql
Unduh berkas-- PostgreSQL 17 menghapus pause_at_recovery_target.
SHOW pause_at_recovery_target;
status-arsip.sql
Unduh berkas-- Status pengarsipan WAL di server latihan.
SHOW archive_mode;
SHOW archive_command;
SHOW archive_timeout;
-- Statistik proses archiver: berkas terarsip vs gagal.
SELECT archived_count, failed_count FROM pg_stat_archiver;
timeline-aktif.sql
Unduh berkas-- Timeline aktif server latihan.
-- Server yang belum pernah dipromosikan berjalan di timeline 1.
SELECT pg_is_in_recovery();
SELECT timeline_id FROM pg_control_checkpoint();