logo
Publicado el

Feature Flag Kullanımının Görünmeyen Maliyetleri 📍

Feature Flag Kullanımının Görünmeyen Maliyetleri 📍

Feature Flag Kullanımının Görünmeyen Maliyetleri 📍

Feature flag kavramı artık neredeyse her projede, her kodda, her mimari kararda bir şekilde önüme düşüyor. İlk başta gözüme basit bir çözüm gibi görünüyordu, ama bir noktadan sonra “acaba bu aç kapa olayı düşündüğüm kadar masum mu?” diye sorgulamaya başladım. Araştırmaya başlamamla birlikte zaten birileri benden önce bu konulara çoktan yelken açtığını gördüm. Bu yazı da, biraz bunların derlemesi şeklinde olacak.

Kod değişikliği yapmadan davranış değiştirebiliyorsun. Yeni bir özelliği sadece belirli bir kullanıcı grubuna açabiliyorsun. Belki de o an aktif olmayacak ama bir kaç sprint sonra aktif olacak kodu bugünden yazabiliyorsun. A/B testlerde, gradual rollout’ larda, canary deploy’ larda her yerde görebiliyorsun. Ve bunların hepsini bir `if` bloğuyla, bir “flag” sayesinde kontrol edebiliyoruz. Kulağa harika geliyor. Ve büyük ihtimalle zaten feature flag'ler her geliştiricinin cebinde olan tekniklerden birisidir.

Bu kadar hayat kurtarıcı görünen popüler bir teknik, neden zamanla kodu okunamaz hâle getiriyor? Neden “o flag açıkken çalışıyor ama kapalıyken patlıyor” cümlesini bu kadar sık duyuyoruz? Veyahut bir hata logunu tracing neden bu kadar zaman alıyor?

Bu sebeplerden yola çıkarak yalnızca “ feature flag nedir” i konuşmayacağız. Bu sevimli dostumuzun kuyruğuna taktığı bal kabağını inceleyeceğiz. Evet, feature flag güçlü bir teknik… ama kontrolü kaçırırsan?

Hazırsak başlayalım.

Feature Flag Nedir, Neden Bu Kadar Popüler?

Şimdi olayı biraz somutlaştıralım. Bir tren hayal edin. Doğal olarak bu tren raylar üzerinde hareket ediyor. Siz de bu raylardaki makasları yöneten oparatörsünüz. İşte bu senaryoda yer alan ray makasları bizim feature flag'lerimiz oluyor. Rayın yapısı bozulmuyor. Tren'in varacağı yer aynı olsa bile; gideceği yol tamamen farklılaşabiliyor.

Teorik olarak açıklayacak olursak; uygulamamızın kodunu değiştirmeden bir feature veya bir functionality'i aktif edebiliriz. Buna ayrıca feature toggles, switches, flippers, ya da  bits ler de denebiliyor. Karşınıza "şöyle toggle eklesek" diye çıkarsa veya toggles diye bir config variable görürseniz şaşırmayın.

Kafada hala oturmadıysa; gerçek bir kurgudan devam edelim;

Bir e-Ticaret firmasında continues delivery pratiklerini uygulayan takımlardan birisiniz. Geçmişte yaşanılan uzun ömürlü branch'lerin mergelenmesi konusunda oldukça ızdırap çekmiş bu ekipte, geçmiş tecrübelerini yaşamamak adına, trunk-based development uyguladığınızı varsayalım. Mümkün olduğunca branchlere dallanmadan geliştirme yapıyorsunuz.

Bu sırada, kullanmakta olduğunuz payment akışlarında, kullanılan teknolijinin de değiştiği yeni bir akış geliştirmesi yapılacak. Bu noktada, yeni geliştirme üzerinde çalışan ekibimizin, geri kalan ekiplerin işleyişini bozmaması ve de code-base'in dengesini bozmaması gerekiyor.

Geliştirme yapılacak ama normal akış bozulmayacak... Ekip bu nedenle; Feature Toggle kullanmaya karar veriyor.

// Example of a feature flag controlling the new payment flow
package main

import (
    "fmt"
)

func main() {
    isNewPaymentFlowEnabled := false // it may come from your env

    if isNewPaymentFlowEnabled {
        showNewPaymentFlow()
    } else {
        showOldPaymentFlow()
    }
}

func showNewPaymentFlow() {
    fmt.Println("The new payment flow is displayed.")
}

func showOldPaymentFlow() {
    fmt.Println("The old payment flow is displayed.")
}

İlk başta, geliştirmeler tamamlanana kadar bu Feature Toggle'ın uygulamalarına misafir olacağı aşikar.

İşte olası, feature toggle kullanma ihtiyacının çıkışını ve örnek bir uygulamasını gördük. Oldukça da mantıklı bir karar olduğuna herkes hemfikirdir.

Geliştirici ekibimiz, test ortamında (bu local ortam da olabilir) testlerini bu flag açık şekilde gerçekleştirdi. Tüm testler başarılı olduğunda belirli kullanıcılara açarak A/B testlerini gerçekleştirdi ve canlıya sorunsuz geçişlerini yaptılalar. Mutlu son.

Feature Flag Türleri ve Kullanım Senaryoları

Aslına bakılırsa yukarıda release feature flag için bir örnek vermiş olduk. Unutulmamalıdır ki; her ihtiyaç, kendi dilinde konuşan bir çözüm ister. Bu sebeple; biz feature flag deyip geçsek de arkasında flag'leri oluşturan nedenler ve ele alış şeklimiz feature flag'leri farklı çatılarda kategorize etmemizi sağlıyor.

1- Release Toggles

Release Toggles, geliştirme sırasında tamamlanmamış özelliklerin ana dalda tutulmasına izin veren bir tekniktir. Bu sayede kod üretim ortamına gönderilebilir, ancak özellik kullanıcıya gösterilmeden ya da diğer entegrasyonları etkilemeden sistemde pasif olarak kalır.

Release Toggle'lar doğası gereği geçicidir. Product-centered toggle'ların daha uzun süre yerinde kalması gerekebilse de, genellikle bir veya iki haftadan daha uzun süre kalmamalıdırlar. Bir Release Toggle'ın geçiş kararı tipik olarak çok statiktir. Belirli bir sürüm için her geçiş kararı aynı olacaktır ve bu geçiş kararını bir toggle config değişikliği ile yeni bir sürüm sunarak değiştirmek genellikle kabul edilebilir.

2- Experiment Toggles

Experiment Toggles, çok değişkenli veya A/B testi gerçekleştirmek için kullanılır. Sistemin kullanıcıları bir küme(cohort) içerisine yerleştirilir. Ve istenen kullanıcı kümesine farklı akışlar sunulabilir. Böylelikle datayı karşılaştırabiliriz.

Anlamlı bir istatistik verisi oluşturabilmesi için yeterince sistemde kalmaları gerekir. Bu sistemin aldığı trafikle ters orantılıdır. Düşük traffic alan sistemde haftalarca kalabilirken, yüksek trafikli sistemlerde bu süre günlere düşebilir. Diğer geliştirmeler de bu istatistiği etkileyeceğinden süreci çok da uzatmamakta fayda olacaktır.

3- Ops Toggles

Ops toggles, isminden de anlaşılacağı üzere sistemimizin davranışını operasyonel olarak yönetmemizi sağlayan flag'lerdir. Bu flag'leri performans etkilerini tam olarak belirleyemediğimiz caselerde kullanıldığını görürüz. Bu amaçla kullanılan flag'ler, yeni eklenen özelliğin performansına güvendiğimiz takdirde kaldırılması gerekir ve kısa ömürlü olmalıdırlar. Bunun aksine bir de uzun ömürlü olanlar vardır. Yüksek yük altında hayati olmayan veya etkileri konusunda el sıkışıldığı bazı özellikleri bypass etmek için kullanılan "kill-switch"leri  de görmemiz hiç de şaşırtıcı değildir. . Atıyorum bir kampanya döneminde bir e-ticaret firması alacağı yüksek yük için nispeten daha pahalı olan fraud kontrollerinden vazgeçebilirler. Bu tür uzun ömürlü Ops Toggles, manuel olarak yönetilen bir "Circuit-Breaker" olarak görülebilir.

Bu bayrakların amacı production sorunlarına hızlı bir şekilde tepki verilmesini sağlamak olduğundan, son derece hızlı bir şekilde yeniden yapılandırılmaları gerekir. Bu toggle'ı yönetmek için yeni bir deployment yapılması bizleri çok da mutlu etmeyecektir. Hele ki; bir freeze varsa.

4- Permissioning Toggles

Bu toggle'lar, belirli kullanıcıların aldığı özellikleri veya ürün deneyimini değiştirmek için kullanılır.Eminim hepimizin bir ChatGPT üyeliği vardır. Burada 3 farklı plan sunuluyor. Free, Plus ve Premium plan. Diyelim ki; OpenAI,  5v versiyonunu sadece premium kullanıcılar demo olarak aktif edecek ve istenildiğinde bu kısa süreli demo iptal edilecek. Bunu şu şekilde yönettiğimizi görebiliriz:

if (user.hasRole(plans.Premium) && toggles.enableVersion5) {
  showVersion5();
}

enableVersion5 flag'i, yalnızca premium kullanıcılara açık olan bu özelliği yönetmenin bir yolu olarak kullanıldığında, diğer flaglere göre daha uzun ömürlü olmaları beklenebilir. Bu yıllar ölçeğinde olabilir(DeepSeek korkusu version upgrade'i hızlandırmazsa 🤫). İzinler kullanıcıya özgü olduğundan, bir Permissioning Toggles'ın geçiş kararı her zaman request başına olacağından, bu flag'imizi dynamic hale getirir.

You tested before deploying, right?

Feature Flag’leri Yönetmenin 5 Yolu

Nasıl yönetiriz kısmını çok detaylandırmayacağım. Lakin, yukarıdaki bahsettiğimiz tipleri yorumladığımız takdirde kafamızda canlanan pek çözüm olması olasıdır. Hangisini hangi amaçla kullanacağım kısmını zaten yukarıda konuştuk. Bir kaç haftalık projede kalacak bir toggle için dynamic bir yapı kurmak; sürekli değişen bir flag'i hard coded tutmak kadar mantıksız olacaktır.

Genel bir persfektif sunması açısından bunlardan bazılarını başlıklar halinde açıklayalım.

Parameterized Toggle Configuration

Özelliğin açılıp kapanması kullanıcı, ortam, zaman gibi parametrelere göre dinamik olarak belirlenir.

Hard-coded Configuration

Toggle değeri en başta verdiğimiz örnekte olduğu gibi, doğrudan kod içinde sabit olarak tanımlanır ve değiştirmek için kodu güncellemek gerekir.

Configuration File

Toggle değerleri uygulama başlatıldığında okunan dışardan alınan yapılandırma dosyalarında (örneğin .json, yaml, env) tutulur.

Distributed Toggle Configuration

Toggle’lar merkezi bir sistemden (API, remote config servisi, LaunchDarkly, Unleash, ConfigCat gibi) gerçek zamanlı olarak çekilir ve yönetilir. Burda availability oldukça önemlidir. 

Toggle Configuration in App DB

Toggle bilgileri uygulamanın kendi veritabanında saklanır, bu sayede canlı sistemde kolayca güncellenebilir. 

Gizli Maliyetler: Feature Flag’in Görünmeyen Yüzü

Teorikte; teorik ve pratik arasında bir fark yoktur. Ama; pratikte vardır. 

Gerçek dünya senaryosunda; bir feature flag ekleniyorsa, bu Release Flag dahi olabilir, uzun zamanlar kodla birlikte hayatına devam ediyor. Ufak bir if'in kimseye zararı olmaz diyerek eklenen kod parçası, gizli sorunlara zemin hazırlayarak kodlarımızı içten çürütüyor. Resmin bütününe, farklı perspektiflerden bakarak olayları incelersek ipin sonunda nelerle karşılaşabileceğimizi kestirebiliriz. 

Kodun Okunabilirliğine Etkisi

Bir koda eklenen her if, yeni bir yol ayrımıdır. Ve her yol, farklı bir kullanıcı deneyimi sunar. Bu da, kodu okuyan geliştirici için zihinsel yük anlamına gelir. Çünkü artık sadece kodun ne yaptığına değil, hangi koşulda ne yaptığına da hâkim olması gerekir.

Kodu okuyan biri için, tüm bu olası yolları zihninde canlandırmak kolay değildir. Bazen olay örgüsü öyle dallanır ki, takip etmek ve anlamak neredeyse imkânsız hale gelir.

Bu durumu özellikle kural setlerinde (rule engine) net bir şekilde görürüz. Domain bilgisi ne kadar yüksek olursa olsun, geliştiricinin flag kontrollü bir logic'i okuduğunda “bu ne yapıyor?” sorusuna hemen yanıt veremediği olur. Çünkü farklı kombinasyonlar, birden fazla anlam taşır ve zihni yorar.

Debugging Kabusu: Feature Flag’lerin Takibi

Bir gün nöbetçiyken başıma gelen bir olaydan bahsetmek istiyorum.

X servisten gelen alert'ler üzerine hata log'larını inceliyordum. Sonrasında hatanın geldiği yeri kontrol ettiğimde logiclerde sorun olmadığını görüyordum ama bir sıkıntılı durum vardı tabi. O anın heyecanı ile olacak, isEnable kısmını atlayarak total kodu incelemiştim ve kod oldukça iç içe methodlardan oluşuyordu. Sınıftan sınıfa, method'tan methoda kontrol ederek ilerlemiş ve yarım saatin sonunda elim boş kalmıştı. Çünkü; bu kod çalışmıyordu. Loglar da yeterli değildi.  Aklıma en son buradaki isEnable flağini kontrol etmek gelmişti. Büyük projelerde gerçekten samanlıkta iğne gibi oluyorlar. Ve evet, bu flag atıl bir flag'ti. Masum gibi gözüken ufak bir if, benim yarım saatime ve biraz da mental sağlığıma sebep olmuştu.

Feature flag’ ler genellikle küçük bir if koşulu gibi başlar. Ancak zamanla bu flag’ ler yayılır, kontrol noktaları çoğalır ve bir süre sonra kodun gerçek çalışma akışını tahmin etmek neredeyse imkânsız hale getirir. Hangi durumda ne çalışıyor? Hangi flag hangi ortamda açık? Hangi kombinasyonlar test edildi? Bunlar cevabı kolay sorular değildir.

Benim başıma gelen olayda da olduğu gibi, bir feature flag’ in pasif halde olması bile kodu anlamayı ve hatayı teşhis etmeyi ciddi anlamda zorlaştırabiliyor. Özellikle loglama yeterli değilse, tracing sistemleri flag'e özel veri sunmuyorsa ya da observability zayıfsa, flag’ ler geliştirici için adeta karanlıkta kalan bir yol ayrımı olur.

Takibi zorlaştırmamak için Loglar en basit çözüm olacaktır. Ayrıca, hangi tracing tooling'i kullanıyorsanız(open-telemetry vs) span eklemeyi unutmayın.

Refactoring Neden Zorlaşır?

Refactoring süreci, bir sistemin davranışını değiştirmeden yapısını sadeleştirmeyi hedefler. Ancak feature flag’ ler, kodun hangi koşulda nasıl çalıştığını anlamayı zorlaştırarak bu süreci karmaşık hale getirir. Başlangıçta geçici olarak eklenen bu flag’ ler, çoğu zaman kalıcı hale gelir ve sistemin evrilmesini engelleyen görünmez bariyerlere dönüşür.

Bir geliştirici olarak bir sınıfı sadeleştirmek ya da bir modülü redesign etmek istediğinde, kafanda sorular belirmeye başlar:

  • Bu flag hala kullanılıyor mu?

  • t anında tekrar kullanılmak istenir mi?

  • Hangi ortamda aktif?

  • Silersem production’ da bir şeyi bozar mıyım?

Bu soruların cevaplarını almak çoğu zaman log'lara, dashboard’ lara ya da sadece takım arkadaşlarının hafızasına bağlıdır. Ve ne yazık ki; çoğu flag için bu bilgiler ya güncel değildir ya da hiç yoktur. Ve sırf bu belirsizlikler nedeniyle refactoring ertelenir. Dolaylı yoldan technical depth oluşturmaya başlar. 

Önerim; her feature flag'in bir expiration date'i olmalıdır ve kaldırılacağı sprinti önceden planlamanızdır. Refactoring'e başlanmadan flag'lerin kaldırılarak uygulamayı stabil hale getirmeniz de bu yükü ortadan kaldıracaktır.

Test Süreçlerine Etkisi ve Combinatoric Patlama

Bir özelliği test etmek zaten başlı başına zahmetliyken, işin içine bir de flag girince senaryo sayısı katlanmaya başlıyor. Bir özelliğin flag açıkken ve kapalıyken nasıl davrandığını test etmek gerekiyor. Bir de birden fazla flag, aynı yerde kullanılıyorsa; geçmiş olsun. Bu  2n2^n  tane kombinasyona (Örn: A, B, C flag’leriyle 8 kombinasyon olur) test yazılacak demektir.

  • A ve B flag'leri açıksa
  • A flag'i açık, B flag'i kapalıysa
  • A flag'i kapalı, B  flag'i açıksa
  • A ve B flag'leri kapalıysa

Çoğu zaman testler, projelerin en yavaş kodlarıdır. Her ne kadar parallel koşulsa da aynı anda limitli test çalıştırılır. Bu nedenle CI pipeline'da yavaşlayacaktır. 

Ayrıca QA her senaryoyu test etmeyebilir. Çünkü; hangi kombinasyonun geçerli olduğunu anlamak kolay olmayabilir. Bu da doğrulanmamış kodların production'a alınmasına ve olası incident'lara kapı açacaktır.

Her bir case için test eklerken Parameterized testlerden faydalanmak uzun testleri ve dublikelerinin önüne geçecektir. QA'den test edilmeden geçilmemesi için önlem açısından Acceptance criteria belirlerken bu Flaglerin test edilmesi için checklist oluşturmak da QA'den test edilmeden geçilmesi için önlem alınmış olacaktır. 

Güvenlik Riskleri ve Kaybedilen Milyonlar

Feature flag'ler ile araştırma yaptığımızda en ikonik olaylardan birini görürüz.

2012 yılında ABD'li finans şirketi olan Knight Capital Group, yeni bir elektronik işlem algoritmasını piyasaya sürmek için feature flag sistemi kullanmış. Ancak eski bir flag yanlışlıkla yeniden aktif hale getirilmişti. Sonuçta yeni (hatalı) kod, sürecin geri kalanıyla uyumsuz çalışınca sabah açılışında piyasada 4 dakika içinde 440 milyon dolar zarar oluşmuştu. 

Ayrıca olası bir experiment Toggles ya da local ortamda uğraştırıcı yetki problemlerini devre dışı bırakmak için eklenmiş flaglerin eklenmesi, flagler'in client side saklanması da çeşitli güvenlik zafiyetlerine neden olmaktadır.

Geçtiğimiz günlerde, Cursor bir geliştirme yapmak nedeniyle öğrenciler için free license verme opsiyonunu Türkiye için devre dışı bırakmıştı. Fakat bunu sadece ui üzerinde kapatmışlardı. Bunu keşfeden öğrenciler bu config dosyasını ezerek listeye Türkiye'yi eklemeyi başarmış ve lisanslarını almışlardı. Bunun benzerini önceki senelerde XBOX game pass üzerinde de görmüştük. Bunlar tam olarak configuration işlemleri olsa da feature flagler için de yaşanabilir diye örnek göstermek istedim.

Feature flag'leri nerde yönettiğinize bağlı olarak yetkisiz kişilerce aktif veya pasif edilerek sizi bir felakete de sürükleyebilir.

Zihinsel Yorgunluk: Geliştiriciye Ekstra Yük

Belki de bu işin en atlanan kısımlarından birisi de budur. Kod akışını koşullara göre değiştiren her flag, geliştiricinin kafasında "eğer şuysa..." durumu oluşturur. Bu da her bir if ile birlikte kodun artık sabit değil, dinamik bir yapıya bürünmesine neden olur. Sonucunda, geliştiriciye geliştirmesini yaparken ya da bir bug'ı çözüyorken kafasında şu soruları doğurur:

  • Bu flag şu anda aktif mi?

  • Hangi ortamlarda açık, hangilerinde kapalı?

  • Hangi kullanıcı grubu bu flag’i görüyor?

  • Bu flag başka bir flag’le birlikte nasıl çalışıyor?

Bu tür sorulara cevap aramak için elbette bu geliştiricinin ekstra eforlar harcaması gerekir:

  • Daha fazla iletişim,

  • Daha fazla kod incelemesi,

  • Daha fazla konfigürasyon bilgisi,

  • Daha fazla test senaryosu,

  • Daha fazla debugging,

  • Daha fazla tracing,

Sonuç olarak, bir problemi anlamak ya da yeni bir özellik eklemek için gereken zihinsel çaba katlanır.

Şimdi kafada daha çok somutlaştıralım. Kodda şu satırı gördüğünü düşün:

func applyDiscountRule() {
    if featureFlagService.IsEnabled("useNewDiscountRule") {
        applyNewDiscountRules()
        return
    }
    applyLegacyDiscountRules()
}

  • İki farklı logic var,

  • İki farklı davranış testi gerekiyor,

  • İki farklı log/tracing yolu var,

  • Belki üç ay sonra bu flag'in ne işe yaradığını hatırlayan kimse kalmayacak,

  • Eğer ki; iyi design edilmemiş configuration 'ınız yoksa ortalık bayağı bir çorba olacak.

Ve geliştiricinin ekibinde yaklaşık 90 service olduğunu düşünürseniz bu durumun çok da iç açmadığına emin olabilirsiniz. Ayrıca ekibe yeni katılan bir geliştiricinin de onboarding sürecini daha zorlu hale getirebilirsiniz.

Bu nedenle feature flag kullanımı, sadece teknik değil aynı zamanda cognitive (zihinsel) bir yük getirir. Bu yükü azaltmanın yolları ise:

  • Flag lifecycle yönetimini, ne zaman silineceğini en başından belirlemek gereklidir. Ve mümkünse; bunu bir yerelere not almamız gereklidir.

  • Geliştiriciye net bilgi sunan tooling'ler(Unleash gibi) kullanılabilir. 

  • Gözlemlenebilirlik (log, trace, dashboard)  sağlamak da yine geliştirici üzerindeki baskıyı alacaktır.

Sonuç ve Uygulanabilir Öneriler

Uzun yaşam döngüsüne sahip uygulamalar oluşturmak isteniyorsak bu konu sadece bir flag olsa da bunu planlamamız gerekir. Verilmiş hızlı kararların, geri dönüşü ilk zamanlarda hissedilmeyebiliyor. Fakat, uzun vadede çeşitli sorunlarla karşı karşıya kalabiliyoruz. Ve eğer ki; bu günden sonra bir flag kullanma kararı almanız gerekirse aşağıdaki şekilde ilerlemeniz yararınıza olacaktır.

  • Bireysel bir Feature flag kararı almayın. Ekibinizle yapacağınız beyin fırtınası belki de en faydalısı olacaktır. İlk olarak, durumu anlayın. Neden flag gerekli onu açıklayın. Mutlaka Flag'in gerekliliğini sorgulamadan geçmeyin.
  • Her flag aynı değildir. Flag'inizi spesifik şekilde tanımlayabiliyor olmanız gereklidir.
  • Flag'inizi belirlerken; adını ve yaşamını ne zaman tamamlaması gerektiğini belirleyin. 
  • Flag'inizi görünür yapın. Loglarda ve çeşitli tracing toollarında o flag'in state'ini görebilmek çoğu takip problemlerinin önüne geçecektir.

Kod yazmak şiir yazmaya benzer. İkisini de bir başkası okuyacaktır. O nedenle, nasıl bir cümlede nokta önemliyse kodunuza da ekleyeceğiniz şeyler bir o kadar önemlidir. Kendiniz ve kendinizden sonraki developer arkadaşınızın mental sağlını düşünerek kod yazmanız dileğiyle. Sağlıcakla kalın.

Yararlanılan Kaynaklar