Deployment aman dengan rollback berbasis capability flag adalah pendekatan rilis yang memisahkan akses ke komponen berisiko—seperti database baru, worker, endpoint, dan integrasi eksternal—ke dalam flag yang dapat diaktifkan atau dimatikan secara independen. Tujuannya sederhana: saat insiden terjadi, Anda tidak perlu selalu melakukan full revert terhadap seluruh versi aplikasi; cukup matikan capability yang bermasalah.
Ide ini terinspirasi dari konsep capability: kode tidak otomatis mendapat akses ke semua hal, tetapi hanya ke kemampuan yang secara eksplisit diberikan. Dalam konteks operasi rilis, prinsip yang sama diterapkan ke deployment. Build yang sama bisa tetap jalan, tetapi akses terhadap resource atau perilaku tertentu dikendalikan oleh flag yang sempit dan terukur. Hasilnya adalah blast radius lebih kecil, rollback lebih cepat, dan investigasi lebih terarah.
Mengapa rollback tradisional sering terlalu kasar
Pada banyak sistem, satu deployment membawa beberapa perubahan sekaligus:
- schema database baru atau query path baru,
- worker asynchronous baru,
- endpoint API baru,
- integrasi ke pihak ketiga,
- perubahan cache atau event publishing.
Ketika satu bagian gagal, respons umum adalah rollback seluruh release. Ini memang sederhana, tetapi sering punya masalah:
- Terlalu mahal: perubahan lain yang sehat ikut dicabut.
- Tidak selalu aman: rollback kode belum tentu kompatibel dengan migrasi data yang sudah jalan.
- Lambat: pipeline redeploy atau revert image butuh waktu.
- Membingungkan: sulit membedakan apakah masalah ada di kode, konfigurasi, traffic, atau integrasi eksternal.
Rollback berbasis capability flag mengubah unit kendali dari versi aplikasi menjadi kemampuan operasional tertentu. Anda tetap bisa melakukan full revert bila perlu, tetapi itu bukan satu-satunya opsi.
Prinsip capability flag untuk deployment aman
Capability flag berbeda dari feature flag UI biasa. Fokusnya bukan sekadar menyalakan tampilan atau alur produk, tetapi mengontrol akses ke jalur teknis yang berisiko pada saat rilis.
Karakteristik capability flag yang baik
- Sempit: satu flag mengendalikan satu capability, bukan paket fitur besar.
- Eksplisit: nama flag menjelaskan resource atau perilaku yang diaktifkan.
- Aman saat mati: perilaku default ketika flag off harus jelas dan stabil.
- Dapat diamati: penggunaan flag harus tercermin di log dan metric.
- Dapat diubah cepat: perubahan flag tidak bergantung pada build baru bila memungkinkan.
Contoh capability yang layak dipisah
- Akses database baru: query read path baru, tabel baru, index baru, atau write path baru.
- Worker/job baru: consumer queue, cron, scheduled job, atau handler event async.
- Endpoint baru: route publik, route internal, webhook receiver, atau API versi baru.
- Integrasi eksternal: pengiriman ke payment gateway, email provider, ERP, analytics, atau webhook keluar.
Pemisahan seperti ini membuat rollback menjadi selektif. Misalnya, bila provider eksternal timeout, Anda cukup mematikan cap.ext.payment_v2 tanpa memengaruhi endpoint lain atau job internal yang sehat.
Desain capability flag yang praktis
Penamaan flag
Gunakan nama yang konsisten dan mudah dipindai saat insiden. Contoh:
cap.db.user_profile_v2_readcap.db.user_profile_v2_writecap.worker.invoice_reconciliationcap.endpoint.public_orders_v2cap.ext.shipping_partner_push
Hindari nama seperti new_feature_enabled karena tidak menjelaskan area risiko dan menyulitkan operasi rollback.
Bedakan read dan write path
Untuk database, memisahkan read dan write sangat membantu. Banyak insiden terjadi karena write path baru menghasilkan data salah, sementara read path lama masih stabil. Jika keduanya dibungkus dalam satu flag, opsi rollback Anda menjadi lebih sempit.
Gunakan mode degradasi yang jelas
Saat flag mati, sistem sebaiknya jatuh ke perilaku yang telah teruji. Contoh:
- gunakan query lama,
- tolak endpoint dengan status terkontrol seperti
404atau503sesuai kebutuhan, - skip publish ke integrasi eksternal dengan retry atau dead-letter yang jelas,
- pause worker baru tanpa menghentikan worker lama.
Kesalahan umum adalah mematikan flag tetapi tidak menyediakan fallback yang valid. Akibatnya sistem tetap gagal, hanya dengan jalur error yang berbeda.
Contoh implementasi sederhana
type Capabilities = {
dbUserProfileV2Read: boolean,
dbUserProfileV2Write: boolean,
workerInvoiceReconciliation: boolean,
endpointOrdersV2: boolean,
extShippingPartnerPush: boolean
}
function updateUserProfile(userId, payload, caps) {
if (caps.dbUserProfileV2Write) {
return writeToNewProfileTables(userId, payload)
}
return writeToLegacyProfileTables(userId, payload)
}
function registerRoutes(app, caps) {
if (caps.endpointOrdersV2) {
app.post('/api/orders/v2', createOrderV2)
}
}
async function pushShipment(event, caps, logger) {
if (!caps.extShippingPartnerPush) {
logger.info('shipping_push_skipped', { reason: 'capability_disabled' })
return
}
await shippingPartnerClient.send(event)
}Contoh di atas sengaja sederhana. Pada implementasi nyata, sumber capability biasanya berasal dari config service, env, atau sistem flag yang dapat diubah saat runtime.
Alur deployment aman dengan capability flag
Strategi ini bekerja paling baik bila deployment dipisah menjadi beberapa tahap kecil.
1. Rilis kode dengan capability default off
Deploy build yang sudah mengandung jalur baru, tetapi semua capability baru dalam keadaan nonaktif. Tahap ini memvalidasi bahwa binary/container dapat hidup normal tanpa memakai capability baru.
2. Jalankan perubahan yang kompatibel ke belakang
Untuk database, utamakan perubahan yang backward compatible:
- menambah tabel atau kolom baru,
- menambah index,
- mempersiapkan consumer baru tanpa langsung mengalihkan traffic,
- menambahkan route handler tanpa mempublikasikan aksesnya.
Hindari perubahan destruktif di fase awal, seperti menghapus kolom lama atau mengubah makna data secara langsung.
3. Aktifkan capability secara bertahap
Urutan yang umum dan aman:
- Internal dry run: aktifkan untuk environment staging atau subset internal.
- Canary: aktifkan pada sebagian kecil pod/instance atau subset tenant.
- Low-risk traffic: aktifkan read path lebih dulu, lalu write path.
- Scale up: naikkan coverage secara bertahap sambil memantau metric inti.
Untuk worker, Anda bisa mengaktifkan satu consumer dulu dengan concurrency rendah. Untuk integrasi eksternal, mulai dari sandbox, tenant tertentu, atau event type tertentu.
4. Finalize setelah stabil
Setelah capability stabil dalam periode observasi yang memadai, barulah lakukan pembersihan teknis:
- hapus jalur lama bila memang sudah aman,
- rapikan flag yang tidak lagi diperlukan,
- finalisasi migrasi data atau backfill,
- perbarui runbook dan dokumentasi operasi.
Jangan terlalu cepat menghapus fallback. Banyak tim gagal bukan saat aktivasi pertama, tetapi saat lonjakan traffic atau skenario pinggiran muncul beberapa jam kemudian.
Preflight check sebelum mengaktifkan capability
Sebelum flag dinyalakan, lakukan preflight check singkat namun ketat. Ini membantu mencegah insiden yang sebenarnya bisa diprediksi.
Checklist preflight umum
- Konfigurasi flag sudah benar di environment target.
- Dashboard log, metric, dan alert untuk capability tersebut sudah tersedia.
- Fallback saat flag off telah diuji.
- Timeout, retry, dan circuit breaker untuk integrasi eksternal sudah diset.
- Database migration yang dibutuhkan sudah selesai dan tervalidasi.
- Worker baru bisa dihentikan cepat tanpa memengaruhi queue lain.
- Runbook rollback telah disiapkan dan diketahui oleh on-call.
Checklist khusus database
- Query plan sudah diperiksa untuk query baru yang berat.
- Index yang dibutuhkan sudah ada sebelum read path baru aktif.
- Write path baru aman bila dijalankan paralel dengan write lama, jika memakai dual-write.
- Backfill data, bila ada, dipisahkan dari waktu puncak traffic.
- Schema lama belum dihapus sebelum confidence cukup tinggi.
Checklist khusus worker dan queue
- Consumer group atau subscription baru tidak mencuri beban dari alur lama secara tak sengaja.
- Idempotensi handler jelas, terutama jika retry dapat terjadi.
- Concurrency dan batch size konservatif pada awal aktivasi.
- Dead-letter queue atau mekanisme karantina tersedia.
Checklist khusus integrasi eksternal
- Kredensial, endpoint, dan rate limit tervalidasi.
- Request dapat ditandai dengan correlation ID.
- Retry tidak menyebabkan duplikasi aksi bisnis yang berbahaya.
- Ada opsi kill switch terpisah untuk menghentikan panggilan keluar.
Observability minimum untuk rollback selektif
Rollback selektif hanya efektif bila Anda bisa melihat capability mana yang bermasalah. Observability minimum tidak harus rumit, tetapi harus cukup untuk menjawab tiga pertanyaan saat insiden:
- Capability mana yang baru aktif?
- Apa gejala utamanya: error, latency, backlog, atau data mismatch?
- Apakah mematikan capability memperbaiki kondisi?
Log yang wajib ada
Setiap penggunaan capability sebaiknya meninggalkan jejak log terstruktur. Minimal sertakan:
- nama capability,
- status on/off atau path yang dipilih,
- request ID atau job ID,
- tenant atau actor yang relevan,
- hasil operasi dan error ringkas.
{
"event": "capability_decision",
"capability": "cap.ext.shipping_partner_push",
"enabled": true,
"request_id": "req-123",
"tenant_id": "tenant-a"
}Metric minimum per capability
- Request/error rate untuk endpoint baru.
- Latency untuk path database atau API eksternal.
- Queue depth, retry count, dead-letter count untuk worker.
- Fallback count untuk melihat seberapa sering sistem kembali ke path lama.
- Activation audit kapan flag berubah dan oleh siapa.
Alert yang berguna
Alert tidak perlu terlalu banyak. Fokus pada sinyal yang memicu tindakan rollback:
- error rate melonjak setelah capability diaktifkan,
- latency melampaui ambang operasional normal,
- queue backlog tumbuh terus setelah worker baru aktif,
- timeout ke provider eksternal meningkat,
- fallback atau exception tertentu meningkat tajam.
Hubungkan alert dengan runbook yang spesifik. Alert tanpa tindakan yang jelas hanya menambah kebisingan saat insiden.
Contoh runbook rollback berbasis capability flag
Runbook harus sesingkat mungkin, bisa dieksekusi cepat, dan tidak bergantung pada ingatan individu. Contoh berikut untuk kasus integrasi eksternal yang bermasalah.
Skenario
Setelah deploy, capability cap.ext.shipping_partner_push diaktifkan. Dalam beberapa menit, error timeout naik dan antrean event pengiriman menumpuk.
Tujuan rollback
Menghentikan panggilan ke partner eksternal tanpa menurunkan seluruh aplikasi.
Langkah runbook
- Konfirmasi gejala di dashboard: timeout, error rate, queue backlog.
- Periksa audit perubahan flag untuk memastikan capability baru memang baru diaktifkan.
- Nonaktifkan
cap.ext.shipping_partner_push. - Verifikasi log mulai menunjukkan
shipping_push_skippedatau path fallback yang diharapkan. - Pastikan endpoint internal tetap sehat dan backlog berhenti bertambah.
- Jika queue sudah terisi event gagal, pindahkan ke retry terkontrol atau dead-letter sesuai kebijakan.
- Buat catatan insiden: waktu aktivasi, waktu rollback, dampak, dan hipotesis awal.
Contoh checklist eksekusi
- [ ] Alert tervalidasi, bukan false positive
- [ ] Capability yang dicurigai teridentifikasi
- [ ] Flag dimatikan
- [ ] Dampak menurun dalam jendela observasi singkat
- [ ] Tidak ada efek samping pada capability lain
- [ ] Status insiden diperbarui ke tim terkait
Kapan full revert tetap diperlukan
Rollback selektif bukan obat untuk semua masalah. Full revert tetap masuk akal bila:
- bug ada di jalur dasar aplikasi, bukan capability terpisah,
- build gagal start atau crash loop,
- perubahan menyentuh keamanan atau autentikasi inti,
- data corruption sudah menyebar dan fallback tidak cukup.
Pola implementasi per area
Database: expand, switch, contract
Pola aman yang umum adalah:
- Expand: tambah schema baru yang kompatibel.
- Switch: aktifkan capability read/write bertahap.
- Contract: hapus schema lama setelah sistem stabil.
Jika perlu dual-write, gunakan dengan hati-hati. Dual-write membantu transisi, tetapi menambah risiko inkonsistensi. Idealnya ada mekanisme verifikasi atau rekonsiliasi untuk membandingkan hasil write lama dan baru.
Worker: pause/resume sebagai capability
Worker sering menjadi sumber blast radius besar karena berjalan terus dan memproses volume tinggi. Anggap setiap worker baru sebagai capability tersendiri:
- punya toggle aktif/nonaktif,
- bisa dibatasi concurrency,
- bisa diarahkan ke queue terpisah,
- bisa dikarantina ke dead-letter jika terjadi lonjakan error.
Kesalahan umum adalah mengaktifkan worker baru dengan concurrency produksi penuh sejak awal. Lebih aman mulai kecil lalu naikkan bertahap.
Endpoint: registration dan exposure dipisah
Untuk endpoint baru, ada dua kontrol yang sering berguna:
- Registration capability: route benar-benar tersedia atau tidak.
- Exposure capability: route ada, tetapi hanya dapat diakses oleh tenant, header, atau upstream tertentu.
Dengan begitu, Anda bisa menguji endpoint di produksi secara terbatas tanpa membukanya ke semua klien.
Integrasi eksternal: kill switch wajib
Semua integrasi keluar yang memengaruhi proses bisnis sebaiknya punya kill switch sendiri. Ini berguna ketika provider mengalami gangguan atau perilaku tak terduga, dan Anda ingin menghentikan efek domino secepat mungkin.
Mencegah blast radius tetap kecil
Capability flag membantu, tetapi efektivitasnya sangat dipengaruhi oleh disiplin desain sistem.
Pisahkan domain risiko
Jangan gabungkan beberapa capability berisiko tinggi dalam satu flag besar. Misalnya, write database baru dan publish ke partner eksternal sebaiknya tidak ada di flag yang sama. Jika keduanya digabung, Anda kehilangan rollback selektif.
Batasi cakupan aktivasi
Aktifkan capability berdasarkan unit yang bisa dikendalikan:
- per instance atau pod,
- per tenant,
- per region,
- per job type,
- per endpoint path.
Semakin sempit cakupannya, semakin kecil blast radius awal dan semakin mudah mengisolasi masalah.
Jangan campur migrasi berat dengan jam sibuk
Backfill besar, reindex, atau replay queue sebaiknya dipisahkan dari momen aktivasi capability. Jika semua dilakukan bersamaan, Anda akan kesulitan membedakan sumber degradasi.
Audit perubahan operasi
Catat kapan flag diubah, oleh siapa, dan untuk environment mana. Banyak insiden membesar karena tim tidak yakin kapan perubahan terakhir dilakukan.
Postmortem ringan setelah insiden
Setelah rollback selektif berhasil, jangan berhenti di pemadaman saja. Lakukan postmortem ringan agar kejadian serupa tidak berulang.
Pertanyaan yang perlu dijawab
- Capability mana yang memicu insiden?
- Gejala pertama terlihat di mana: log, metric, alert, atau laporan pengguna?
- Apakah preflight check seharusnya bisa menangkap masalah ini?
- Apakah runbook cukup jelas dan cepat dijalankan?
- Apakah fallback saat flag off bekerja sesuai harapan?
- Apakah ada gap observability yang membuat diagnosis lambat?
Output postmortem yang bermanfaat
- timeline singkat kejadian,
- akar masalah teknis atau hipotesis terkuat,
- perubahan konkret pada kode, preflight, dashboard, atau alert,
- keputusan apakah capability akan diperbaiki, dipecah, atau dihapus.
Fokuskan postmortem pada perbaikan sistem, bukan menyalahkan operator yang mematikan flag saat insiden.
Trade-off: kompleksitas vs keamanan rilis
Pendekatan ini tidak gratis. Ada biaya desain dan operasi yang nyata.
Keuntungan
- Rollback lebih cepat dan lebih sempit.
- Tidak semua insiden perlu full revert.
- Lebih aman untuk perubahan yang melibatkan schema atau integrasi eksternal.
- Diagnosis insiden lebih terarah karena unit kontrol lebih kecil.
Biaya dan keterbatasan
- Kompleksitas kode bertambah: ada lebih banyak cabang eksekusi dan fallback.
- Butuh observability lebih disiplin: tanpa log dan metric yang baik, flag hanya memindahkan kompleksitas.
- Risk of flag debt: flag yang tidak dibersihkan akan menumpuk dan membingungkan.
- Tidak cocok untuk semua perubahan: beberapa perubahan arsitektural tetap membutuhkan rollback versi atau strategi migrasi yang lebih besar.
Pilih capability flag terutama untuk area dengan risiko operasional tinggi dan rollback yang mahal: database, worker, endpoint publik, dan integrasi eksternal. Jangan memaksakan semua detail kecil menjadi flag karena biaya kognitifnya akan naik.
Checklist implementasi singkat
- [ ] Identifikasi capability berisiko tinggi per fitur
- [ ] Pisahkan read/write, worker, endpoint, dan integrasi bila perlu
- [ ] Pastikan default off dengan fallback yang valid
- [ ] Tambahkan log terstruktur dan metric per capability
- [ ] Siapkan alert yang terhubung ke tindakan rollback
- [ ] Buat runbook singkat untuk kill switch atau disable flag
- [ ] Aktifkan bertahap: internal, canary, sebagian traffic, penuh
- [ ] Lakukan cleanup flag setelah stabil
Penutup
Deployment aman dengan rollback berbasis capability flag bukan sekadar variasi feature flag, tetapi cara mendesain rilis agar akses ke bagian sistem yang berisiko bisa dikendalikan secara presisi. Dengan memisahkan capability untuk database baru, worker, endpoint, dan integrasi eksternal, Anda mendapat opsi rollback yang jauh lebih fleksibel daripada full revert.
Mulailah dari area yang paling sering menimbulkan insiden atau paling mahal untuk di-rollback. Jika dipadukan dengan preflight check yang disiplin, observability minimum yang memadai, dan runbook yang ringkas, pendekatan ini dapat menurunkan blast radius tanpa membuat proses rilis menjadi kaku.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!