Langsung ke konten
Feature Flag: Cara Saya Rilis Fitur Bertahap Tanpa Takut Merusak Produksi
Kembali ke Blog
Backend Development·6 menit baca·

Feature Flag: Cara Saya Rilis Fitur Bertahap Tanpa Takut Merusak Produksi

Feature flag membuat saya bisa rilis fitur bertahap, matikan cepat saat produksi bermasalah, dan uji perubahan besar tanpa deploy ulang. Ini praktik yang saya pakai sekarang.

TH
Tomi Hartanto·Senior Software Engineer

Rabu malam tahun lalu, saya deploy perombakan besar modul invoicing ke produksi. Tes lintas semua hijau, review dapur dua orang senior. Dua jam setelah deploy, laporan pelanggan masuk: template invoice lama ikut ke-render untuk tenant tertentu. Rollback butuh 25 menit — build, deploy, verifikasi. Dalam 25 menit itu saya berjanji pada diri sendiri: tidak lagi menggantung pemulihan produksi pada proses deploy.

Sejak itu, hampir semua rilis fitur di tim saya lewat feature flag. Bukan karena trend, tapi karena dua insiden seperti itu mengajarkan saya satu hal: kecepatan mematikan fitur bermasalah jauh lebih berharga daripada kecepatan deploy.

Apa yang Sebenarnya Diselesaikan Feature Flag

Feature flag pada intinya adalah percabangan if di runtime: kode baru sudah ada di produksi, tapi belum aktif untuk semua pengguna. Pertanyaan klasik di Stack Overflow tentang apa itu feature flag menyebut poin pentingnya — ini pola untuk memisahkan deploy dari rilis. Deploy jadi kegiatan teknis yang membosankan, rilis jadi keputusan bisnis yang bisa dicabut kapan saja.

Praktisnya, ini yang berubah bagi saya:

  • Deploy hari Selasa, rilis hari Kamis. Kode berdiam di produksi dalam kondisi off dulu, saya observasi metrik dasar (error rate, latency), baru buka bertahap.
  • Kill switch. Saat ada gejala aneh, saya matikan flag dalam hitungan detik lewat dashboard — tanpa build, tanpa rollback.
  • Rilis persentase. Buka 1% traffic dulu, lihat error rate, naikkan bertahap ke 10%, 50%, 100%.

Kalau kamu sudah terbiasa dengan praktik environment variable yang saya tulis sebelumnya, bedanya mendasar: env var butuh restart proses untuk berubah, flag dievaluasi per-request. Itu selisih antara "nanti saya restart dulu" dan "sudah mati sekarang".

Diagram alur rilis bertahap dengan feature flag, deploy terpisah dari aktivasi fitur

Implementasi Minimal yang Benar-Benar Jalan

Kamu tidak butuh SaaS mahal untuk mulai. Versi paling sederhana yang saya pakai di proyek kecil: tabel PostgreSQL + cache in-memory. Begini skemanya di Prisma:

model FeatureFlag {
  key       String   @id
  enabled   Boolean  @default(false)
  rollout   Int      @default(0) // 0-100 persen
  updatedAt DateTime @updatedAt
}

Dan evaluatornya di TypeScript:

const cache: Map<string, { value: FeatureFlag; fetchedAt: number }> = new Map();
const TTL_MS = 10_000; // refresh tiap 10 detik

export async function isEnabled(key: string, userId?: string): Promise<boolean> {
  let entry = cache.get(key);
  if (!entry || Date.now() - entry.fetchedAt > TTL_MS) {
    const flag = await prisma.featureFlag.findUnique({ where: { key } });
    if (!flag) return false;
    entry = { value: flag, fetchedAt: Date.now() };
    cache.set(key, entry);
  }
  const { enabled, rollout } = entry.value;
  if (!enabled || rollout === 0) return false;
  if (rollout >= 100) return true;
  // hash stabil: user yang sama selalu dapat hasil sama
  const hash = crc32(userId ?? key);
  return (hash % 100) < rollout;
}

Dua detail yang sering orang lewatkan dan pernah membuat saya kena masalah:

  1. Hash harus deterministik terhadap user ID, bukan Math.random(). Kalau random, user yang sama melihat fitur itu hilang-hidup di antara request — pengalaman yang membingungkan dan sulit di-debug.
  2. Cache dengan TTL pendek. Query flag per-request ke database itu pemborosan, apalagi kalau kamu sudah pernah mengalami masalah kehabisan koneksi database. TTL 10 detik artinya perubahan flag aktif maksimal 10 detik kemudian — kompromi yang saya terima.

Untuk proyek yang lebih besar, saya sarankan langsung pakai sistem flag yang matang seperti Unleash yang mendokumentasikan prinsip scaling flag dengan baik, atau pertimbangkan OpenFeature, API vendor-agnostic untuk feature flagging supaya kamu tidak terkunci ke satu vendor.

Ilustrasi cache flag dan evaluasi deterministik berdasarkan hash user ID

Kesalahan yang Paling Sering Saya Lihat (dan Pernah Saya Lakukan)

Flag yang tidak pernah mati

Ini penyakit nomor satu. Enam bulan setelah rilis, kode masih penuh if (isNewBillingFlow). Setiap flag yang hidup adalah kompleksitas kombinatorik yang harus dites. Dokumen best practice Unleash menyarankan siklus hidup flag yang eksplisit, dan saya setuju total — sekarang setiap PR yang menambah flag wajib menyertakan issue "hapus flag X" dengan tenggat. Aturan saya: flag eksperimen hidup maksimal dua sprint, flag ops/kill-switch boleh lebih lama tapi harus terdaftar.

Flag bercabang di dalam bercabang

if (flagA && !flagB) tersebar di 12 file adalah mimpi buruk pengujian. Kombinasi yang harus dites tumbuh eksponensial. Kalau saya mulai butuh logika flag bersarang, itu sinyal fiturnya terlalu besar dan harus dipecah.

Ngerasa flag menggantikan branch dan testing

Flag memperkecil risiko rilis, bukan risiko kode. Fitur ber-flag tetap perlu unit test pada kedua kondisi (on dan off). Saya pernah skip test kondisi off karena "kan pasti on nanti" — dan persis kondisi off itulah yang jadi jalur rollback saat produksi bermasalah. Jalan yang tidak dites adalah jalan yang akan gagal saat paling kritis.

Kapan Feature Flag Tidak Layak

Saya tidak mau terdengar seperti fanboy. Ada kasus di mana flag justru menambah risiko:

  • Migrasi skema destruktif. Rename kolom atau drop tabel tidak bisa di-flag; itu wilayahnya strategi soft delete vs hard delete dan migrasi bertahap, bukan flag.
  • Perubahan kecil yang sepele. Memperbaiki typo di teks email lewat flag adalah over-engineering murni. Biaya flag (kode bercabang, testing ganda, kebersihan) harus sebanding dengan risiko yang dia turunkan.
  • Tim satu orang dengan traffic mini. Kalau rollback kamu cuma butuh dua menit dan penggunanya tiga orang, git revert sudah lebih dari cukup.

Selain itu, flag menambah latensi evaluasi (minimal lookup cache) dan beban kognitif. Di sistem multi-tenant, hati-hati juga granularitasnya — flag per-tenant yang saya bahas mirip dengan problem isolasi di perbandingan RLS vs schema-per-tenant: makin banyak dimensi, makin sulit menjawab "tenant X sekarang lihat versi apa?".

Rutinitas Rilis yang Saya Pakai Sekarang

Konkritnya begini, dari deploy sampai penuh:

  1. Deploy dengan flag off. Verifikasi metrik dasar 30–60 menit. Kode mati di produksi tidak seharusnya mengubah apa pun — kalau berubah, itu bug tersendiri yang harus ditangkap di sini.
  2. Nyalakan untuk internal. Tim sendiri jadi pengguna pertama (dogfooding), biasanya lewat flag dengan target user ID tertentu.
  3. Buka 1%, lalu naikkan. Di tiap kenaikan, saya lihat error rate dan latency di dashboard selama minimal satu siklus traffic puncak. Angka spesifik saya: interval antar kenaikan minimal 1 jam, dan kalau error rate naik di atas 0.5% absolut, berhenti dan investigasi.
  4. 100% + observasi 48 jam. Setelah itu hapus kode cabang flag di PR terpisah.

Total memang lebih lama daripada deploy-langsung-penuh. Tapi bandingkan dengan total waktu downtime, hotfix, dan post-mortem dari dua insiden yang saya alami — proses bertahap ini jauh lebih murah.

Takeaway

Kalau kamu mau mulai besok, ambil versi minimalnya dulu: satu tabel, satu evaluator dengan hash deterministik, satu fitur yang sedang kamu takut-takutkan rilisnya. Jangan langsung beli SaaS atau bangun sistem flag lengkap sebelum merasakan trade-offnya sendiri. Yang tidak boleh ditawar cuma dua: flag harus bisa dimatikan dalam hitungan detik tanpa deploy ulang, dan harus ada disiplin menghapusnya setelah fitur stabil. Sisanya — tooling, persentase, targeting — bisa berevolusi seiring kebutuhan.

Sumber

TH

Tomi Hartanto

Senior Software Engineer

Backend developer dengan 8+ tahun pengalaman membangun sistem scalable. Menulis tentang arsitektur backend, database, dan DevOps.