Tunas Akara
Kembali ke Blog

Kesalahan WAL SQLite dan Batas Teknologi yang Sudah Matang

Kesalahan SQLite yang lama tersembunyi menunjukkan pentingnya memeriksa pola penggunaan, versi perbaikan, integritas, dan pencadangan.

oleh RayhanDiperbarui 8 menit baca

Untuk Tim Teknis

sqlitedata-integrityembedded-databasereliability
Kesalahan WAL SQLite dan Batas Teknologi yang Sudah Matang
Daftar isi

Satu kesalahan SQLite tidak terdeteksi selama enam belas tahun. Dalam kondisi tertentu, baris yang transaksinya sudah dinyatakan tersimpan dapat hilang tanpa pesan galat atau proses berhenti. Tailscale menemukannya melalui pola pemakaian yang berbeda dari kebanyakan pengguna.

Agustus 2026, Tailscale merilis laporan pascainsiden setelah enam bulan penyelidikan. Tim menelusuri penyebab baris data yang hilang tanpa pesan galat. Sembilan belas insiden korupsi data terpisah, tanpa pemicu yang konsisten, tanpa pesan galat. Kesalahannya bukan di kode Tailscale. Kesalahan itu ada di dalam SQLite sendiri — basis data yang dibundel ke lebih banyak perangkat dan aplikasi dibanding hampir semua perangkat lunak lain. Kesalahan itu sudah bersembunyi di sana selama kurang lebih enam belas tahun.

Tulisannya mencapai 1.221 poin dan 239 komentar di Hacker News. Hampir semua engineer punya SQLite di salah satu sudut susunan teknologinya, dan mekanisme kesalahannya memunculkan pertanyaan yang wajar: apakah ini berlaku untuk sistem saya juga?

Apa yang sebenarnya rusak

Bayangkan paket sudah ditandai "terkirim" sebelum dipindahkan ke tujuan akhirnya. Jika pemindahan gagal, statusnya terlihat selesai meski paket tidak pernah sampai. Mekanisme kesalahan ini memiliki akibat serupa pada penyimpanan data.

Dalam mode write-ahead logging (WAL), SQLite menulis perubahan ke catatan terpisah sebelum menyalinnya ke berkas basis data utama. Mode bawaan SQLite sebenarnya rollback journal (journal_mode=DELETE); WAL perlu diaktifkan. Halaman data baru masuk dulu ke berkas WAL terpisah; sebuah "pemindahan data WAL" belakangan menyalin halaman-halaman itu ke berkas utama dan mengosongkan catatannya. Langkah pemindahan data WAL inilah tempat kesalahannya bersembunyi — kondisi balapan akses data antara pemindahan data WAL yang menyalin halaman WAL dan transaksi yang menyimpan halaman baru.

Ketika waktunya jatuh di saat yang salah, pemindahan data WAL menyimpulkan sudah menyalin semua halaman, padahal satu atau lebih halaman yang sudah dinyatakan tersimpan tidak pernah benar-benar tersalin. Transaksinya melaporkan sukses ke pemanggil. Datanya hilang.

Memuat diagram…

Pemicunya spesifik: basis data mode WAL dengan dua koneksi atau lebih — thread atau proses terpisah. Kedua koneksi itu menjalankan penulisan atau pemindahan data WAL pada berkas yang sama di saat yang sama persis. Dokumentasi SQLite sendiri mengonfirmasi ini adalah kondisi balapan akses data dengan batas waktu eksekusi yang sangat ketat. Kalau kombinasi itu tidak pernah terjadi, kesalahan ini tidak akan pernah muncul. Kalau sering terjadi, seperti di skala Tailscale, hasilnya sembilan belas insiden dalam enam bulan.

Kenapa enam belas tahun pemakaian produksi tidak menemukannya

Kesalahan ini kemungkinan ada sejak versi 3.7.0, rilis Juli 2010, sampai 3.51.2, rilis Januari 2026. Diperbaiki di 3.51.3 dan versi sesudahnya, dengan perbaikan yang juga dibawa ke cabang lama melalui 3.44.6 dan 3.50.7, menurut dokumentasi resmi SQLite. Enam belas tahun, di basis data yang mungkin paling banyak dipakai di dunia, dan tidak ada yang menemukannya.

Kasus ini memperlihatkan batas pengalaman produksi sebagai bukti kematangan teknologi. Mayoritas besar penerapan SQLite pakai satu koneksi per proses, pemindahan data WAL mengikuti jadwal bawaan SQLite, jauh dari race window.

Pola pemakaian Tailscale berbeda: pemindahan data WAL agresif dan manual, lintas banyak koneksi konkuren sekaligus, demi memeras kapasitas pemrosesan lebih tinggi dari SQLite dibanding asumsi nilai bawaannya. Pengalaman pengguna lain belum tentu mencakup susunan proses dan waktu yang digunakan Tailscale.

Antithesis, perusahaan deterministic pengujian, berhasil mereproduksi kesalahan ini dalam sekitar lima belas menit, memakai beban kerja generik penulisan dan pemindahan data WAL konkuren. Ini kesalahan yang bahkan pengembang SQLite sendiri tidak pernah berhasil memicu secara organik. Kombinasi beban ini dapat terbentuk dari operasi biasa, tetapi jarang terpicu tanpa pengujian yang sengaja menyusunnya.

Pelajaran sebenarnya bukan soal SQLite

Saya tetap menyarankan teknologi matang yang sudah dipahami tim. Namun, pengalaman panjang membuktikan keandalannya pada pola pemakaian tertentu. Pola yang berbeda tetap memerlukan pengujian tersendiri.

Reputasi battle-tested sebuah dependensi lahir dari akumulasi pemakaian orang lain, dan reputasi itu berlaku hanya sejauh pemakaiannya tumpang tindih dengan pemakaian mereka. Begitu keluar dari asumsi bawaan, sebuah tim diam-diam sudah keluar dari pengujian kolektif yang membuat pilihan itu terasa aman.

Saya paling sering melihat ini di sistem embedded dan edge, tempat tim mendorong satu alat yang sudah familiar ke peran yang tidak pernah diantisipasi nilai bawaannya. Konkurensi lebih berat, waktu eksekusi lebih ketat, jalur pemulihan yang tidak biasa — semua penyimpangan dari asumsi awal. Penyimpangan tersebut dapat terlewat saat peninjauan kode karena tampak seperti pemakaian biasa dari alat yang sudah dipercaya.

Korupsi diam adalah mode kegagalan terburuk

Ini yang harusnya lebih mengkhawatirkan dibanding kesalahannya sendiri: tidak ada satu pun yang berisik. Tidak ada berhenti, tidak ada exception, tidak ada baris log. Transaksi penulisan melaporkan sukses, pemindahan data WAL selesai tanpa keluhan, dan satu-satunya gejala adalah data yang seharusnya ada tapi tidak ada. Gejala itu baru ditemukan belakangan, secara tidak langsung, oleh seseorang yang menyadari satu baris hilang.

Penulisan yang ditolak atau proses yang berhenti karena halaman rusak memberi sinyal langsung untuk ditangani. Kehilangan data setelah laporan berhasil lebih sulit ditemukan karena tidak memunculkan peringatan saat kejadian.

Untuk penerapan edge tanpa DBA yang memantau dasbor, jarak antara "kelihatannya baik-baik saja" dan "benar-benar baik-baik saja" bisa berjalan berbulan-bulan. Persis seperti yang terjadi selama enam bulan di Tailscale, perusahaan yang justru sedang mencarinya dengan sengaja.

Batas penggunaan SQLite pada perangkat lapangan

Semua ini tidak membuat SQLite jadi pilihan buruk untuk penerapan IoT, embedded, atau edge. SQLite tetap pilihan bawaan yang tepat untuk beban kerja lokal, single-process, single-writer, yang mencakup mayoritas penerapan.

Tapi batas di sekitar asumsi mode WAL sekarang lebih tegas. Dokumentasi SQLite menyatakan mode WAL tidak bekerja lewat sistem berkas jaringan, karena butuh semua proses yang mengakses basis data berbagi shared memory di host yang sama. File basis data di storage jaringan atau shared sudah salah arsitektur sejak sebelum kesalahan ini ada.

Risiko yang lebih sempit adalah penulisan konkuren multi-koneksi dengan kontrol pemindahan data WAL manual — persis pola Tailscale. Disiplin single-writer, satu proses, satu koneksi, jadwal pemindahan data WAL bawaan, menjaga sistem tetap aman dari race condition yang dibutuhkan kesalahan ini. Banyak proses yang menghantam berkas WAL yang sama, dengan interval pemindahan data WAL yang diatur manual, adalah penyimpangan yang layak diaudit sekarang. Periksa setiap rilis terdampak yang belum menerima perbaikan, termasuk cabang lama tanpa pembaruan tersebut.

Deteksi korupsi murah yang harusnya sudah dimiliki setiap tim

Pemeriksaan integritas dan pencocokan data membantu menemukan kerusakan tanpa harus mereproduksi kondisi balapan tersebut. Terapkan kebiasaan berikut:

  • Jalankan PRAGMA integrity_check (atau versi lebih cepat, quick_check) secara terjadwal. Perintah ini melakukan pemindaian konsistensi level rendah secara penuh — entri yang salah format, halaman hilang, indeks rusak. Ia mengembalikan satu baris ok kalau tidak ada masalah. Pemeriksaan ini membantu menemukan kerusakan struktur, tetapi tidak menggantikan pencocokan dengan sumber sekunder untuk baris yang hilang.
  • Verifikasi pencadangan dengan cara memulihkannya. Pencadangan yang belum pernah dipulihkan adalah harapan, bukan rencana. Saya bahas prinsip yang sama di verifikasi pencadangan Postgres; prinsipnya berlaku persis sama untuk berkas SQLite yang tersimpan di perangkat embedded.
  • Pantau selisih data, bukan hanya ketersediaan. Bandingkan jumlah baris atau nilai pemeriksaan integritas dengan sumber sekunder untuk membantu menemukan data yang hilang sebelum ada laporan pengguna.

Jadwalkan pemeriksaan integritas dengan perhatian yang sama seperti waktu respons dan tingkat galat. Gunakan alat yang sudah tersedia sebelum menambah sistem baru.

Pertanyaan untuk penanggung jawab pengembangan

Dependensi yang paling keras diaudit biasanya yang paling baru. Yang tidak pernah diperiksa ulang justru yang sudah terpasang bertahun-tahun, dibiarkan begitu saja karena "boring" dan "selalu bekerja". Itu terbalik: kadar audit harusnya sebanding dengan seberapa jauh pemakaiannya menyimpang dari bawaan sebuah dependensi, bukan sebanding dengan seberapa baru dependensi itu.

Ketika saya mengambil alih sebuah sistem untuk klien — latihan yang sama seperti yang saya jelaskan di menstabilkan basis kode warisan — pertanyaan pertama saya selalu sama. Bagian battle-tested mana yang dipakai dengan cara yang tidak biasa? Pengalaman produksi paling relevan ketika pola pemakaiannya serupa. Catat penyimpangan tersebut dan uji dampaknya sebelum menganggap keandalan masa lalu berlaku pada rancangan baru.

Artikel Terkait

Perlu bantuan menerapkan solusi ini?

Pembenahan & Modernisasi Sistem Lama

Tinjau sistem yang sudah berjalan, tentukan masalah paling mendesak, lalu perbaiki bertahap dengan rencana pemindahan data dan serah terima.

Lihat layanan terkait

Ceritakan kendala operasional Anda agar kita dapat membahas langkah yang sesuai.

Bagikan artikel
Diskusi Awal Gratis