Backup Database Aktif Itu Nggak Boleh Sekadar Cp
Menyalin file SQLite yang aktif bukan backup. Pelajaran dari script backup ringan: hot backup, tar atomik terverifikasi, dan prune berlapis.
Ringkasan
Script ini menyalin SQLite aktif lewat perintah .backup, bukan cp mentah, supaya snapshot tetap konsisten di mode WAL. Arsip ditulis ke nama sementara, diverifikasi dengan tar -tzf, baru direname atomik, jadi tidak ada arsip setengah jadi. Penghapusan lama baru jalan setelah verify, dengan lantai minimal tiga arsip terbaru plus flock biar cron nggak dobel.
Folder ~/.hermes/backups isinya sekarang konsisten: arsip tar.gz sekitar 90MB tiap dua hari, dan yang lama terhapus otomatis. Kelihatan sepele, tapi script di baliknya, hermes-light-backup.sh, lahir dari satu kekeliruan klasik yang hampir saya lakukan: menganggap menyalin file database SQLite yang aktif sama dengan membackupnya. Dua hal itu beda jauh, dan bedanya baru kelihatan pas paling butuh.
Kenapa Cp Mentah Bisa Jahat
File SQLite yang dipakai aktif itu bukan file statis. Di mode WAL, penulisan masuk dulu ke file wal di sampingnya, jadi isi file utama plus wal adalah satu kesatuan yang terus bergerak. Menyalin keduanya dengan cp di momen yang salah bisa menghasilkan salinan yang tercampur antara keadaan lama dan baru. SQLite punya jawaban resminya: Online Backup API, yang menyalin bertahap dan cuma mengunci sumber sesaat saat dibaca [5]. Hasil akhirnya snapshot yang identik bit-per-bit dengan database saat penyalinan dimulai [5], dan meski ada writer lain nulis di tengah jalan, hasilnya tetap snapshot yang konsisten [5].
Script memakai bentuk paling gampang dari API itu: sqlite3 file.db .backup target.db. Kalau binary sqlite3 nggak ada, baru jatuh ke cp, dengan sadar bahwa fallback itu kualitasnya lebih rendah. Trade-off ini saya terima karena fallback cuma jalan di mesin yang nggak sempat pasang sqlite3.
Verify Dulu, Baru Ganti Nama
Arsip dibuat ke nama sementara yang diakhiri .tmp, bukan langsung ke nama final. Baru setelah tar -tzf berhasil membaca seluruh isi arsip [7], file itu di-rename ke nama final. Urutan kecil ini yang bikin folder backup nggak pernah berisi arsip setengah jadi: rename atomik itu satu-satunya momen nama final muncul.
Pengunci prosesnya pakai flock di file descriptor terpisah dengan flag non-blocking [6]. flock itu lock advisory, artinya proses lain secara teknis bebas mengabaikannya [6], tapi untuk kebutuhan ini cukup: satu-satunya proses yang menghormati lock ya sesama instansi script ini, dan justru itu yang mau dicegah tumpang tindih. Cron yang telat nggak jalan dobel; dia skip, catat satu baris SKIP locked ke log, selesai.
Ada satu detail yang saya suka dari script ini: dia nulis log ke file terpisah, bukan stdout, sesuai kontrak cron-nya yang diam. Log-nya juga dipangkas, cukup 300 baris terakhir kalau sudah lewat 500, supaya alat pemantau nggak pelan-pelan jadi monster disk sendiri.
Prune Yang Punya Rem
Bagian hapus otomatis justru bagian yang paling saya pertimbangkan. Kebijakannya berlapis. Backup disimpan 14 hari sejak modify time. Di bawah itu nggak disentuh. Tapi ada lantai kedua: minimal 3 arsip terbaru tetep diselamatkan apa pun kondisi umurnya. Jumlah yang boleh dihapus dibatasi juga, nggak mungkin proses hapus bikin total arsip turun di bawah lantai itu. Dan semuanya cuma dieksekusi setelah arsip baru lolos verify.
Kenapa ribet? Karena mode gagal script backup itu halus. Jadwal bisa meleset berhari-hari karena server mati atau partisi penuh. Script yang prune tanpa verify bakal terus menghapus arsip tua selagi nggak produce arsip baru yang valid. Akhirnya pas dibutuhkan, isi folder itu kosong atau isinya sampah. Backup tanpa verifikasi plus prune tanpa lantai itu resep kehilangan data yang manis di atas kertas.
Jadi urutan operasinya kaku: kunci proses, susun staging, salin database dengan cara yang benar, bungkus jadi tar.gz sementara, verify, rename, catat, baru pikirkan menghapus. Setiap langkah sengaja membosankan. Justru karena membosankan itulah script ini saya percaya buat jalan tiap dua hari tanpa saya awasi.