Rekayasa Defensif: Sistem yang Tahan Bencana
Pelajari pola rekayasa defensif untuk mencegah bencana produksi: molly guard, mode dry-run, soft delete, dan alasan dialog konfirmasi itu gagal.

Seorang junior engineer di perusahaan fintech berukuran menengah menjalankan skrip migrasi database pada Jumat sore. Skrip itu seharusnya membersihkan record yatim (orphaned) di lingkungan staging. Alih-alih begitu, skrip tersambung ke database produksi dan menghapus 4,2 juta record transaksi pelanggan. Backup-nya? Sudah tiga hari lalu. Perusahaan menghabiskan 72 jam berikutnya dalam kondisi incident response penuh, merekonsiliasi record secara manual dari log payment processor. Total biayanya lebih dari $400.000 untuk waktu engineering, kredit pelanggan, dan pelaporan ke regulator.
Skrip itu tidak punya pengecekan environment. Tidak ada flag dry-run. Tidak ada prompt konfirmasi yang menampilkan database mana yang sedang terhubung. Connection string-nya diambil dari environment variable yang kebetulan diset ke produksi di laptop engineer tersebut, sisa sesi debugging dua minggu sebelumnya. Setiap kegagalan ini sebenarnya bisa dicegah.
Mengapa Dialog “Are You Sure?” Tidak Berfungsi
Mekanisme pengaman paling umum dalam software adalah dialog konfirmasi. Ini juga yang paling tidak berguna. Studi tentang confirmation fatigue menunjukkan bahwa pengguna hampir selalu mengklik melewati prompt “Are you sure?” setelah beberapa kali melihatnya. Prompt itu jadi tak kelihatan — cuma satu klik lagi di jalur menuju hal yang sudah Anda putuskan untuk dilakukan.
Masalahnya bukan konfirmasi itu ide buruk. Masalahnya, konfirmasi generik tidak membawa informasi apa pun. “Are you sure you want to proceed?” tidak memberi tahu Anda apa yang akan dilakukan. Bandingkan dengan: “Anda akan menghapus 4.217.893 baris dari tabel transactions di prod-us-east-1. Ketik nama database untuk konfirmasi.” Versi kedua memaksa Anda benar-benar membaca apa yang sedang terjadi. Itulah bedanya antara speed bump dan molly guard.
Apa Itu Molly Guard dan Mengapa Penting
Istilah “molly guard” berasal dari penutup plastik fisik yang dipasang di atas Big Red Button pada mainframe untuk mencegah shutdown tidak sengaja — konon dinamai dari putri kecil seorang programmer, Molly, yang suka menekannya. Dalam software, molly guard adalah mekanisme apa pun yang membuat aksi destruktif sulit dilakukan secara tidak sengaja, tapi tetap bisa dilakukan saat memang disengaja.
Paket Linux molly-guard melakukan persis ini. Ia mencegat perintah shutdown, reboot, dan halt pada sesi SSH, lalu meminta Anda mengetik hostname mesin yang akan dimatikan. Anda tidak bisa melewatinya dengan autopilot — Anda harus membuktikan bahwa Anda tahu sedang berada di mesin mana.
$ sudo reboot
Good grief! You're on a remote SSH session to prod-web-03.
Please type the hostname to confirm: prod-web-03
# Compare with the useless version:
$ sudo reboot
Are you sure? (y/n): y
# *types y without reading, muscle memory*
Prinsipnya sederhana: konfirmasi harus meminta informasi yang membuktikan operator memahami aksinya. Mengetik “y” tidak membuktikan apa-apa. Mengetik hostname, nama tabel, atau jumlah record yang terdampak membuktikan Anda sudah membaca peringatannya.
Mode Dry-Run: Lihat Dulu Sebelum Menembak
Setiap operasi destruktif seharusnya punya mode dry-run. Bukan “seharusnya” sebagai best practice — tapi “seharusnya” dalam arti Anda pada akhirnya akan menyesal tidak punya. Dry run menjalankan seluruh logika operasi, mencatat dengan tepat apa yang akan terjadi, lalu berhenti. Tanpa efek samping. Visibilitas penuh.
Polanya mudah diimplementasikan. Ini contoh skrip migrasi dengan dry-run bawaan:
#!/bin/bash
set -euo pipefail
DRY_RUN=${DRY_RUN:-true} # Default to dry-run!
DB_HOST=$(get_db_host)
DB_NAME=$(get_db_name)
echo "Target: $DB_HOST / $DB_NAME"
echo "Mode: $([ "$DRY_RUN" = true ] && echo 'DRY RUN' || echo 'LIVE')"
ROWS=$(psql -h "$DB_HOST" -d "$DB_NAME" -t -c \
"SELECT count(*) FROM orphaned_records WHERE created_at < now() - interval '90 days'")
echo "Records to delete: $ROWS"
if [ "$DRY_RUN" = true ]; then
echo "Dry run complete. Set DRY_RUN=false to execute."
exit 0
fi
# Require explicit confirmation for live runs
read -p "Type '$DB_NAME' to confirm deletion: " CONFIRM
if [ "$CONFIRM" != "$DB_NAME" ]; then
echo "Aborted."
exit 1
fi
psql -h "$DB_HOST" -d "$DB_NAME" -c \
"DELETE FROM orphaned_records WHERE created_at < now() - interval '90 days'"
echo "Deleted $ROWS records."
Perhatikan tiga hal: default-nya adalah dry-run (Anda harus memilih untuk melakukan penghapusan, bukan memilih untuk tidak), skrip menampilkan database target dan jumlah baris sebelum melakukan apa pun, dan bahkan dalam mode live pun Anda tetap harus mengetik nama database. Tiga lapis pertahanan. Salah satunya saja sudah cukup untuk mencegah insiden yang saya ceritakan di awal.
Jaring Pengaman Migrasi Database
Migrasi database termasuk operasi dengan risiko tertinggi dalam pipeline deployment mana pun. Sering kali tidak bisa dibatalkan, berjalan di atas state yang dipakai bersama, dan satu migrasi yang buruk bisa melumpuhkan seluruh aplikasi. Namun kebanyakan tim memperlakukannya sebagai sekadar langkah deploy biasa.
Ini hierarki mekanisme pengaman, dari yang dasar sampai yang anti-gagal:
- Pengecekan environment — Skrip migrasi memverifikasi bahwa ia menargetkan environment yang dituju sebelum dieksekusi. Kedengarannya jelas. Anda akan kaget seberapa sering ini terlewat.
- Snapshot pra-migrasi — Otomatis membuat snapshot database sebelum migrasi apa pun berjalan. Jika migrasi gagal atau menimbulkan masalah, Anda bisa memulihkan dalam hitungan menit, bukan jam.
- Row count guard — Jika migrasi akan memengaruhi lebih dari N baris, minta konfirmasi eksplisit. Migrasi yang tiba-tiba menyentuh jutaan baris hampir selalu adalah bug.
- Statement timeout — Pasang timeout yang agresif pada query migrasi. Migrasi yang berjalan 45 menit sedang mengunci tabel dan menurunkan performa. Gagal lebih cepat.
- Hanya backward-compatible — Pastikan setiap migrasi kompatibel mundur dengan kode yang sedang di-deploy. Artinya tidak ada rename kolom, tidak ada penambahan NOT NULL tanpa default, tidak ada drop kolom yang masih dirujuk.
# Rails example using strong_migrations gem
class AddPhoneToUsers < ActiveRecord::Migration[7.1]
def change
# This will raise an error with a safe alternative:
# add_column :users, :phone, :string, null: false
#
# Instead, do it in two steps:
add_column :users, :phone, :string
# Then in a separate migration after backfill:
# change_column_null :users, :phone, false
end
end
Tool seperti strong_migrations di Rails, squawk untuk PostgreSQL, dan skeema untuk MySQL menangkap pola berbahaya sebelum sampai ke produksi. Jika Anda tidak memakai sesuatu seperti ini, Anda hanya mengandalkan code review untuk menangkap masalah migrasi yang halus — dan reviewer sering melewatkan hal-hal seperti itu.
Soft Delete dan Jendela Undo
Hard delete adalah komitmen permanen yang dibuat pada saat merasa yakin. Masalahnya, rasa yakin itu sering salah. Soft delete — menandai record sebagai terhapus tanpa benar-benar membuangnya — memberi Anda jendela untuk memulihkan diri dari kesalahan.
-- Hard delete: gone forever
DELETE FROM projects WHERE id = 4872;
-- Soft delete: recoverable
UPDATE projects
SET deleted_at = now(),
deleted_by = 'user:priya'
WHERE id = 4872;
-- Recovery is trivial
UPDATE projects
SET deleted_at = NULL,
deleted_by = NULL
WHERE id = 4872;
Trade-off-nya nyata: soft delete menambah kerumitan query (Anda harus menulis WHERE deleted_at IS NULL di mana-mana), menambah penyimpanan, dan bisa membingungkan soal kondisi data yang sebenarnya. Tapi untuk data yang dilihat pengguna dan rawan terhapus tidak sengaja, trade-off ini sepadan. GitHub tidak benar-benar menghapus repository Anda sampai 90 hari. Slack menyimpan pesan yang dihapus untuk kepatuhan. Trash Gmail dikosongkan setelah 30 hari. Ini bukan kebetulan — ini keputusan engineering yang disengaja.
Prinsip yang sama berlaku untuk infrastruktur. Alih-alih menerminasi instance EC2, hentikan dulu. Alih-alih men-drop database, ganti namanya menjadi mydb_deleted_20260315 dan pasang pengingat di kalender untuk benar-benar menghapusnya dua minggu lagi. Biaya menyimpan instance yang dihentikan atau database yang diganti nama selama beberapa hari hampir tidak ada dibandingkan biaya memulihkan dari backup.
Keamanan Deployment: Canary, Circuit Breaker, dan Rollback
Deployment adalah kategori aksi destruktif lain yang kebanyakan tim tidak perlakukan dengan cukup hati-hati. Deploy yang buruk bisa melumpuhkan produksi sama efektifnya dengan database yang di-drop — dan kejadiannya jauh lebih sering.
Setup keamanan deployment minimum yang layak mencakup:
- Canary deployment — Arahkan 1-5% trafik ke versi baru. Jika error rate melonjak, rollback otomatis sebelum blast radius membesar.
- Pemicu rollback otomatis — Tentukan threshold error rate, threshold latensi, dan kegagalan health check yang memicu rollback otomatis. Jangan bergantung pada manusia yang baru sadar jam 2 pagi.
- Deploy freeze saat insiden — Jika ada insiden yang sedang berjalan, blokir semua deployment. Hal terakhir yang Anda butuhkan saat sedang memadamkan kebakaran adalah seseorang men-deploy perubahan yang tidak berkaitan.
- Rollback satu klik — Rollback seharusnya lebih mudah daripada roll forward. Jika proses rollback Anda melibatkan SSH ke server dan menjalankan perintah manual, berarti Anda tidak punya proses rollback.
# Kubernetes progressive delivery with Argo Rollouts
apiVersion: argoproj.io/v1alpha1
kind: Rollout
spec:
strategy:
canary:
steps:
- setWeight: 5
- pause: { duration: 5m }
- analysis:
templates:
- templateName: error-rate-check
- setWeight: 25
- pause: { duration: 5m }
- setWeight: 50
- pause: { duration: 10m }
- setWeight: 100
# Auto-rollback if error rate exceeds 1%
abortScaleDownDelaySeconds: 30
Polanya di sini adalah progressive commitment. Anda tidak langsung dari 0 ke 100% dalam satu langkah. Anda ambil langkah kecil, verifikasi tiap langkah, dan tetap bisa mundur di setiap tahap. Memang lebih lambat dibanding yolo-deploy ke semua instance sekaligus, tapi saat pertama kali ia menangkap deploy buruk sebelum mengenai semua pengguna, Anda akan bersyukur atas setiap menit ekstra itu.
Pencegahan Arsitektural: Membuat Hal yang Salah Jadi Mustahil
Mekanisme keamanan terbaik tidak meminta Anda untuk berhati-hati. Mereka membuat kesalahan secara struktural tidak mungkin dilakukan. Inilah bedanya guardrail dengan rambu peringatan.
- Infrastruktur immutable — Jika Anda tidak bisa SSH ke server produksi, Anda tidak bisa tidak sengaja menjalankan perintah di sana. Jika deployment selalu berupa instance baru dari image yang diketahui, Anda tidak akan punya configuration drift.
- Akses least-privilege — Engineer tidak seharusnya menyimpan kredensial database produksi di laptop mereka. Titik. Gunakan tool akses just-in-time yang memberikan kredensial sementara dengan jejak audit.
- Kredensial terpisah per environment — Jika staging dan produksi memakai credential store yang berbeda, Anda secara harfiah tidak bisa tidak sengaja terhubung ke produksi dengan tooling staging.
- Proteksi penghapusan — AWS memungkinkan Anda mengaktifkan termination protection pada instance EC2 dan deletion protection pada database RDS. Nyalakan untuk semua yang penting. Ini perubahan konfigurasi lima detik yang mencegah satu kelas kesalahan katastrofik sepenuhnya.
Jika seseorang bisa tidak sengaja menghancurkan produksi dengan satu perintah, masalahnya bukan pada orangnya — melainkan pada sistem yang membiarkan satu perintah bisa menghancurkan produksi.
Saya pernah melihat tim merespons insiden produksi dengan menambah dokumentasi, checklist, dan training. Ini membantu, tapi semuanya bergantung pada manusia yang sempurna. Manusia tidak sempurna. Respons yang lebih baik adalah mengubah sistemnya agar kesalahan itu tidak mungkin terjadi sejak awal, atau kalau terjadi, blast radius-nya terbatas dan pemulihannya cepat.
Membangun Budaya Engineering yang Mengutamakan Keamanan
Tools dan arsitektur memang penting, tapi budayalah yang menentukan apakah semua itu benar-benar diterapkan. Tim yang menganggap mekanisme keamanan sebagai beban atau birokrasi akan melewatkannya saat tekanan deadline — dan tekanan deadline itu permanen.
Pola paling efektif yang pernah saya lihat adalah memperlakukan mekanisme keamanan sebagai kebutuhan engineering kelas utama, bukan sekadar nice-to-have. Setiap operasi destruktif harus lolos design review yang secara spesifik bertanya: apa yang terjadi jika ini dijalankan ke target yang salah? Apa yang terjadi jika dijalankan dua kali? Apa yang terjadi jika dijalankan dengan data yang sudah usang? Bagaimana cara membatalkannya?
Blameless post-mortem sudah jadi syarat minimum sekarang. Tapi praktik yang lebih jarang, dan menurut saya lebih penting, adalah pre-mortem. Sebelum meluncurkan perubahan berisiko, kumpulkan tim dan tanyakan: “Anggap ini berantakan total. Apa yang terjadi?” Orang ternyata sangat jago mengidentifikasi mode kegagalan ketika diminta berimajinasi, bukan memprediksi. Kegagalan yang mereka temukan itulah yang menjadi mekanisme keamanan yang Anda bangun.
Junior engineer yang men-drop database produksi itu? Dia masih di perusahaan. Sekarang dia salah satu advokat paling vokal untuk defensive engineering di tim. Insiden itu bukan salahnya — itu kegagalan sistem. Dan sistemnya sekarang jauh lebih sulit untuk dijebol.


