Czym są feature flagi? Jak je stosować w produkcji
Summary
Feature flagi oddzielają deployment od releasu: kod trafia do produkcji w stanie nieaktywnym, a udostępniasz go stopniowo, ograniczając zasięg awarii. Kluczowe są cztery typy flag (release, eksperyment, ops, uprawnienia), bramki SLO między etapami rollout oraz kill switch projektowany przed incydentem, nie podczas niego. Przy 500+ flagach w systemie, dług flagowy staje się poważnym ryzykiem operacyjnym wpływającym na latencję p99.
Czym są feature flagi? Jak oddzielić deployment od releasu
Feature flagi to warunkowe wyrażenia w kodzie aplikacji, które kontrolują, które ścieżki kodu uruchamiają się dla danego użytkownika, sesji lub środowiska -- bez konieczności nowego buildu. Flaga new_checkout_flow ustawiona na false dla 99% bazy użytkowników oznacza, że kod został wdrożony dwa tygodnie temu. Po prostu jeszcze jej nie włączono. Kontrakt, który ustanawiają, jest precyzyjny: deployment i release to dwa oddzielne zdarzenia. Dla każdego zespołu robiącego continuous deployment w skali, ten kontrakt jest fundamentem architektury.
Deployment i release to nie to samo zdarzenie
Większość zespołów uczy się tej różnicy po trudnym rollout. Feature wychodzi w środę po południu, coś w śladzie żądania zaczyna zachowywać się inaczej w czwartek rano, a gdy ktoś otwiera śledztwo, diff obejmuje cztery commity i dwie granice serwisów. Przypisanie przyczynowości w takim scenariuszu jest naprawdę trudne.
Feature flagi wymuszają precyzję. Gdy kod za flagą jest scalony i wdrożony, leży nieaktywny w produkcji. Potwierdzasz, że deployment się powiódł, obserwujesz metryki bazowe przez kilka godzin lub dni, sprawdzasz, czy nic się nie pogorszyło. Potem przełączasz flagę dla 1% ruchu. Masz teraz jedną przypisywalną zmienną. Jeśli wskaźnik błędów wzrośnie na ścieżce oznaczonej flagą, nie debugujesz diffa kodu. Przełączasz wartość konfiguracyjną.
To rozdzielenie to przede wszystkim argument o zasięgu awarii (blast radius). Release wpływający na 1% sesji, który idzie źle, można przywrócić w minutach. Release wpływający na 100% sesji, który idzie źle, to poważny incydent.
Mechanizm jest prosty. W TypeScript ewaluacja flagi wygląda mniej więcej tak:
const showNewCheckoutFlow = flagClient.variation(
'new_checkout_flow',
{ userKey: session.userId, custom: { plan: user.plan } },
false // domyślna wartość, gdy serwis flagowy jest niedostępny
);
if (showNewCheckoutFlow) {
return renderNewFlow(cart);
}
return renderLegacyFlow(cart);Wartość domyślna false to nie formalność. To zachowanie, które dostaną twoi użytkownicy, gdy serwis ewaluacji flag ma awarię sieci. Zdefiniuj ją świadomie, nie przez przypadek.
Cztery typy flag, które naprawdę mają znaczenie w produkcji
Nie wszystkie feature flagi służą temu samemu celowi operacyjnemu. Traktowanie ich jednakowo w bazie kodu i narzędziach zarządzania to pewna droga do zamieszania podczas incydentów i akumulacji długu flagowego.
Flagi release bramkują nowe funkcje podczas developmentu i rollout. Są z założenia tymczasowe: tworzone gdy praca na feature branchu się zaczyna, usuwane gdy funkcja osiąga 100% użytkowników i zespół potwierdził stabilność. Flagi release bez daty usunięcia i bez przypisanego właściciela stają się stałym elementem bazy kodu.
Flagi eksperymentów napędzają testy A/B i eksperymenty wielowariantowe. Są powiązane z identyfikatorami kohort analitycznych, a ich cykl życia jest ograniczony przez eksperyment. Gdy eksperyment się kończy, flaga odchodzi razem z nim. Częsty błąd: zachowanie zwycięskiego wariantu za flagą w nieskończoność, bo usunięcie jest "nieistotne". Dwa lata później flaga eksperymentu jest częścią krytycznej ścieżki i nikt nie pamięta, który wariant jest aktywny.
Flagi ops to kill switche i circuit breakery. W przeciwieństwie do flag release, są zaprojektowane jako stała infrastruktura. Flaga disable_ml_recommendations, która pozwala ominąć wolną warstwę inferencji ML gdy jej SLO się degraduje, jest czymś, co chcesz mieć dostępne o 3 w nocy bez czytania dokumentacji. Te flagi powinny ewaluować lokalnie, mieć dobrze udokumentowany fallback i być regularnie testowane w normalnych operacjach, a nie odkrywane pod presją.
Flagi uprawnień kontrolują dostęp według poziomu użytkownika, planu konta lub kohorty beta. Są długotrwałe z założenia. Ryzyko pomyłki: flagi uprawnień często wyglądają jak flagi release dla czytelnika, który nie zna historii. Jasna konwencja nazewnictwa jest tu ważniejsza niż gdziekolwiek indziej.

Procentowy rollout bez bramki SLO to tylko powolny deployment
Tu większość implementacji feature flag się zatrzymuje. Zespół platformy kabluje harmonogram rollout: 1% w poniedziałek, 5% we wtorek, 25% w środę, 100% w piątek. Dokumentuje to, udostępnia interesariuszom i nazywa progressive delivery.
Ale "progresywny" bez warunku walidacji na każdym etapie to odroczone ryzyko, a nie zredukowane ryzyko. Procentowy przełącznik kontroluje ekspozycję. Nie waliduje bezpieczeństwa.
To, co sprawia, że etapowy rollout ma sens operacyjny, to bramka SLO między każdym etapem. Przed przejściem z 5% do 25%, coś powinno odpowiedzieć: czy wskaźnik błędów na ścieżce oznaczonej flagą mieści się w budżecie SLO? Czy latencja p99 utrzymuje się w tym samym paśmie co grupa kontrolna? Czy budżet błędów spala się szybciej niż bazowa linia?
Jeśli żadne z tych pytań nie jest zainstrumentowane, harmonogram rollout to oś czasu, nie pętla walidacyjna.
Konfiguracja, która się sprawdza w produkcji: zdefiniuj dwa okna ewaluacji SLO. Krótkie okno (15 minut) wychwytuje szybkie awarie -- złe zapytanie do bazy danych, niezgodność schematu, regresję w krytycznej ścieżce. Dłuższe okno (24 godziny lub jeden pełny cykl ruchu) wychwytuje stopniową degradację -- wycieki pamięci, presję cache, przypadki brzegowe w segmentach ruchu o niskiej częstotliwości. Wymagaj, aby oba okna pokazywały zielony kolor przed jakimkolwiek zaawansowaniem. Jeśli którekolwiek okno jest naruszone, zatrzymaj rollout i zadzwoń do on-call.
Procent to pokrętło. Okno SLO to bramka. Oba są wymagane.
Kill switch: zaprojektuj go zanim go potrzebujesz
Kill switch to nie fallback. To decyzja projektowa pierwszej klasy, która powinna istnieć przed napisaniem pierwszej linii kodu funkcji.
Kill switch zaprojektowany pod presją to kill switch z niezbadanymi założeniami. Testujesz go po raz pierwszy podczas aktywnego incydentu, w sesji terminalowej otwartej z powiadomienia PagerDuty, z pięcioma osobami obserwującymi wątek Slack. To najgorszy możliwy moment, aby odkryć, że twoja flaga ops targetuje użytkowników po ID sesji, a serwis sesji jest aktualnie zdegradowany.

Trzy właściwości, które są obowiązkowe dla każdego kill switch przeznaczonego do użytku produkcyjnego:
Lokalna ewaluacja poniżej 50ms: sprawdzenie flagi nie może być wywołaniem sieciowym do zdalnego serwisu ewaluacji. Jeśli ewaluacja zależy od serwisu, który sam może być zdegradowany, masz zależność cykliczną w ścieżce reagowania na incydenty.
Jawna wartość fallback: co zwraca flaga, gdy serwis flagowy jest niedostępny? Musi być to udokumentowane, zdefiniowane w kodzie i przetestowane. "Cokolwiek jest domyślne w SDK" to nie odpowiedź.
Testowany pod awarią zależności: kill switche powinny być częścią rotacji chaos engineering. Waliduj je w scenariuszach, gdzie auth jest zdegradowane, gdzie sam serwis flagowy jest niedostępny, i gdzie latencja sieci do endpointu ewaluacji przekracza 2 sekundy.
Zespoły, które sprawnie wykonują reagowanie na incydenty, to te, które ćwiczyły. Kill switch jest częścią runbooka. Uczyń go nudnym.
Dług flagowy: dług techniczny, którego nikt nie wpisuje na roadmapę
Zespoły, które agresywnie adoptują feature flagi, często akumulują dług flagowy: flagi, które wypełniły swój cel, ale nigdy nie zostały usunięte. Funkcja wyszła, eksperyment się zakończył, beta dobiegła końca. Flaga pozostała.
Przy 50 flagach to drobna niedogodność. Przy 500 flagach w rozproszonym systemie to aktywne ryzyko operacyjne. Każda flaga to gałąź kodu, która wymaga utrzymania, testowania i rozumienia podczas badania incydentów. Deweloper próbujący zrozumieć awarię o 2 w nocy nie chce śledzić przez 40 gałęzi warunkowych, aby znaleźć tę właściwą.
Jeden wzorzec zaobserwowany w wielu zespołach platformy: middleware ewaluacji flag pojawiający się w pięciu najwyższych ramkach stosu dla latencji p99. Przyczyną w większości przypadków są przestarzałe flagi ze złożonymi regułami targetowania, ewaluujące dziesiątki warunków na żądanie, niosące ciężar decyzji podjętych dwa lata wcześniej, których nikt nie czuł się komfortowy usuwając.
Środkiem zaradczym jest organizacja, a nie technologia. Każda tworzona flaga powinna mieć trzy atrybuty: właściciela, typ (release, eksperyment, ops, uprawnienia) i oczekiwaną datę usunięcia. Flagi release powinny być usuwane w ciągu dwóch sprintów po pełnym rollout. Flagi eksperymentów powinny być usuwane gdy eksperyment się kończy, a nie "gdy ktoś znajdzie czas". Inwentarz flag powinien być audytowalny na żądanie i widoczny w dashboardach zdrowia inżynieryjnego.
Granularność targetowania: wymiar, który się łamie w skali enterprise
Większość platform feature flag obsługuje rollout procentowy i podstawowe targetowanie atrybutów użytkownika. Luka, która ujawnia się w skali enterprise, to granularność targetowania: zdolność do wyrażania reguł rollout wystarczająco szczegółowych, aby były użyteczne, bez stania się na tyle złożonymi, że stają się nierentowne.
Użyteczna hierarchia targetowania dla rollout produkcyjnych na poziomie zespołu platformy:
Poziom środowiska: produkcja, staging, preview. Pierwsza bramka, nie jedyna.
Segment infrastruktury: centrum danych, strefa dostępności lub klaster Kubernetes. Przydatny do izolowania geograficznego zasięgu awarii.
Konto lub tenant: dla platform SaaS B2B, rollout per-konto jest często bezpieczniejszy niż rollout per-procent-użytkownika, bo możesz obserwować pełny wzorzec ruchu konta zamiast statystycznej próbki.
Kohorta użytkowników: użytkownicy beta, wewnętrzni, power users według poziomu aktywności.
Atrybut sesji: przydatny dla flag eksperymentów, niebezpieczny dla flag ops.
Platformy, które implementują to dobrze (LaunchDarkly i Statsig są najczęściej cytowane przez zespoły SRE robiące to w tej skali) pozwalają na złożone komponowanie reguł bez wymagania czasu inżynierskiego do modyfikacji logiki targetowania podczas aktywnego rollout. Ta zdolność samoobsługi to różnica między 30-sekundową pauzą rollout a ticketem do zespołu platformy.
Platformy, które implementują to słabo, zmuszają do wyboru między granularnością targetowania a prostotą operacyjną. Ten kompromis ujawni się o 3 w nocy.
Co post-mortem wciąż odkrywa
Każdy post-mortem dla incydentu produkcyjnego związanego z funkcją zadaje ten sam zestaw pytań. Czy funkcja była bramkowana za flagą? Czy rollout był progresywny? Czy był warunek walidacji między etapami? Czy kill switch był testowany przed incydentem?
Jeśli wszystkie cztery odpowiedzi są twierdzące, incydent to problem kalibracji: progi ustawione zbyt luźno, reguły targetowania z przypadkiem brzegowym, zachowanie SDK podczas partycji sieci, które nie zostało uwzględnione. To są problemy rozwiązywalne zmianami konfiguracji i aktualizacjami runbooka.
Jeśli jakakolwiek odpowiedź jest przecząca, incydent to problem architektoniczny. Feature flagi to nie wygoda debugowania. To decyzja architektoniczna wdrożenia. Ta decyzja albo istnieje przed wysyłką funkcji, albo w ogóle nie istnieje.
Pytanie warte odpowiedzi przed następnym releasem: co by powiedział post-mortem o tym rollout, gdyby coś poszło nie tak dziś wieczór?