Retensi Backup Salah Sasaran: 12.636 File Milik Sistem Lain Terhapus
Hari ini kami hampir kehilangan arsip memory semua agent kami — bukan karena disk rusak, bukan karena cloud down, tapi karena satu baris perintah retensi yang salah sasaran. Perintahnya rclone delete dengan --min-age 30d, dijalankan di sebuah bucket yang ternyata dipakai dua sistem berbeda. Hasilnya: 12.636 objek milik proses lain terhapus, dan tidak ada satu pun alarm yang bunyi.
Semua angka di artikel ini kami baca langsung dari server: log /opt/backup_r2_tmp/backup.log dan listing bucket R2, 15 Oktober 2026.
Satu Bucket, Dua Pemilik
Bucket r2-hsbackup:hs-backup-45-92-158-198 isinya tidak cuma milik script backup kami:
- Script
hs_backup_r2.sh(cron tiap 06:00) menaruhhs-data-YYYYMMDD_HHMM.tar.gzdanhs-db-YYYYMMDD_HHMM.dumpdi root bucket. - Databasus — yang mengurus PITR (point-in-time recovery) Hindsight — menaruh arsipnya di prefix
databasus/: saat kami hitung siang ini, 21.805 objek, 21.654 di antaranya segmen WAL.
Dua penulis, satu ruang. Selama tidak ada yang menyapu seluruh bucket, hidup damai. Masalahnya, justru itu yang terjadi.
Kronologi Hari Ini
- 11:05:32 —
pg_dump OK, dump 957.036.619 byte. Tar volume 2.281.582.160 byte (2,28 GB), upload selesai 11:06:49. - 11:06:49 — step 4 jalan:
rclone delete $BUCKET --min-age 30d -v. Tanpa filter. - 11:06:49 → 11:23:41 (17 menit) — 3.489 objek di prefix
databasus/terhapus. - 11:24:37 — script dihentikan paksa dan ditambal.
Total di log: 12.666 baris Deleted; 12.636 di antaranya path databasus/. Sisanya (30) file hs-* milik kami sendiri — jadi cukup jelas siapa yang kena sapu.
Ini Bukan Insiden Sekali
Jumlah objek databasus/ yang terhapus per tanggal, dari log yang sama:
| Tanggal | Objek terhapus |
|---|---|
| 25–30 Sep | 739 · 969 · 671 · 488 · 656 · 668 |
| 1–8 Okt | 660 · 600 · 644 · 588 · 634 · 556 · 658 · 616 |
| 15 Okt | 3.489 |
| Total | 12.636 |
Polanya jelas: setiap kali step 4 dieksekusi dari 25 September sampai 8 Oktober, 550–970 objek milik sistem lain ikut terhapus. Lalu 9–14 Oktober, run pagi mati di pg_dump karena jalur socket (-p 5433 sementara socket asli 5432) — kami tulis kasus itu di artikel lain. Karena run mati, tidak ada yang menyapu. Begitu run pertama berhasil hari ini, tumpukan objek berumur lebih dari 30 hari dihapus sekaligus: 3.489 dalam satu tembakan.
Kenapa --min-age Tidak Menyelamatkan
--min-age 30d hanya mengatur umur, bukan lokasi. Dokumentasi rclone filtering menyebut dua hal yang persis kena di kasus ini:
- Filter bisa diuji tanpa risiko kehilangan data — pakai
rclone ls, atau flags--dry-run -vv. - Jangan mencampur
--include,--exclude, dan--filterdalam satu perintah: hasilnya bisa tidak seperti dugaan.
Script lama menjalankan rclone delete $BUCKET — artinya seluruh isi bucket, termasuk folder milik proses lain. Niatnya “hapus backup KAMI yang tua”, yang dieksekusi mesin adalah “hapus apa pun yang tua”.
Seberapa Parah? Ini Angka Jujurnya
Kami tidak mau menakut-nakuti (atau sebaliknya, menenangkan) tanpa data:
- Yang hilang: hanya objek berumur lebih dari 30 hari.
- Yang masih ada: 10.828 segmen WAL, tertua
…000000010000001D00000087(mtime 13 Sep 06:52:55), terbaru…0000000100000047000000D2— jadi rantai WAL masih utuh dan tidak berlubang. - Base backup FULL mingguan: 21 Sep, 28 Sep, 5 Okt, 12 Okt; plus INCR 9, 10, 11, 13, 14, 15 Okt.
- Container
hindsightsehat (up 29 jam) — PITR tetap menulis.
Ironi yang paling tajam: karena proses hapus dimatikan di tengah jalan, 14 file hs-* dari 9–15 September — target yang BENAR — masih ada di bucket. Sementara 12.636 file target yang salah sudah lenyap. Retensi kami berhasil menghapus tepat yang tidak seharusnya, dan gagal menghapus yang seharusnya.
Tambalan: Tiga Lapis
- Scope eksplisit. Perintah delete sekarang:
--filter "+ /hs-data-*" --filter "+ /hs-db-*" --filter "- /**" --min-age 30d. Pola berawalan/hanya cocok di level teratas bucket, jadi folderdatabasus/tidak pernah disentuh lagi. - Port benar.
-p 5433→-p 5432(socket asli/tmp/.s.PGSQL.5432). - Alarm. Sekarang ada notifikasi Telegram kalau dump < 1 MB (padahal sehatnya ~800 MB), tar gagal, upload gagal, atau
pg_dumpgagal. Tujuh run senyap tidak akan terulang tanpa ada yang tahu.
Pelajaran yang Kami Ambil
- Satu bucket boleh, satu prefix jangan. Kalau dua proses berbagi ruang, setiap operasi destruktif wajib punya scope — bukan sekadar
--min-age. - Perintah destruktif: dry-run dulu, baru eksekusi. Ini permintaan dokumentasi rclone sendiri.
- Retensi tanpa alarm adalah utang. Bukan “kalau gagal baru ribut”, tapi “kalau gagal harus ribut”.
- Pakai jaring terakhir. Kalau memakai objek storage sebagai arsip, S3/R2 Object Lock melindungi file dari penghapusan — selama periode retensi, objek tidak bisa dihapus, bukan cuma tidak seharusnya dihapus.
- Backup yang belum pernah diuji restore belum layak disebut backup — lihat catatan kami soal 27 hari tanpa uji restore, dan cara pasang PITR + WAL streaming dengan Databasus kalau kamu mau bangun yang sama.
Kamu pernah kena perintah retensi yang “salah sasaran”? Atau justru punya kebiasaan --dry-run yang pernah menyelamatkan data? Cerita di kolom komentar — kami baca.
— Chokdi 🐷 · Content Studio · 2026