Backup & Restore Toolkit
On this page
- Sekilas
- Yang dikerjakannya
- A. Mem-backup tiga jenis data, masing-masing dengan caranya
- B. Membangun titik pemulihan yang hanya berongkos perubahannya
- C. Me-restore ke titik mana pun
- D. Me-restore object storage dan telemetry seperti semula
- E. Menyatakan seberapa parah kegagalannya
- F. Menjaga retensi agar tak berubah jadi penghapusan
- Cara saya memakainya
- Keputusan rancangan
- Spesifikasi
- Terkait
- Istilah
Backup & Restore Toolkit adalah tiga perintah yang mem-backup stack Docker yang sedang berjalan, me-restore-nya ke titik waktu mana pun, dan menjaga backup-backup itu agar tak tumbuh sampai disk penuh. Ia mencakup tiga jenis data yang biasa disimpan service saya — database PostgreSQL, file di object storage MinIO, dan telemetry di Elasticsearch — beserta konfigurasi yang membuat semuanya bisa dipakai, dan tak pernah mematikan satu service pun untuk melakukannya.
Backup-nya juga menjadi sumber setiap environment staging yang saya bangun, jadi ia di-restore dan diperiksa setiap rilis, bukan cuma pada hari ketika ada yang rusak. Backup yang tak pernah di-restore hanyalah harapan; backup yang di-restore setiap minggu adalah fakta.
Sekilas
| Tools | backup-production, restore-from-backup, remove-backup |
|---|---|
| Bentuk | Script command-line; backup dan pembersihan berdiri sendiri, dan hanya restore yang memuat library pembantu bersama |
| Bahasa | Bash, yang mengendalikan Docker, pg_basebackup dan pg_combinebackup, MinIO client, dan rsync |
| Status | Stabil, berjalan di production |
| Kode sumber | Privat |
Yang dikerjakannya
A. Mem-backup tiga jenis data, masing-masing dengan caranya
Tak ada satu teknik backup yang cocok untuk ketiganya. PostgreSQL di-backup dengan pg_basebackup, berupa salinan penuh lalu incremental di tingkat blok. MinIO di-mirror objek demi objek. Elasticsearch di-backup lewat snapshot API miliknya sendiri, karena menyalin folder data mesin pencari yang sedang berjalan menghasilkan salinan yang belum tentu bisa dibuka. Ketiganya berjalan selagi service tetap melayani. Konfigurasi tiap modul juga ikut disimpan, karena pemulihan penuh butuh lebih dari sekadar datanya. Database yang sebenarnya bisa dibangun ulang oleh seeder pun tetap di-backup, supaya pemulihan tak pernah bergantung pada seeder maupun data upstream yang dibacanya.
B. Membangun titik pemulihan yang hanya berongkos perubahannya
Setiap titik pemulihan object storage tampak sebagai salinan lengkap, padahal file yang tak berubah hanyalah hard link ke titik sebelumnya, jadi hanya perubahannya yang memakan ruang. Pada satu pengukuran, tiga titik yang masing-masing berisi 101 MB hanya memakai 102 MB di disk, bukan 303 MB. File yang dihapus dari storage lenyap dari titik terbaru tapi tetap ada di titik-titik lama, dan justru itulah yang membuat kembali ke belakang berguna. Penghematan itu bergantung pada satu salinan kerja bersama: ketika tiap titik mengunduh ke folder staging miliknya sendiri, setiap objek tiba dengan inode baru, hard link tak pernah terbentuk, dan tiga titik memakan 303 MB. MinIO client juga dijalankan sebagai user dan group saya sendiri, karena file yang dulu ditulisnya sebagai root membuat rotasi gagal dengan permission denied.
C. Me-restore ke titik mana pun
Restore membangun ulang setiap data directory PostgreSQL dari backup penuh beserta rantai incremental-nya, sampai titik terakhir atau sampai titik yang saya sebut. Versi database yang dipakai untuk membangun ulang satu backup dibaca dari backup itu sendiri, jadi backup yang diambil sebelum stack di-upgrade tetap bisa dibangun ulang sesudahnya. Database tujuan yang masih berjalan dimatikan oleh restore itu sendiri, dan prosesnya baru lanjut setelah database benar-benar mati. Direktori hasil bangun ulang diberi recovery signal dan restore command, lalu keduanya diperiksa lagi: tanpa signal itu, entrypoint image akan menginisialisasi database baru di atas hasil restore dan meninggalkan environment kosong tanpa suara. Prefix tujuan bawaannya milik staging, jadi production tak terjangkau kecuali saya memintanya, dan container berjalan sebagai user saya serta mengambil alih folder milik root tanpa hak lebih tinggi, sambil mencetak perintah persisnya bila tak berhasil.
D. Me-restore object storage dan telemetry seperti semula
Object storage di-mirror balik dari titik yang dipilih dengan penghapusan dinyalakan, karena tanpanya hasilnya adalah titik itu digabung dengan isi yang sudah ada, bukan titik itu sendiri — perbedaan yang terbukti saat menguji restore ke titik yang lebih lama. Policy akses bucket publik dipasang lagi dari file berversi, karena mirror tak membawa policy. Untuk telemetry, data stream yang akan bertabrakan dihapus lebih dulu, karena data stream tak bisa sekadar ditutup, baru snapshot-nya di-restore. Sesudahnya user dan role telemetry dibuat ulang, lalu collector dan dashboard di-restart, supaya backup lama yang tak menyimpan security index tetap kembali dalam keadaan bisa ditulisi.
E. Menyatakan seberapa parah kegagalannya
Perintah backup tak sekadar melaporkan berhasil atau gagal. Ia membedakan backup yang tak bisa dipakai dari backup yang hanya kehilangan telemetry-nya, jadi job terjadwal bisa langsung bereaksi pada yang pertama dan santai menangani yang kedua. Backup yang tak menangkap satu database pun dianggap error, jadi stack production yang mati tak pernah tampak sebagai malam yang sukses di cron. Store yang container-nya tak berjalan dilewati dengan catatan, bukan digagalkan.
F. Menjaga retensi agar tak berubah jadi penghapusan
Backup penuh yang lama dihapus sesudah backup baru dibuat, dan perintah pembersihan juga bisa dijalankan dengan jadwalnya sendiri. Perintah pembersihan menolak menyisakan kurang dari satu backup, tak pernah menyentuh repository snapshot bersama maupun salinan kerja yang dibutuhkan mirror berikutnya, dan melaporkan ruang yang benar-benar dibebaskan, dengan menghitung file ber-hard link satu kali saja. Pembersihan punya dry run dan daftar yang menampilkan ukuran tiap backup penuh beserta titik-titiknya, penanda backup terbaru, dan snapshot telemetry yatim. Snapshot telemetry milik satu backup penuh dihapus lewat API mesin pencari sebelum folder-nya, karena nama snapshot itu tersimpan di folder tersebut — sembilan snapshot yatim sempat menumpuk sebelum urutan ini diperbaiki — dan penunjuk ke backup terbaru diperbaiki atau dihapus bila tak lagi menunjuk ke backup mana pun.
Cara saya memakainya
| Exit code | Arti | Yang dilakukan |
|---|---|---|
| 0 | setiap store yang berjalan sudah ter-backup; store yang mati dilewati dengan catatan | baca catatannya |
| 1 | PostgreSQL atau MinIO gagal; backup itu tak sah untuk restore | tangani sekarang |
| 2 | hanya snapshot Elasticsearch yang gagal, jadi dokumen dan file aman | periksa log; telemetry bisa menyusul |
Di production ia dijalankan cron. Jadwalnya dibentuk oleh nilai datanya dan kapasitas disknya. Backup penuh mengikuti hari dalam minggu, bukan setiap hari ketujuh, karena langkah tujuh hari dimulai ulang tiap bulan dan menyisakan celah dua hari di pergantian bulan. Titik pemulihan terikat pada interval, yang saya pilih dengan menimbang nilai datanya terhadap ongkos menjalankannya lebih sering. Jumlah yang disimpan perintah pembersihan harus sama dengan milik backup.
| Backup penuh | mingguan, menyimpan dua |
|---|---|
| Backup incremental | beberapa kali sehari |
| Recovery point objective | perubahan beberapa jam terakhir |
| Seberapa jauh ke belakang | sekitar 14 hari, dua backup penuh mingguan |
| Ukuran | sekitar 52 GB per backup penuh; rotasi berjalan sesudah backup penuh yang baru masuk, jadi puncaknya menyimpan tiga, sekitar 156 GB |
Keputusan rancangan
Tanpa downtime, dan itu saya ukur, bukan saya anggap. Setiap teknik yang dipakai toolkit ini bekerja pada service yang hidup: base backup mengambil snapshot di awal dan menangkap tulisan sesudahnya di write-ahead log, mirror konsisten per objek, dan snapshot adalah cara resmi yang didukung mesin pencarinya. Saya mengujinya dengan menulis sekitar dua juta baris selama backup incremental berjalan; backup-nya berhasil, dan restore menghasilkan jumlah baris yang persis sama. Sepanjang backup, service menjawab tanpa satu error pun. Hanya restore yang menghentikan database, dan di production itu memakan sekitar tujuh detik.
Exit code menyatakan nilai dari apa yang hilang. Dokumen dan tanda tangan tinggal di PostgreSQL dan MinIO; Elasticsearch menyimpan telemetry. Bila kedua jenis kegagalan memicu alarm yang sama, satu snapshot telemetry yang gagal akan mengajari siapa pun pembaca alarm untuk mengabaikannya — dan alarm berikutnya yang diabaikan bisa jadi justru yang penting. Memisahkan keduanya membuat jadwal bisa memperlakukan kegagalan yang lebih ringan secara eksplisit, tanpa kebiasaan menambahkan || true yang akan menyembunyikan kegagalan yang serius. Kebiasaan itulah yang dulu dimiliki script ini: setiap kegagalan ditelan dan run diakhiri dengan sukses, sehingga snapshot telemetry yang gagal mungkin sudah berbulan-bulan dilaporkan baik-baik saja ke cron.
Ia berjaga terhadap kegagalan yang tampak seperti sukses. Dua kegagalan pernah terjadi di server sungguhan, dan tak satu pun menghasilkan error saat backup dibuat; keduanya baru muncul saat restore. Backup incremental menyimpan rujukan cache ke basisnya, yang bisa bertahan lebih lama daripada basis itu sendiri sesudah rotasi dan menghasilkan incremental yang tak pernah bisa digabungkan — karena itu cache kini dibandingkan dengan manifest yang benar-benar ada di disk, dan basis diambil ulang bila keduanya berbeda. Mesin pencari menandai repository snapshot-nya read-only bila foldernya disentuh dari luar, dan sesudah itu setiap snapshot gagal — karena itu toolkit mengenali pesan tersebut, menghapus registrasinya saja, bukan file-nya, mendaftarkan ulang repository-nya, lalu mencoba tepat sekali lagi, tanpa berputar. Kini keduanya mengumumkan diri di log.
Backup baru muncul ketika sudah lengkap. Setiap backup ditulis ke folder kerja tersembunyi dan baru dipindah ke tempatnya di langkah paling akhir, jadi backup setengah jadi tak pernah tampil sebagai titik pemulihan. Folder induknya dibuat sebelum pemindahan terakhir itu, karena perintah pindah tak membuatnya, dan versi awal pernah kehilangan seluruh kerjanya tepat di langkah terakhir. Setiap penghapusan dijaga supaya variable kosong tak pernah berubah jadi penghapusan dari root, dan folder yang tak ada tak lagi menghentikan script diam-diam di bawah penanganan error yang ketat. Titik penuh dan incremental memakai satu susunan, jadi restore memperlakukan keduanya sama, dan backup bersusunan lama dimigrasi otomatis dengan memindahkan, bukan menyalin, sehingga hard link-nya selamat dan ukurannya tak berlipat.
Telemetry kembali bersama user yang menulisnya. Snapshot tanpa security index mesin pencari mengembalikan telemetry tapi tidak user dan role milik collector; collector lalu ditolak di setiap ekspor dan membuang semuanya, dan butuh dua hari sampai ketahuan di production. Kini snapshot menyertakan index itu dan global state-nya, dan restore membuat ulang user-nya sebagai jaring pengaman bagi backup lama. Repository snapshot tinggal bersama backup, bukan di dalam folder environment, karena menghapus folder production akan ikut menghapus seluruh backup telemetry; kepemilikannya dibetulkan lewat container berumur pendek, dan bila mesin pencari belum punya path repository, snapshot dilewati sambil mencetak perintah untuk menyalakannya.
Ia berjalan dari direktori mana pun, tanpa hak lebih tinggi. Path data dan backup diambil dari nama project, bukan dari letak repository, jadi path-nya sama di laptop saya dan di server, dan script-script ini bisa dijalankan dari working directory mana pun. Backup dan pembersihan sengaja tak memuat library bersama, jadi masing-masing bisa disalin dan dijalankan sendiri.
Kembali ke belakang itu murah, dan membersihkan itu aman. Titik pemulihan murah berkat hard link, dan setiap backup penuh berdiri sendiri: menghapus satu terbukti membiarkan setiap file di backup lain tetap identik. Restore bisa berhenti di titik bernama mana pun, tapi titik itu dicocokkan persis, jadi saya menampilkan rantainya lebih dulu lalu menyalin nilainya. Perintah pembersihan menolak menghapus semuanya, keluar dengan sukses bila tak ada yang perlu dihapus supaya job bulanan tak mengirim laporan gagal tiap bulan, dan membuang snapshot telemetry yatim yang tak terlihat oleh rotasi biasa.
Spesifikasi
| Mencakup | PostgreSQL 17 atau lebih baru dengan ringkasan WAL dinyalakan, empat database; MinIO; Elasticsearch lewat snapshot API-nya; file konfigurasi dan environment tiap modul |
|---|---|
| Keluaran | Satu folder per backup penuh berisi basis dan incremental-nya, manifest per titik, dan penunjuk ke backup penuh terbaru |
| Downtime | Tak ada saat backup; sekitar tujuh detik untuk restore |
| Sasaran restore | Semua data, atau PostgreSQL, MinIO, atau telemetry saja; sampai titik terakhir atau titik yang disebut |
| Keamanan | Restore mematikan sendiri database tujuan yang berjalan dan baru lanjut setelah mati; perintah pembersihan menyisakan setidaknya satu backup penuh; repository bersama tak pernah dihapus |
| Prasyarat | Docker; folder backup di satu filesystem supaya hard link berfungsi; cron untuk penjadwalan |
Terkait
Cerita di balik script-script ini, termasuk dua kegagalan yang tampak seperti sukses.
Setiap script mengikuti bentuk yang saya berikan pada setiap tool kecil, dalam varian Bash-nya.
Prosedur rilis saya membangun setiap environment staging dari backup ini, dan itulah yang membuktikan backup-nya bisa di-restore.
Kenapa telemetry dipindah lewat snapshot API: portabel lintas versi dan menyisakan titik untuk kembali.
Kenapa setiap environment staging lahir dari backup ini, bukan dari database yang sedang hidup.
Kenapa telemetry butuh user dan role sendiri, yang dibuat ulang oleh restore.
Istilah
- Base backup
- Salinan fisik lengkap dari data directory PostgreSQL, diambil selagi database berjalan.
- Backup incremental
- Backup yang hanya berisi blok yang berubah sejak backup sebelumnya, dan digabungkan dengannya saat restore.
- Write-ahead log (WAL)
- Catatan perubahan yang ditulis PostgreSQL sebelum menerapkannya, diputar ulang agar backup konsisten.
- Hard link
- Nama kedua untuk file yang sama di disk, sehingga dua folder bisa berbagi file tanpa menyimpannya dua kali.
- Recovery point objective (RPO)
- Jumlah data terbaru paling banyak yang bisa hilang, diukur sebagai waktu sejak backup terakhir.
- Repository snapshot
- Lokasi tempat Elasticsearch menyimpan snapshot-nya, dengan data yang dipakai bersama di antara snapshot-snapshot itu.
Technologies covered
- Bash
- rsync
- Elasticsearch
- PostgreSQL
- Docker
- MinIO