awesome-everything EN
↑ Обратно к восхождению

Инженерная практика

Feature flags: расцепить деплой и релиз, не утонув в долге флагов

Суть Флаги шипят код тёмным и релизят тумблером, покупая постепенный выкат и мгновенный kill-switch. Цена сеньора: каждый флаг — живая ветка в проде, а забытый забрал у Knight Capital $440M за 45 минут.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на junior-высоте — поверхность
◷ 16 min

1 августа 2012 года Knight Capital задеплоил новый код на 7 из 8 своих торговых серверов. Новый код переиспользовал старый флаг с именем Power Peg — тестовую фичу 2003 года, мёртвую годами. На восьмом, незапатченном сервере переключение этого флага разбудило мёртвый код. За 45 минут он выстрелил 4 миллионами ошибочных сделок по 154 акциям и потерял примерно $440 миллионов — больше рыночной капитализации фирмы. Устаревший флаг, оставленный в кодбазе и переиспользованный, прикончил компанию из 1400 человек.

Деплой — не релиз

Первый рефрейм сеньора: задеплоить код и зарелизить фичу — два разных события. Без флагов они сварены вместе — мерж, попавший в main, и есть момент, когда пользователи видят изменение, так что каждый рискованный запуск становится высокоставочным деплоем в 2 ночи под взглядом всей команды. Feature flag их разделяет. Ты деплоишь код «тёмным» (присутствует в проде, обёрнут в if (flags.newCheckout) {...} и возвращает false для всех), потом релизишь позже переключением флага — без пересборки, без редеплоя.

Это разделение меняет то, как команды шипят. Код мержится непрерывно и мелко (trunk-based development перестаёт зависеть от долгоживущих веток), незаконченные фичи безопасно сидят за выключенным флагом, а релиз становится изменением конфига, что SDK подхватывает за секунды. Клиентский спек Unleash поллит состояние флага на дефолтном интервале 15 секунд; LaunchDarkly стримит обновления по SSE, так что тумблер распространяется на работающие серверы заметно меньше чем за секунду. Релиз перестаёт быть деплоем и становится решением.

Четыре типа флагов — и почему их жизненные циклы различны

«Feature flag» прячет четыре разных вида, и ошибка сеньора — обращаться со всеми одинаково. Тип диктует, как долго флаг должен жить и кто им владеет.

ТипНазначениеСрок жизниВладелец
releaseГейтит фичу в процессе; постепенный выкатДни–недели — удалить после 100%Команда разработки
ops / kill-switchОтключить подсистему под нагрузкой или в аварииПостоянный — держат нарочноSRE / on-call
experimentОтдавать A/B-варианты, мерить метрикуОдин цикл эксперимента — потом удалитьПродукт / данные
permissionГейтить по плану, роли или правуДолгоживущий — привязан к модели продуктаПродукт

Release-флаг, которого никогда не удалили, тихо стал долгом флагов. Kill-switch, который кто-то «прибрал», потому что он выглядел устаревшим, убрал твою страховочную сетку. Тот же механизм, противоположная правильная судьба — ровно поэтому тип надо записывать, не угадывать.

Постепенный выкат и мгновенный откат

Суперсила release-флага — процентный выкат. Вместо 0% → 100% ты разгоняешь: 1% → 5% → 25% → 100%, следя за частотой ошибок и латентностью на каждом шаге. Если новый путь ломается на 5%, ты выключаешь флаг, и радиус поражения был 5% трафика, восстановлен за секунды — без отката коммита, без пайплайна хотфикса, без редеплоя. Вот настоящий аргумент за флаги: откат перестаёт быть инженерным событием и становится тумблером конфига.

Механизм важен для корректности. Хороший выкат липкий: тот же пользователь должен продолжать получать тот же вариант между запросами, иначе твой UI мерцает, а данные эксперимента — мусор. SDK делают это, хешируя стабильный ключ (userId плюс groupId) в корзину 0–99; «выкат 25%» значит, что корзины 0–24 включены. Хеш детерминирован и считается локально, так что оценка субмиллисекундна и не требует сетевого вызова на проверку — SDK держит весь набор правил в памяти и обновляет его в фоне.

Почему это работает

Зачем хешировать локально вместо запроса к серверу на каждую оценку? На масштабе флаг проверяется тысячи раз на путь запроса. Сетевой round-trip на проверку добавил бы латентность и жёсткую зависимость: если сервис флагов лежит, твоё приложение лежит. Локальная in-memory оценка с фоновой синхронизацией означает, что проверка флага — это lookup в хешмапе, а падение сервиса флагов деградирует до «последнего известного конфига», а не в твою аварию.

Каждый флаг — ветка в проде

Вот цена, что сеньор взвешивает против всей этой скорости. Каждый живой флаг — это if/else, оба пути которого работают в проде одновременно. Десять независимых булевых флагов — это 2^10 = 1024 возможных рантайм-конфигурации — ты не можешь протестировать их все, и комбинация, в которую пользователь реально попадает в проде, может быть той, что не задействовал ни один тест. Флаги умножают пространство состояний твоей системы. Они ещё и гниют: флаг, оставленный на 100% месяцами, — мёртвый конфиг, который всё ещё оценивается, всё ещё захламляет код веткой, которую никто не читает, и — урок Knight Capital — может быть переиспользован, разбудив код, о котором все забыли.

Это долг флагов, и он не гипотетический. Собственное руководство LaunchDarkly определяет устаревший флаг как тот, что отдаёт один и тот же вариант всем больше ~30 дней, и рекомендует архивировать по расписанию; инструменты вроде Piranha от Uber существуют ровно для того, чтобы AST-парсить кодбазы и автогенерить pull request, удаляющий флаг и его мёртвую ветку. Дисциплина — это вся игра: release-флаг должен нести срок годности и Jira-тикет на удаление, kill-switch’и должны быть помечены постоянными, чтобы никто их не «прибрал», а удаление — часть definition of done фичи, а не когда-нибудь-может-быть.

Выбери лучший вариант

Новый путь checkout собран и протестирован в staging. Хочешь зашипить его в прод сегодня, но снизить риск запуска. Выбери подход к выкату.

Викторина

Что на самом деле значит расцепить деплой и релиз?

Викторина

Release-флаг стоит на выкате 100% три месяца, и никто его не трогал. Каков ход сеньора?

Расставь шаги по порядку

Расставь жизненный цикл release-флага от создания до вывода из эксплуатации:

  1. 1 Создай флаг, дефолт выключен; задеплой код тёмным в прод
  2. 2 Релизь 1–5% пользователей, липко по userId; следи за частотой ошибок и латентностью
  3. 3 Расширь выкат до 25% → 100%, пока метрики чисты (или kill-switch выключи, если нет)
  4. 4 Стабильно на 100% — убери флаг и удали теперь мёртвую ветку else
  5. 5 Подтверди, что ссылок не осталось в коде или конфиге; закрой тикет на чистку
Вспомните перед уходом
  1. 01
    Объясни, как feature flags расцепляют деплой и релиз, и почему это меняет то, как команда шипит.
  2. 02
    Что такое долг флагов, почему инцидент Knight Capital — канонический пример, и какая дисциплина его предотвращает?
Итог

Feature flags расцепляют деплой и релиз: код шипится в прод тёмным, а рантайм-тумблер — подхваченный SDK за секунды — решает экспозицию, так что релиз становится решением, а не высокоставочным деплоем. Это покупает постепенный процентный выкат (1% → 5% → 25% → 100%, липко по userId, чтобы варианты не мерцали) и мгновенный откат kill-switch, при оценке локально как субмиллисекундный lookup хеша, так что падение сервиса флагов деградирует мягко. Но четыре типа флагов — release, ops/kill-switch, experiment, permission — имеют противоположные правильные сроки жизни, и каждый живой флаг — ветка в проде, так что N флагов значат 2^N конфигураций, что не протестировать целиком. Режим отказа — долг флагов: устаревшие или забытые флаги, что всё ещё оцениваются, захламляют код и могут быть переиспользованы — ровно механизм, стоивший Knight Capital ~$440M за 45 минут в 2012. Дисциплина сеньора — жизненный цикл: типизируй каждый флаг, давай release-флагам срок годности и тикет на удаление, помечай kill-switch’и постоянными, чтобы никто не удалил страховочную сетку, и делай чистку флагов частью «готово».

Продолжить восхождение ↑Feature flags: тест с выбором ответа
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources4
expand
  1. 01
  2. 02
  3. 03
  4. 04

Trademarks belong to their respective owners. Editorial reference only.