Skip to content

Write-Ahead Log: Kenapa Database Menulis Dua Kali tapi Tetap Cepat ​

Setiap transaksi database yang sudah COMMIT menjanjikan satu hal yang terdengar mahal: datanya harus tetap tersimpan meskipun server database mendadak mati. Jaminan ini disebut durability.

Menulis ke disk memang lambat. Kalau setiap perubahan data harus langsung ditulis ke file tabel di disk sebelum COMMIT boleh dikonfirmasi, transaksi akan sangat lambat dan database akan menghabiskan sebagian besar waktunya menunggu disk.

Database relasional modern menyelesaikan dua tuntutan ini sekaligus, durability yang kuat dan commit yang cepat, dengan satu mekanisme yang disebut Write-Ahead Log (WAL). Menariknya, solusi ini justru membuat database menulis "dua kali": sekali ke log, dan belakangan sekali lagi ke file tabel. Tapi anehnya, justru karena desain ini commit menjadi jauh lebih cepat.

Apa itu Write-Ahead Log? ​

Write-Ahead Log bekerja dengan satu aturan sederhana yang menjadi nama turunannya: sebelum halaman data di file tabel diubah, perubahan harus dicatat dulu ke log di disk. Write ahead berarti "log ditulis lebih dulu".

Struktur WAL sangat sederhana:

  • File log bersifat append-only: record baru selalu ditambahkan di ujung file, tidak pernah mengubah record lama.
  • Isi tiap record adalah delta perubahan: "baris X di halaman Y berubah dari nilai A menjadi nilai B", bukan salinan seluruh halaman data.
  • Record ditulis berurutan seiring waktu, membentuk riwayat lengkap semua perubahan database.

Nama mekanismenya berbeda di tiap database, tapi ide dasarnya sama: WAL di PostgreSQL dan redo log di MySQL InnoDB.

Konsep Dasar

WAL bukan tempat penyimpanan data akhir. WAL adalah catatan perubahan yang dipakai untuk dua hal: memastikan COMMIT tahan crash (durability), dan membuat penulisan data yang mahal bisa ditunda ke waktu yang lebih tepat.

Konsep Dasar: dua tempat penyimpanan saat transaksi berjalan ​

Untuk memahami WAL, ada tiga komponen yang harus dipisahkan perannya:

  • Buffer cache (memori): database tidak membaca dan menulis halaman data langsung ke disk untuk setiap operasi. Halaman yang sedang aktif diakses ditaruh dulu di area memori yang disebut buffer cache (di PostgreSQL disebut shared buffers, di MySQL disebut buffer pool). Perubahan dari UPDATE atau INSERT pertama kali diterapkan ke halaman di memori ini. Halaman yang sudah diubah di memori tetapi belum tersinkron ke disk disebut dirty page.
  • WAL file (disk): saat transaksi melakukan COMMIT, yang wajib sampai ke disk hanyalah record WAL dari perubahan tersebut. Halaman data di buffer cache tidak perlu di-flush.
  • Checkpoint: proses latar belakang yang membersihkan dirty page (flush): menulis halaman yang sudah berubah dari buffer cache ke file tabel, sehingga salinan di memori dan di disk sama. Setelah dirty page tersimpan, WAL record yang sudah dipakai tidak lagi dibutuhkan untuk recovery dan file log-nya bisa di-recycle.

Alur menulisnya terlihat seperti ini:

Loading diagram...

Perhatikan urutannya: WAL ditulis dan di-flush ke disk lebih dulu. Setelah record WAL aman, COMMIT boleh dikonfirmasi ke aplikasi, meskipun halaman data di disk belum berubah sama sekali.

Satu catatan istilah sebelum lanjut: flush selalu berarti menulis isi memori ke disk, baik untuk WAL maupun untuk halaman data. Flush WAL berarti menulis record log ke file WAL di disk, agar tahan crash. Flush dirty page data berarti menulis dirty page ke file tabel, dan setelah tersimpan status halaman berubah dari dirty menjadi bersih. Kata membersihkan yang dipakai untuk checkpoint mengacu pada perubahan status halaman ini, bukan pada penghapusan isi log.

Kapan checkpoint berjalan? ​

Checkpoint bukan sesuatu yang berjalan di setiap commit. Ada dua pemicu yang perlu dipahami (contoh di PostgreSQL):

  • Berdasarkan waktu: setting checkpoint_timeout, default 5 menit. Database menjalankan checkpoint setiap interval ini, kecuali pemicu kedua tercapai lebih dulu.
  • Berdasarkan ukuran WAL: setting max_wal_size. Jika volume WAL yang tertulis sejak checkpoint terakhir sudah mendekati batas ini, database mempercepat checkpoint walau 5 menit belum tercapai. Aplikasi yang menulis sangat banyak, seperti batch besar atau ETL, akan memicu checkpoint lebih sering dibanding aplikasi yang traffic-nya kecil.

MySQL InnoDB tidak memakai interval waktu. Checkpoint-nya adaptive: mengikuti kapasitas redo log yang tersedia dan proporsi dirty page di buffer pool. Intinya sama dengan PostgreSQL: checkpoint berjalan berkala mengikuti beban tulis, bukan di setiap commit.

Konsep Dasar

Konsekuensi: durasi recovery setelah crash bergantung pada seberapa banyak WAL yang belum dipindahkan sejak checkpoint terakhir. Makin sering checkpoint, makin sedikit WAL yang harus diputar ulang saat database booting, makin cepat database kembali siap melayani.

Lifecycle File WAL ​

Setelah checkpoint dipahami, sisi berikutnya yang perlu dibahas sendiri adalah file log itu sendiri: apa yang terjadi padanya seiring waktu.

Jika tiap COMMIT selalu menulis di WAL, apakah file WAL bertambah terus tanpa batas? Jawabannya tidak, dan checkpoint inilah pembersih log-nya. WAL tersusun dari segmen-segmen berukuran tetap (di PostgreSQL, 16 MB per segmen). Saat checkpoint selesai, seluruh perubahan yang tercatat di segmen-segmen lama sudah aman tersimpan di file tabel. Segmen-segmen itu tidak dibutuhkan lagi untuk recovery, sehingga database boleh me-recycle-nya: di-reuse sebagai wadah record berikutnya, atau dihapus bila kapasitas sudah berlebih.

Loading diagram...

Segmen yang di-recycle adalah segmen yang seluruh isinya sudah tersimpan di file tabel. Setelah di-recycle, salinan log lamanya memang tidak ada lagi di WAL, tetapi datanya tidak hilang karena sudah tersimpan permanen di file tabel. Karena itu penggunaan disk untuk WAL naik-turun: bertumbuh sejak checkpoint terakhir, lalu stabil setelah segmen lama di-recycle. Yang selalu ada hanyalah segmen aktif yang sedang ditulis, jadi ukurannya tidak pernah kembali nol.

Aturan penting: segmen tidak boleh di-recycle selama masih ada pembaca yang belum selesai mengonsumsinya, misalnya server replikasi atau CDC connector. Bila salah satu berhenti dalam waktu lama, WAL menumpuk dan disk database bisa penuh. Ini alasan lag replikasi dan CDC perlu dipantau di production.

Bagaimana jika ada SELECT setelah COMMIT, padahal data belum dipindah ke tabel utama? ​

Ini pertanyaan yang wajar muncul: jika COMMIT hanya menulis ke WAL dan penulisan ke file tabel ditunda sampai checkpoint, bagaimana dengan query SELECT yang berjalan seketika setelahnya? Apakah query itu membaca data lama karena file tabel di disk belum diperbarui?

Jawabannya tidak. Untuk memahaminya, lihat kembali apa yang terjadi saat COMMIT:

  1. Database menulis record WAL ke disk.
  2. Sekaligus di momen yang sama, perubahan data sudah diterapkan ke halaman yang ada di buffer cache (memori). Dirty page itu langsung memuat nilai terbaru.

Jadi ketika SELECT berikutnya berjalan, prosesnya seperti ini:

Loading diagram...

Kunci pemahamannya ada di sini: SELECT selalu membaca dari buffer cache dulu. Halaman yang baru saja diubah oleh transaksi yang sudah commit pasti masih berada di buffer cache, lengkap dengan nilai terbarunya. Walau file tabel di disk masih memuat nilai lama, query SELECT tidak pernah membaca nilai lama itu, karena salinan memori lebih baru dan itulah yang dianggap benar oleh database.

Kapan pun checkpoint membersihkan dirty page ke file tabel, tidak ada yang berubah dari sisi pembacaan: nilai di memori dan nilai di disk akhirnya menjadi sama. Mekanisme WAL membuat kebenaran data tidak pernah bergantung pada seberapa cepat file tabel di-flush.

Batas pemahaman ini

Aturan "buffer cache memuat nilai terbaru" berlaku untuk transaksi yang sudah commit. Transaksi lain bisa saja membaca kondisi sebelum commit tergantung isolation level, yaitu level isolasi yang mengatur seberapa terlihat perubahan transaksi yang belum commit. Yang dijamin WAL adalah: setelah commit, nilai terbaru tidak mungkin hilang dan pembacaan berikutnya tidak akan mendapatkan nilai lama karena data belum ter-flush.

Sebagai pelengkap: jika server crash sebelum checkpoint sempat membersihkan dirty page, recovery saat booting membaca WAL dan memutar ulang perubahan yang sudah di-commit, sehingga file tabel kembali lengkap.

Kenapa WAL penting? ​

WAL memikul dua peran penting sekaligus:

  • Durability: COMMIT dikonfirmasi hanya setelah record WAL benar-benar aman di disk. Karena itu walau server mati detik berikutnya, database bisa memutar ulang WAL dan memulihkan seluruh perubahan yang sudah di-commit. Jaminan durability transaksi database secara teknis dibangun di atas WAL ini.
  • Performa: transaksi hanya menunggu satu tulisan kecil ke log, bukan penulisan halaman data yang mahal. Commit menjadi cepat dan database tetap ramai melayani query berikutnya.

Tanpa WAL, database harus memilih: commit cepat dengan risiko kehilangan data saat crash, atau durability kuat dengan commit yang lambat.

Mengapa WAL adalah desain yang bagus? ​

Sekarang pertanyaan yang paling sering muncul: kalau WAL dan file tabel sama-sama di disk, kenapa menulis ke WAL lebih cepat daripada menulis ke tabel utama? Ada tiga alasan utamanya:

1. Sequential append vs random I/O ​

Tulisan ke WAL selalu di ujung file, berurutan tanpa perlu mencari posisi (seek). Tulisan ke file tabel tersebar di banyak lokasi yang jauh terpisah: halaman accounts di satu area, halaman orders di area lain, halaman index di tempat lagi.

Di HDD, tiap perpindahan head mahal secara waktu. Di SSD, sequential write juga masih lebih efisien daripada random write karena pola akses yang lebih mudah diprediksi oleh controller. Satu tulisan berurutan ke WAL lebih murah daripada sepuluh tulisan tersebar ke file tabel, walau total byte-nya mirip.

2. Tulis delta, bukan seluruh halaman ​

Record WAL mencatat perubahan saja, bukan seluruh kondisi data. Contoh: aplikasi transfer uang menyimpan saldo tiap akun di tabel accounts, dan satu pembayaran hanya mengubah kolom balance di satu baris. Perubahan itu terekam di WAL sebagai "saldo ACC-001 berubah dari 50000 menjadi 150000". Sementara untuk menyimpan halaman data secara utuh, database harus menulis seluruh blok halaman, meski yang berubah hanya satu baris di dalamnya. Delta yang kecil berarti tulisan yang lebih sedikit dan lebih cepat.

3. Group commit ​

Banyak transaksi yang commit hampir bersamaan bisa digabung dalam satu flush WAL ke disk. Lima transaksi yang commit di milidetik yang sama cukup membayar sekali biaya sinkronisasi disk, bukan lima kali.

Konsep Dasar

Inti desain WAL: pindahkan tulisan yang mahal dan tersebar (halaman data) ke waktu yang santai di latar belakang, dan tukar dengan tulisan yang murah dan berurutan (log) di jalur kritis commit. Durability dijaga oleh log, throughput dijaga oleh penundaan penulisan data.

Sebagai penutup, log yang sama ini ternyata dipakai ulang untuk tujuan lain di pekerjaan: streaming replication (server standby memutar WAL untuk mengikuti server utama), point-in-time recovery (memulihkan database ke titik waktu tertentu dari backup plus WAL), dan change data capture atau CDC (tool seperti Debezium membaca ulang WAL untuk mengalirkan setiap perubahan data ke sistem lain seperti search index, cache, dan data warehouse). Satu desain sederhana, banyak manfaat.

Bacaan lanjutan

Untuk melihat bagaimana WAL dipakai sebagai sumber event dan perannya dalam menyelesaikan masalah dual write, baca article Change Data Capture: Menangkap Setiap Perubahan Data secara Real-Time.

Rangkuman ​

  • WAL menegakkan satu aturan: tulis log dulu di disk, baru ubah halaman data. Itulah sumber namanya, write-ahead.
  • Saat COMMIT, yang wajib aman di disk hanyalah record WAL. Perubahan data diterapkan di buffer cache (memori) sebagai dirty page.
  • SELECT selalu membaca dari buffer cache dulu, sehingga hasil query tetap nilai terbaru walau file tabel belum di-flush.
  • Checkpoint membersihkan dirty page ke file tabel (flush), dipicu oleh waktu (checkpoint_timeout, default 5 menit di PostgreSQL) atau ukuran WAL (max_wal_size). InnoDB memakai checkpoint adaptive.
  • WAL lebih cepat daripada menulis langsung ke tabel karena tulisannya sequential, hanya delta perubahan, dan bisa di-group commit.
  • Flush WAL berarti menulis record ke disk agar tahan crash, bukan mengosongkan log. Segmen lama baru di-recycle setelah checkpoint menyimpan perubahannya ke file tabel, sehingga ukuran WAL naik-turun dan tidak kembali nol.
  • Log yang sama dipakai untuk recovery, streaming replication, point-in-time recovery, dan CDC.

References ​