Pengguna aplikasi tidak peduli bagaimana data sampai ke disk — yang mereka pedulikan, setelah layar menampilkan “transaksi berhasil”, data itu masih ada besok pagi, meski listrik padam lima menit kemudian. Menjamin hal itu tidak trivial: halaman data ditulis ke disk secara lambat dan tidak berurutan, sementara janji “tersimpan” harus diberikan saat COMMIT terjadi. PostgreSQL menjembatani kedua kecepatan itu dengan Write-Ahead Logging (WAL): setiap perubahan dicatat dulu ke log urutan-append yang cepat, baru kemudian halaman data di disk diperbarui dengan santai di belakangnya. Log inilah yang direplay saat crash recovery, dan arsipnya yang menjadi bahan bakar point-in-time recovery. Chapter ini membedah aturan tulis-dahulu itu dari sisi yang bisa kita amati langsung: alamat LSN, file segmen WAL, checkpoint, hingga parameter synchronous_commit yang menukar kecepatan dengan durability — bersambung dari base backup yang kita ambil di chapter sebelumnya.

Aturan Tulis-Dahulu #

Nama tekniknya adalah kontraknya: write-ahead berarti record WAL ditulis dan di-flush ke disk sebelum halaman data yang berhubungan boleh ditulis ke disk. Saat Anda menjalankan COMMIT, yang dipaksa ke disk hanyalah bagian WAL milik transaksi itu — halaman tabel dan indeksnya boleh tetap kotor di memori bertahun-turuh detik kemudian. Konsekuensinya ada dua. Pertama, klien menerima konfirmasi cepat karena hanya satu tulisan berurutan kecil yang disinkronkan. Kedua, di disk, file data dan WAL bisa saja tidak sinkron — dan justru WAL-lah yang selalu lebih mutakhir.

Ketidaksinkronan itulah sumber daya pulih. Jika server mati mendadak, file data bisa setengah tertulis, tetapi WAL memuat setiap perubahan yang sudah dikonfirmasi. Saat server dinyalakan kembali, proses startup menjalankan crash recovery: membaca checkpoint terakhir, lalu mereplay record WAL satu per satu dari titik itu sampai akhir log — seperti memutar ulang kaset transaksi di atas kondisi disk apa adanya. Hasilnya: database kembali konsisten di titik persis ketika server mati, tanpa satu pun transaksi terkonfirmasi hilang.

LSN: Alamat Baku Setiap Perubahan #

Setiap byte di WAL memiliki alamat yang disebut Log Sequence Number (LSN), ditulis heksadesimal dalam bentuk XX/XXXXXXXX. PostgreSQL menyediakan fungsi admin untuk berhitung dengan alamat itu. File berikut memakai LSN statis sebagai bahan latihan:

-- LSN ditulis dalam bentuk heksadesimal XX/XXXXXXXX.
-- Fungsi-fungsi admin memungkinkan kita berhitung dengan LSN statis.

-- Selisih dua posisi: satu segmen WAL (16 MiB).
SELECT pg_wal_lsn_diff('0/2000000', '0/1000000') AS selisih_byte;

-- Bentuk yang mudah dibaca manusia.
SELECT pg_size_pretty(pg_wal_lsn_diff('0/2000000', '0/1000000')) AS selisih;

-- Nama file segmen WAL tempat suatu LSN berada.
SELECT pg_walfile_name('0/2000000');

Output yang Dihasilkan:

text
 selisih_byte 
--------------
     16777216
(1 row)

 selisih 
---------
 16 MB
(1 row)

     pg_walfile_name      
--------------------------
 000000010000000000000002
(1 row)

Kita bedah tiga hasil itu:

  1. selisih_byte = 16777216: pg_wal_lsn_diff() menghitung jarak byte antara dua LSN. Selisih 0x1000000 heksadesimal memang tepat 16.777.216 byte — ukuran satu segmen WAL.
  2. selisih = 16 MB: pg_size_pretty() cukup memformat angka di atas agar enak dibaca — fungsi yang sama yang kita pakai untuk ukuran tabel dan indeks.
  3. pg_walfile_name = 000000010000000000000002: fungsi ini menerjemahkan LSN menjadi nama file segmen WAL. Nama 24 digit itu terpecah tiga: timeline 00000001, nomor log 00000000, dan nomor segmen 00000002 — LSN 0/2000000 memang jatuh di segmen kedua dari log ke-0.

Alat hitung ini bukan permainan kosong: saat Anda kelak menentukan target pemulihan (recovery_target_lsn) atau membaca laporan pg_waldump, angka-angka inilah bahasanya.

LSN Berjalan Saat Transaksi Terjadi #

Posisi tulis saat ini bisa dibaca dengan pg_current_wal_insert_lsn(). File berikut menangkap posisi itu sebelum dan sesudah dua INSERT:

-- Melihat LSN bergerak saat transaksi terjadi.
-- Nilai LSN bergantung pada sejarah tulisan cluster, jadi angkanya
-- berbeda di setiap eksekusi -- yang stabil adalah pola naiknya.
CREATE TABLE jejak_wal (id int PRIMARY KEY, catatan text);

SELECT pg_current_wal_insert_lsn() AS lsn_sebelum;

INSERT INTO jejak_wal VALUES (1, 'transaksi pertama'), (2, 'transaksi kedua');

SELECT pg_current_wal_insert_lsn() AS lsn_sesudah;

Output yang Dihasilkan:

text
CREATE TABLE
 lsn_sebelum 
-------------
 2/BB867FA8
(1 row)

INSERT 0 2
 lsn_sesudah 
-------------
 2/BB868168
(1 row)

Dua baris CREATE TABLE dan INSERT hanya menggeser LSN sekitar 448 byte ke depan — kecil, karena record WAL memang ringkas: cukup informasi untuk mengulang perubahan, bukan salinan halaman penuh. Angka 2/BB86... pasti berbeda di mesin Anda karena posisi LSN bergantung pada seluruh sejarah tulisan cluster; yang stabil adalah polanya: posisi sesudah selalu lebih besar daripada posisi sebelum.

Anatomi File WAL #

WAL disimpan di subdirektori pg_wal data directory sebagai rangkaian segmen berukuran tetap — di server latihan 16 MB per segmen, seperti yang terbaca dari wal_segment_size. Segmen yang sudah terisi dan tidak lagi dibutuhkan tidak dihapus, melainkan berganti nama menjadi segmen baru (recycle) — tulisan berurutan menjadi murah karena file lama tinggal dinamai ulang. Segmen lama juga dipertahankan untuk sementara demi standby yang lambat menarik data (dikendalikan wal_keep_size), dan penghapusan paling aman dilakukan lewat replication slot. Parameter wal_level menentukan seberapa banyak informasi ditulis: minimal untuk kecepatan maksimal tanpa replikasi, replica (nilai server latihan) menambah data yang dibutuhkan replikasi dan base backup, dan logical menambah info baris untuk logical replication.

Checkpoint: Titik Aman Replay #

Replay dari awal waktu jelas tidak masuk akal, maka server secara berkala menjalankan checkpoint: semua halaman kotor di-flush ke file data, lalu posisi tersebut dicatat ke WAL sebagai titik mulai replay berikutnya. Semakin jarang checkpoint, semakin panjang replay saat startup — semakin sering, semakin banyak flush besar yang mengganggu query. Dua parameter menjaga keseimbangan itu, dibaca lewat file berikut:

-- Parameter WAL dan checkpoint sebagaimana terbaca di server latihan.
SHOW wal_level;

SHOW wal_segment_size;

SHOW checkpoint_timeout;

SHOW max_wal_size;

SHOW min_wal_size;

Output yang Dihasilkan:

text
 wal_level 
-----------
 replica
(1 row)

 wal_segment_size 
------------------
 16MB
(1 row)

 checkpoint_timeout 
--------------------
 5min
(1 row)

 max_wal_size 
--------------
 1GB
(1 row)

 min_wal_size 
--------------
 80MB
(1 row)

Tiga nilai terakhir bekerja sebagai satu paket: checkpoint dijadwalkan tiap checkpoint_timeout (5 menit) atau lebih cepat bila volume WAL sejak checkpoint terakhir menembus max_wal_size (1 GB); min_wal_size (80 MB) adalah lantai ruang pg_wal yang tidak dilepas. Perintah CHECKPOINT; juga bisa dipicu manual dari sesi superuser — hasilnya tidak tampak di layar klien selain tag CHECKPOINT, tetapi jejaknya terlihat jelas di server log sebagai pasangan baris checkpoint starting: dan checkpoint complete: yang sudah kita bedah di chapter Server Log . Parameter checkpoint_completion_target (bawaan 0.9) menyuruh checkpoint menyelsaikan 90% pekerjaan menjelang jadwal berikutnya sehingga beban I/O tersebar, bukan menumpuk di satu saat — prinsip spread yang sama dengan opsi --checkpoint=spread milik pg_basebackup.

full_page_writes dan Halaman Robek #

Disk hanya menjamin tulisan per sektor, sedangkan PostgreSQL menulis halaman 8 KB. Saat listrik padam di tengah penulisan halaman, sebagian sektor bisa berisi data baru dan sebagian lama — torn page. WAL normal tidak bisa memperbaiki ini karena record WAL hanya memuat delta perubahan, bukan isi halaman penuh. Karena itu full_page_writes = on (bawaan) menyuruh server: segera setelah checkpoint, tulisan pertama ke setiap halaman data dikirim sebagai salinan halaman penuh ke WAL, bukan delta. Replay kemudian bisa memugar halaman robek dari salinan utuh itu. Konsekuensinya adalah sedikit membengkaknya WAL setelah setiap checkpoint — dan di situlah wal_compression berperan: mengompresi halaman-halaman penuh tersebut di dalam WAL.

synchronous_commit: Durability per Transaksi #

Satu parameter sering disalahpahami sebagai tombol “matikan WAL”: synchronous_commit. Fungsi aslinya lebih sempit — menentukan apakah COMMIT menunggu flush WAL ke disk (nilai on) atau langsung kembali tanpa menunggu (off). File berikut memetakan parameter-parameter ketahanan tulisan beserta konteks pengaturannya:

-- Parameter yang menentukan ketahanan tulisan terhadap kegagalan.
-- Kolom context memperlihatkan bagaimana tiap parameter boleh diubah:
-- sighup = reload, postmaster = restart, user = per sesi.
SELECT name, setting, context
FROM pg_settings
WHERE name IN ('fsync', 'synchronous_commit', 'full_page_writes',
               'wal_compression', 'wal_writer_delay', 'wal_writer_flush_after',
               'checkpoint_timeout', 'checkpoint_completion_target')
ORDER BY name;

Output yang Dihasilkan:

text
             name             | setting |  context  
------------------------------+---------+-----------
 checkpoint_completion_target | 0.9     | sighup
 checkpoint_timeout           | 300     | sighup
 fsync                        | on      | sighup
 full_page_writes             | on      | sighup
 synchronous_commit           | on      | user
 wal_compression              | off     | superuser
 wal_writer_delay             | 200     | sighup
 wal_writer_flush_after       | 128     | sighup
(8 rows)

Perhatikan kolom context untuk synchronous_commit: nilainya user, artinya parameter ini boleh diubah per sesi — bahkan per transaksi — tanpa menyentuh konfigurasi server. File berikut membuktikannya:

sql sesi-sync.sql
-- synchronous_commit berkonteks "user": bisa diatur per sesi,
-- bahkan per transaksi, tanpa menyentuh server.
SHOW synchronous_commit;

SET synchronous_commit = off;
SHOW synchronous_commit;

-- Kembalikan ke nilai bawaan sesi ini.
SET synchronous_commit = DEFAULT;
SHOW synchronous_commit;

Output yang Dihasilkan:

text
 synchronous_commit 
--------------------
 on
(1 row)

SET
 synchronous_commit 
--------------------
 off
(1 row)

SET
 synchronous_commit 
--------------------
 on
(1 row)

Tiga nilai SHOW berganti on → off → on murni di tingkat sesi: koneksi lain tidak terpengaruh. Dua sisi pertukarannya:

NilaiSaat COMMITAman terhadap crash server?Aman terhadap mati listrik?
on (bawaan)menunggu WAL ter-flush ke diskyaya, hingga detik commit
offlangsung balik tanpa menunggu flushya — crash recovery tetap utuhbisa kehilangan beberapa milidetik transaksi terakhir

Dengan synchronous_commit = off, window kehilangan dibatasi oleh wal_writer_delay (200 ms di server latihan): proses latar wal_writer mem-flush WAL berkala, jadi transaksi terakhir yang hilang hanya yang belum sempat tersapu. Ini membuat mode off cocok untuk beban yang boleh rugi sedetik data (log kunjungan, metrik) — dan tetap bukan tombol untuk menonaktifkan WAL itu sendiri.

WAL kini punya wajah nyata: segmen 16 MB berisi record beralamat LSN, dipangkas oleh checkpoint, dijamin oleh full-page writes. Pertanyaan berikutnya: bagaimana caranya memakai barisan WAL itu untuk memutar balik waktu — memulihkan database ke menit sebelum sebuah DROP TABLE terjadi? Jawabannya adalah arsip WAL dan point-in-time recovery, topik chapter berikutnya.

Referensi #

Berkas Sumber

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

aritmetika-lsn.sql

Unduh berkas
sql
-- LSN ditulis dalam bentuk heksadesimal XX/XXXXXXXX.
-- Fungsi-fungsi admin memungkinkan kita berhitung dengan LSN statis.

-- Selisih dua posisi: satu segmen WAL (16 MiB).
SELECT pg_wal_lsn_diff('0/2000000', '0/1000000') AS selisih_byte;

-- Bentuk yang mudah dibaca manusia.
SELECT pg_size_pretty(pg_wal_lsn_diff('0/2000000', '0/1000000')) AS selisih;

-- Nama file segmen WAL tempat suatu LSN berada.
SELECT pg_walfile_name('0/2000000');

konfigurasi-wal.sql

Unduh berkas
sql
-- Parameter WAL dan checkpoint sebagaimana terbaca di server latihan.
SHOW wal_level;

SHOW wal_segment_size;

SHOW checkpoint_timeout;

SHOW max_wal_size;

SHOW min_wal_size;

lsn-berjalan.sql

Unduh berkas
sql
-- Melihat LSN bergerak saat transaksi terjadi.
-- Nilai LSN bergantung pada sejarah tulisan cluster, jadi angkanya
-- berbeda di setiap eksekusi -- yang stabil adalah pola naiknya.
CREATE TABLE jejak_wal (id int PRIMARY KEY, catatan text);

SELECT pg_current_wal_insert_lsn() AS lsn_sebelum;

INSERT INTO jejak_wal VALUES (1, 'transaksi pertama'), (2, 'transaksi kedua');

SELECT pg_current_wal_insert_lsn() AS lsn_sesudah;

parameter-durability.sql

Unduh berkas
sql
-- Parameter yang menentukan ketahanan tulisan terhadap kegagalan.
-- Kolom context memperlihatkan bagaimana tiap parameter boleh diubah:
-- sighup = reload, postmaster = restart, user = per sesi.
SELECT name, setting, context
FROM pg_settings
WHERE name IN ('fsync', 'synchronous_commit', 'full_page_writes',
               'wal_compression', 'wal_writer_delay', 'wal_writer_flush_after',
               'checkpoint_timeout', 'checkpoint_completion_target')
ORDER BY name;

sesi-sync.sql

Unduh berkas
sql
-- synchronous_commit berkonteks "user": bisa diatur per sesi,
-- bahkan per transaksi, tanpa menyentuh server.
SHOW synchronous_commit;

SET synchronous_commit = off;
SHOW synchronous_commit;

-- Kembalikan ke nilai bawaan sesi ini.
SET synchronous_commit = DEFAULT;
SHOW synchronous_commit;