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:

text
 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:

  1. 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 berkonteks postmaster), 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.
  2. archive_command = (disabled): teks (disabled) muncul karena archive_mode mati; bila aktif, kolom ini berisi perintah pengarsipan yang sesungguhnya.
  3. 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.
  4. archived_count = 0, failed_count = 0: statistik pg_stat_archiver — angka yang akan Anda pantau di produksi; failed_count yang 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:

text
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:

text
           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:

  1. archive_mode berkonteks postmaster — mengubahnya menuntut restart; archive_command dan archive_timeout berkonteks sighup — cukup reload. Rencanakan jendela restart sebelum menyalakan arsip di produksi.
  2. Seluruh recovery_target_* kosong dan restore_command kosong — inilah wujud “server normal”: parameter target memang hanya diisi pada data directory yang sedang dipulihkan, bukan di server yang sehat.
  3. 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:

sql pause-error.sql
-- PostgreSQL 17 menghapus pause_at_recovery_target.
SHOW pause_at_recovery_target;

Output yang Dihasilkan:

text
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:

LangkahTindakanCatatan
1Matikan server yang rusak (atau sisihkan datanya)jangan pernah pulihkan di atas data directory asli
2Siapkan data directory baru, salin base backup ke sanaformat plain tinggal dipakai; format tar diekstrak dulu
3Buat file recovery.signal di akar data directoryfile kosong ini menyalakan mode pemulihan
4Isi restore_command + recovery_target_* di postgresql.auto.confarahkan ke arsip WAL dan titik henti
5Jalankan server dengan pg_ctl startserver menarik segmen WAL dari arsip dan mereplay-nya
6Server mencapai target, lalu pauseverifikasi data secara read-only
7SELECT 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:

text
# 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:

TargetBentukCocok saat
recovery_target_time2026-10-14 10:31:59+07kejadian diketahui waktunya dari log aplikasi/server
recovery_target_lsn2/BB868168posisi persis diketahui dari pg_waldump atau laporan
recovery_target_namesebelum-migrasirestore point dibuat sadar lewat pg_create_restore_point()
recovery_target_xidID transaksimengejar/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:

text
 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 #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

parameter-recovery.sql

Unduh berkas
sql
-- 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
sql
-- PostgreSQL 17 menghapus pause_at_recovery_target.
SHOW pause_at_recovery_target;

status-arsip.sql

Unduh berkas
sql
-- 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
sql
-- 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();