Backup yang Kukira Jalan Tiap Malam, Ternyata Kosong Sebelas Minggu

Rabu pagi, aku menjalankan skrip migrasi kecil untuk menambah satu kolom di tabel entries—hal yang sudah kulakukan puluhan kali di proyek habit-tracker sampinganku. Skrip itu juga punya langkah TRUNCATE TABLE entries CASCADE di awal, bagian dari fixture seed yang biasa kupakai buat bersih-bersih data dummy di local. Masalahnya, terminal yang kupakai masih terhubung ke DATABASE_URL production dari sesi debugging semalam, dan aku lupa reload environment sebelum menjalankan skrip. Satu perintah, dan tabel utama yang menyimpan seluruh catatan pengguna production langsung kosong.

Panik pertama reda begitu aku ingat ada backup otomatis tiap malam. Aku buka server kecil yang menjalankan cron backup itu, ambil dump semalam, jalankan gunzip—dan filenya cuma 214 byte. Bukan rusak sebagian, tapi nyaris kosong: cuma header pg_dump, tanpa satu baris data pun. Aku cek dump tiga hari sebelumnya. Sama. Seminggu sebelumnya. Sama juga. Baru di dump sekitar sebelas minggu lalu aku menemukan file yang ukurannya masuk akal.

Migrasi yang Kukira Aman di Local

Yang bikin insiden ini menyakitkan bukan cuma soal data hilang, tapi soal betapa mudahnya itu terjadi. Aku sering berpindah antara terminal local dan terminal yang sengaja kubuka untuk debugging langsung ke production—biasanya cuma untuk SELECT cepat, bukan untuk menjalankan apa pun yang mengubah data. Malam sebelumnya aku memang lagi mengecek satu baris data pengguna yang komplain, lalu menutup laptop tanpa menutup sesi terminal itu. Paginya, aku buka tab yang sama, ketik npm run migrate, dan tidak terpikir sama sekali untuk mengecek echo $DATABASE_URL dulu—karena di kepalaku, migrasi selalu berarti local.

Skrip migrasiku sendiri tidak punya pengaman apa pun terhadap kondisi ini. Tidak ada prompt konfirmasi, tidak ada pengecekan "apakah target ini production", tidak ada perbedaan visual di prompt terminal antara sesi local dan production. Semua terlihat identik—warna sama, font sama, cuma nama environment variable yang berbeda dan aku tidak pernah repot mengeceknya karena selama ini tidak pernah salah.

Backup yang Selalu "Berhasil" di Log, Tapi Tidak Pernah Benar-Benar Jalan

Setelah kepanikan awal soal migrasi, kepanikan kedua yang jauh lebih besar muncul: kenapa backup sebelas minggu terakhir kosong? Aku telusuri skrip cron-nya, dan akar masalahnya konyol sekali. Sekitar sebelas minggu lalu aku pindah hosting database dan merotasi password-nya sebagai bagian dari setup baru. Aku update DATABASE_URL di aplikasi utama, tapi server backup itu VPS terpisah yang menyimpan kredensial lewat file .pgpass—dan file itu tidak pernah kusentuh lagi setelah migrasi hosting selesai.

Begitu password berubah, tiap kali cron menjalankan pg_dump, autentikasinya gagal. Tapi skrip backupku ditulis dengan asumsi naif: dia menulis output pg_dump langsung ke file lewat redirect, dan pg_dump yang gagal auth tetap menulis file—cuma isinya pesan error singkat plus header kosong, bukan data. Skrip itu tidak pernah mengecek exit code pg_dump, tidak pernah mengecek ukuran file hasilnya, cuma mengecek "apakah file berhasil dibuat". File selalu berhasil dibuat. Jadi log cron selalu mencatat "backup completed", dan email notifikasi cron yang seharusnya mengabari kalau ada error justru sudah kumatikan bulan-bulan sebelumnya karena kebanjiran alert palsu soal disk space yang nyaris penuh tapi sebenarnya aman.

MacBook dengan baris kode di layar di atas meja kerja
MacBook dengan baris kode di layar di atas meja kerja

Yang Akhirnya Bisa Diselamatkan, dan yang Tidak

Untungnya proyek ini punya tabel audit log terpisah yang mencatat setiap perubahan penting sebagai insert-only—dibuat awalnya untuk fitur riwayat aktivitas pengguna, bukan untuk disaster recovery. Aku habiskan sekitar empat belas jam menulis skrip yang membaca ulang seluruh baris audit log sejak dump valid terakhir, lalu merekonstruksi ulang isi tabel entries dari sana baris per baris. Sebagian besar data—sekitar sembilan dari sepuluh entri—berhasil kembali dengan cara ini.

Layar komputer menampilkan baris-baris kode
Layar komputer menampilkan baris-baris kode

Yang tidak bisa diselamatkan adalah data yang memang tidak pernah masuk audit log: preferensi tampilan pengguna, status onboarding yang sudah selesai, beberapa flag pengaturan kecil yang kuanggap terlalu remeh untuk dicatat riwayatnya waktu mendesain sistem itu. Bagi pengguna, efeknya kelihatan seperti akun mereka "lupa" beberapa setting kecil yang pernah mereka atur—kecil, tapi cukup untuk membuat beberapa orang menulis email bertanya kenapa.

Takeaway

Backup yang tidak pernah dicoba dipulihkan bukan backup—dia cuma file yang kebetulan ada di disk. Cron yang melaporkan "sukses" hanya karena berhasil membuat file, tanpa mengecek exit code proses di dalamnya atau ukuran hasil akhirnya, akan tetap melaporkan sukses bahkan saat isinya kosong total, dan itu bisa bertahan diam-diam selama berminggu-minggu kalau tidak ada yang benar-benar membukanya. Sekarang aku menjadwalkan uji restore penuh sebulan sekali ke database sementara, memasang ambang minimum ukuran file yang memicu alert kalau backup tiba-tiba jauh lebih kecil dari biasanya, dan yang paling sederhana tapi paling kupatuhi sekarang: sesi terminal ke production selalu kututup begitu selesai dipakai, tidak pernah kubiarkan menganggur menunggu perintah berikutnya.

---

Sumber gambar: imgix di Unsplash Sumber gambar: Christopher Gower di Unsplash Sumber gambar: Chris Ried di Unsplash

The conversation 0

No comments yet. Start the conversation.

Comments are reviewed before appearing. Links are masked.