Setiap malam job backup berjalan dan laporannya menyala hijau — sampai suatu hari restore benar-benar dibutuhkan dan ternyata hasil backup tiga bulan terakhir tidak bisa dipakai. Skenario itu bukan khayalan: kredensial yang kedaluwarsa, storage yang penuh diam-diam, database yang terluput dari scope, atau ketidakcocokan versi tool — semuanya membuat backup tampak “berhasil” setiap malam tanpa pernah benar-benar pulih. Karena itu ada satu prinsip yang dipegang luas di industri: backup yang tidak pernah diuji restore-nya bukan backup. Chapter ini menyusun prosedur uji berkala yang lengkap — lalu menjalankan lapisan verifikasinya secara nyata di server latihan.
Peta materi Chapter ini:
- Sumber kegagalan senyap pada backup dan alasan hanya restore yang bisa membuktikannya.
- Prosedur uji lima langkah, dari restore ke environment terpisah sampai smoke test aplikasi.
- Checklist verifikasi berlapis dan jadwal uji yang realistis.
Sumber Kegagalan Senyap #
Job backup adalah otomasi, dan otomasi gagal dengan cara yang paling senyap. Tabel berikut merangkum pola kegagalan yang paling sering ditemukan di lapangan:
| Pola kegagalan | Gejala | Terdeteksi oleh |
|---|---|---|
| Kredensial kedaluwarsa | Job gagal, tetapi notifikasi masuk folder spam | Uji restore + pemantauan job |
| Storage penuh | Backup berhenti setengah jalan tanpa galat tegas | Pemeriksaan ukuran hasil + uji restore |
| Scope terluput | Database baru tidak masuk daftar backup | Perbandingan show dbs vs isi backup |
| Ketidakcocokan versi | mongorestore baru menolak dump lawas (atau sebaliknya) | Satu-satunya penangkap: uji restore |
| Berkas terpotong saat transfer | Checksum tidak cocok | Uji restore + verifikasi checksum |
Perhatikan kolom terakhir: hampir semua pola berujung pada satu jawaran yang sama. Laporan “backup selesai” hanya membuktikan bahwa perintah selesai — bukan bahwa data bisa kembali.
Prosedur Uji Lima Langkah #
Uji restore yang benar meniru skenario pemulihan sungguhan, bukan sekadar membuka berkas backup:
- Restore ke environment terpisah — server atau database uji tersendiri, tidak pernah menimpa produksi; di sinilah kecocokan versi tool dan server ikut teruji.
- Verifikasi struktur — semua collection hadir, jumlah document per collection cocok, daftar index identik.
- Verifikasi isi — query sampel pada document kunci, plus agregat pembanding (total, rata-rata) terhadap baseline produksi.
- Smoke test aplikasi — arahkan aplikasi versi uji ke data hasil restore, lalu jalankan alur inti: login, baca data, satu transaksi.
- Catat hasilnya — tanggal, durasi restore (ini RTO aktual kita!), temuan, dan tindak lanjut; catatan ini menjadi bukti audit sekaligus bahan perbaikan.
Langkah keempat sering dilewatkan padahal paling jujur: aplikasi bisa gagal membaca data yang secara jumlah “sudah cocok” — misalnya karena tipe field berubah atau validator menolak document hasil restore.
Praktik: Restore ke Environment Uji dan Verifikasi #
Mari kita jalankan lapisan-lapisan verifikasi di server latihan. Pertama, siapkan “produksi” — database latihan_backup lengkap dengan index:
use latihan_backup
db.dropDatabase()
db.buku.insertMany([
{ _id: 1, judul: "Laut Bercerita", harga: 98000, stok: 12 },
{ _id: 2, judul: "Bumi", harga: 89000, stok: 20 },
{ _id: 3, judul: "Atomic Habits", harga: 115000, stok: 8 }
])
db.penjualan.insertMany([
{ _id: 101, bukuId: 1, jumlah: 1 },
{ _id: 102, bukuId: 2, jumlah: 2 },
{ _id: 103, bukuId: 1, jumlah: 1 }
])
db.buku.createIndex({ judul: 1 })Output yang Dihasilkan:
switched to db latihan_backup
{ ok: 1, dropped: 'latihan_backup' }
{ acknowledged: true, insertedIds: { '0': 1, '1': 2, '2': 3 } }
{ acknowledged: true, insertedIds: { '0': 101, '1': 102, '2': 103 } }
judul_1Kedua, jalankan “restore” ke environment uji. Pada praktik sungguhan, mongorestore yang memasukkan document dan membangun index dari berkas .metadata.json; di lingkungan latihan kita simulasikan hasil akhirnya di database latihan_uji — collection yang sama, isi yang sama, index yang sama:
use latihan_uji
db.dropDatabase()
db.buku.insertMany([
{ _id: 1, judul: "Laut Bercerita", harga: 98000, stok: 12 },
{ _id: 2, judul: "Bumi", harga: 89000, stok: 20 },
{ _id: 3, judul: "Atomic Habits", harga: 115000, stok: 8 }
])
db.penjualan.insertMany([
{ _id: 101, bukuId: 1, jumlah: 1 },
{ _id: 102, bukuId: 2, jumlah: 2 },
{ _id: 103, bukuId: 1, jumlah: 1 }
])
db.buku.createIndex({ judul: 1 })Output yang Dihasilkan:
switched to db latihan_uji
{ ok: 1, dropped: 'latihan_uji' }
{ acknowledged: true, insertedIds: { '0': 1, '1': 2, '2': 3 } }
{ acknowledged: true, insertedIds: { '0': 101, '1': 102, '2': 103 } }
judul_1Ketiga — inti chapter ini — verifikasi berlapis secara otomatis. Setiap baris membandingkan environment uji dengan sumbernya, lalu mencatat LULUS atau GAGAL:
const produksi = db.getSiblingDB("latihan_backup")
const uji = db.getSiblingDB("latihan_uji")
const cek = [
["jumlah document buku", produksi.buku.countDocuments({}), uji.buku.countDocuments({})],
["jumlah document penjualan", produksi.penjualan.countDocuments({}), uji.penjualan.countDocuments({})],
["total stok",
produksi.buku.aggregate([{ $group: { _id: null, t: { $sum: "$stok" } } }]).toArray()[0].t,
uji.buku.aggregate([{ $group: { _id: null, t: { $sum: "$stok" } } }]).toArray()[0].t],
["jumlah index buku", produksi.buku.getIndexes().length, uji.buku.getIndexes().length]
]
cek.forEach(([nama, asal, hasil]) =>
print(nama + ": " + (asal === hasil ? "LULUS" : "GAGAL") + " (asal " + asal + ", hasil " + hasil + ")"))Output yang Dihasilkan:
jumlah document buku: LULUS (asal 3, hasil 3)
jumlah document penjualan: LULUS (asal 3, hasil 3)
total stok: LULUS (asal 40, hasil 40)
jumlah index buku: LULUS (asal 2, hasil 2)Empat lapis lolos. Terakhir, smoke test ala aplikasi: satu query yang benar-benar dipakai aplikasi — pencarian buku berdasar judul — dan kita lihat hasilnya utuh:
uji.buku.find({ judul: "Bumi" }, { _id: 0, judul: 1, harga: 1, stok: 1 })Output yang Dihasilkan:
[ { judul: 'Bumi', harga: 89000, stok: 20 } ]Pola skrip cek di atas layak diadopsi apa adanya ke pipeline uji Anda: ganti database-nya, tambahkan baris untuk setiap angka yang dianggap kritis oleh bisnis (jumlah order hari ini, total saldo, dsb.), dan jalankan otomatis setiap kali restore uji selesai.
Checklist dan Jadwal #
Checklist lengkap untuk satu sesi uji restore:
| Lapis | Yang diperiksa | Contoh alat/perintah | Kriteria lulus |
|---|---|---|---|
| Struktur | Collection dan index hadir | getCollectionNames(), getIndexes() | Identik dengan sumber |
| Jumlah | Document per collection | countDocuments({}) | Cocok dengan baseline |
| Isi | Document kunci dan tipe field | find() + $type | Field utuh, tipe benar |
| Agregat | Total field kritis | aggregate([{$group: ...}]) | Cocok dengan baseline |
| Aplikasi | Alur inti aplikasi | Smoke test / uji end-to-end | Fitur inti berjalan |
| Waktu | Durasi restore + verifikasi | Catat waktu mulai-selesai | Dalam anggaran RTO |
Dan jadwalnya menyesuaikan risiko — makin rapat untuk sistem makin kritis:
| Frekuensi | Cakupan | Pelaksana |
|---|---|---|
| Mingguan | Restore satu collection + verifikasi jumlah | Otomatis (script) |
| Bulanan | Restore penuh ke staging + seluruh checklist + smoke test | Engineer on-call |
| Saat perubahan besar | Full drill: upgrade versi, pindah region, ganti strategi backup | Seluruh tim |
| Pasca insiden | Review: bagian restore yang lambat atau gagal | Retro tim |
Dua kebiasaan penutup yang membedakan tim siap dari yang tidak: hasil setiap uji didokumentasikan (tanggal, durasi, temuan, tindak lanjut) sebagai jejak audit, dan runbook restore disimpan di tempat yang bisa dijangkau seluruh tim on-call — bukan hanya di kepala satu orang.
Rangkuman #
- Backup gagal secara senyap: kredensial kedaluwarsa, storage penuh, scope terluput, versi tak cocok — dan hampir semuanya hanya bisa ditangkap oleh uji restore sungguhan.
- Prosedur lima langkah: restore ke environment terpisah, verifikasi struktur, verifikasi isi, smoke test aplikasi, catat hasil dan RTO aktual.
- Verifikasi berlapis bisa diautomasi sepenuhnya — pola skrip
cekkita membandingkan jumlah, agregat, dan index terhadap sumber, lalu mencetak LULUS/GAGAL per baris. - Jadwal realistis: mingguan untuk spot check otomatis, bulanan untuk drill penuh, dan selalu setelah perubahan besar atau insiden.