Konfigurasi dan Feature Flag

Control plane dinamis: konfigurasi terpusat dan pelepasan fitur bertahap.

Daftar isi

Konfigurasi terpusat dan feature flag mengubah perilaku aplikasi tanpa deploy baru, dengan memindahkan keputusan yang tadinya tertanam dalam kode menjadi nilai yang dibaca saat aplikasi berjalan. Topik ini membahas konfigurasi terpusat sebagai control plane dinamis, strategi pelepasan fitur bertahap, dan evaluasi rollout persentase yang deterministik, lengkap dengan simulasi dan demo interaktif yang dapat dicoba langsung.

Konfigurasi terpusat sebagai control plane#

Aplikasi selalu memuat dua jenis keputusan, yaitu keputusan yang berubah bersama rilis kode, seperti algoritma yang dipakai, dan keputusan yang berubah jauh lebih sering, seperti batas waktu (timeout) panggilan ke layanan lain atau jumlah percobaan ulang. Menanamkan jenis kedua ke dalam kode berarti setiap penyetelan menuntut rilis penuh, padahal nilainya mungkin perlu diubah dalam hitungan menit saat kejadian darurat.

Control plane konfigurasi menjawab kebutuhan itu dengan menyimpan nilai konfigurasi di satu tempat dan menyebarkannya ke seluruh data plane, yaitu instance aplikasi yang sedang melayani trafik. Aplikasi mengambil nilai melalui polling berkala atau menerima dorongan (push) dari layanan konfigurasi, kemudian menerapkannya tanpa dimulai ulang. Nilai lazim disusun berjenjang, misalnya nilai default aplikasi, lalu nilai khusus satu layanan, sampai nilai khusus satu pengguna, dan nilai yang paling spesifik menang.

Keandalan menjadi persoalan utama karena aplikasi kini bergantung pada layanan lain saat membaca konfigurasi. Praktik yang lazim adalah menyimpan nilai terakhir yang berhasil diambil di memori lokal, sehingga aplikasi tetap berjalan dengan nilai lama apabila layanan konfigurasi tidak terjangkau.

Feature flag dan rollout bertahap#

Feature flag adalah percabangan perilaku yang keputusannya dibaca dari konfigurasi, sehingga sebuah fitur dapat dinyalakan atau dimatikan tanpa mengubah kode. Nilai kembalinya berupa keputusan biner per pengguna, misalnya fitur pencarian baru aktif atau tidak, dan keputusan itu dievaluasi setiap kali fitur dipakai.

Strategi pelepasan yang lazim adalah rollout persentase. Fitur pertama kali aktif untuk sebagian kecil pengguna, misalnya lima persen, lalu persentasenya dinaikkan bertahap setelah indikator error dan latensi diamati aman. Apabila masalah muncul, persentase diturunkan atau fitur dimatikan sepenuhnya, yang dikenal sebagai kill switch, dan langkah itu selesai dalam hitungan detik tanpa rilis. Agar percobaan ini bermakna, keputusan per pengguna harus stabil, yaitu pengguna yang sama tidak boleh pindah pihak setiap kali fitur dievaluasi, karena pengalaman yang berubah-ubah sulit diamati dan menurunkan kepercayaan.

Stabilitas itu dicapai dengan pengbucketan deterministik, yaitu menghitung posisi pengguna dari identitasnya sendiri. Pengguna tidak disimpan ke dalam daftar rollout, melainkan ditempatkan pada salah satu dari 100 bucket berdasarkan hash ID-nya, dan rollout 30 persen berarti semua bucket bernilai di bawah 30 dinyalakan. Menaikkan persentase hanya memperluas batas bucket, sehingga pengguna yang sudah aktif tetap aktif.

Evaluasi rollout yang deterministik#

Prinsip bucketan ini cukup disimulasikan tanpa layanan apa pun. Program berikut menghitung bucket pengguna dari ID-nya dengan rumus aritmetika tetap, lalu mengevaluasi flag dengan rollout 30 persen untuk sepuluh ID pertama dan mengukur sebarannya pada seratus ribu ID.

python rollout-flag.py
"""Evaluasi feature flag dengan rollout persentase yang deterministik.

Bucket pengguna dihitung langsung dari ID pengguna, sehingga keputusan
yang sama selalu berlaku untuk ID yang sama tanpa perlu menyimpan status.
"""

NAMA_FLAG = "fitur-pencarian-baru"
PERSEN = 30


def bucket_pengguna(id_pengguna):
    nilai = (id_pengguna * 48271 + 11) % 10000
    return nilai / 100


def fitur_aktif(id_pengguna, persen=PERSEN):
    return bucket_pengguna(id_pengguna) < persen


print(f"Flag {NAMA_FLAG} dengan rollout {PERSEN} persen:")
for id_pengguna in range(1, 11):
    bucket = bucket_pengguna(id_pengguna)
    status = "AKTIF" if fitur_aktif(id_pengguna) else "nonaktif"
    print(f"  pengguna {id_pengguna:>2}: bucket {bucket:>6.2f} -> {status}")

jumlah = 100_000
aktif = sum(fitur_aktif(i) for i in range(1, jumlah + 1))
print()
print(f"Distribusi pada {jumlah} ID pertama: {aktif} aktif "
      f"({aktif / jumlah * 100:.2f} persen)")
text Keluaran
Flag fitur-pencarian-baru dengan rollout 30 persen:
  pengguna  1: bucket  82.82 -> nonaktif
  pengguna  2: bucket  65.53 -> nonaktif
  pengguna  3: bucket  48.24 -> nonaktif
  pengguna  4: bucket  30.95 -> nonaktif
  pengguna  5: bucket  13.66 -> AKTIF
  pengguna  6: bucket  96.37 -> nonaktif
  pengguna  7: bucket  79.08 -> nonaktif
  pengguna  8: bucket  61.79 -> nonaktif
  pengguna  9: bucket  44.50 -> nonaktif
  pengguna 10: bucket  27.21 -> AKTIF

Distribusi pada 100000 ID pertama: 30000 aktif (30.00 persen)

Distribusi tepat 30,00 persen karena rumus (id * 48271 + 11) % 10000 menyapu seluruh nilai 0 sampai 9999 secara merata pada seratus ribu ID pertama, sehingga setiap bucket ditempati tepat sepuluh kali. Sifat yang lebih penting daripada keadilan sebarannya adalah determinisme, yaitu pengguna 5 selalu berada di bucket 13,66 dan tetap aktif ketika persentase dinaikkan dari 30 ke 40. Pengguna 4 yang berada di bucket 30,95 baru aktif ketika batas melewati 30,95, dan tidak pernah kehilangan akses setelahnya.

Demo interaktif#

Evaluator yang sama dapat dicoba secara langsung. Atur persentase rollout pada penggesernya, masukkan sebuah ID pengguna, lalu amati keputusannya beserta sebaran pada dua belas ID pertama.

Dua hal patut diamati pada demo. Pertama, keputusan hanya bergantung pada ID dan persentase, sehingga ID yang sama selalu menghasilkan status yang sama pada persentase yang sama. Kedua, menaikkan persentase hanya mengubah warna chip yang bucketnya berada di bawah batas baru, dan tidak ada chip yang berubah dari hijau menjadi kelabu ketika persentase naik, persis seperti jaminan pada simulasi sebelumnya.

Kesalahan umum#

  • Flag menumpuk tanpa pembersihan. Setiap flag menggandakan kemungkinan jalur kode, dan dua puluh flag berarti jutaan kombinasi keadaan yang tidak semua teruji. Perbaikannya adalah menetapkan batas umur flag sejak awal dan mencatat setiap flag aktif beserta pemiliknya.
  • Rollout diuji hanya melalui satu akun milik tim. Akun itu mungkin selalu aktif sejak persentase nol sehingga perilaku peralihan tidak teramati, padahal evaluasi bucket justru dirancang untuk menunjuk pengguna yang berbeda-beda.
  • Membaca konfigurasi per permintaan langsung dari layanan konfigurasi. Apabila layanan itu lambat atau mati, seluruh aplikasi ikut lambat atau gagal. Perbaikannya adalah cache lokal dengan umur pendek dan nilai default yang aman.

Ringkasan#

  • Konfigurasi terpusat menjadi control plane dinamis yang menyebarkan nilai ke seluruh instance tanpa deploy, dengan jaring pengaman berupa cache lokal dan nilai default di dalam kode.
  • Feature flag dengan rollout persentase menaikkan dan menurunkan jangkauan fitur dalam hitungan detik, dan keputusan stabil karena bucket pengguna dihitung deterministik dari ID-nya.
  • Rollout 30 persen pada simulasi menghasilkan tepat 30,00 persen pengguna aktif, dan menaikkan persentase tidak pernah mencabut akses yang sudah diberikan.

Referensi#