{ "index": 121, "slug": "editorial-2024-08-field-feature-flags", "title": "Feature flag после rollout: как отделить rollback от cleanup", "excerpt": "Выключенный feature flag прекращает выдачу нового поведения, но не удаляет ветки, конфигурацию и несовместимые данные. Разбираем безопасный rollout, проверяемый rollback и отдельный cleanup gate.", "contentHtml": "

Новый экран работает у сотрудников, но часть клиентов внезапно видит его после релиза. Команда ставит feature flag в false. Ошибка исчезает, график успокаивается, change закрывают. Через месяц в коде всё ещё живут две ветки, в конфигурации лежит старый ключ, а тесты проверяют два ответа. Любая случайная смена окружения может вернуть candidate-путь. Цена ошибки — повторный инцидент, долгий поиск владельца и риск для данных, которые новый путь уже успел записать.

\n

Обратный случай не безопаснее. Rollout достиг 100 процентов, и это объявили завершением. Но 100 процентов означает только текущий результат правила для объявленной аудитории. Это не доказывает совместимость данных, исправность recovery path и право удалить fallback. Feature flag — не одно переключение, а временный контракт: кто получает вариант, кто его вычисляет, что считается остановкой и когда старый путь перестаёт быть нужен.

\n

Тезис. Rollout, rollback и cleanup нужно проводить разными изменениями. Rollout расширяет аудиторию. Rollback останавливает candidate и возвращает fallback. Cleanup удаляет лишнюю ветку после выбора итогового поведения. Если смешать эти операции, команда теряет причинную связь и принимает отключение за исправление.

\n

Симптом → причина → проверка → действие

\n
СимптомПричинаПроверкаДействие
Флаг выключен, но в коде остались две веткиRollback смешали с удалением реализацииНайти evaluator, branches, config references и тесты по ключуОставить fallback исполняемым, открыть отдельный cleanup change
100% rollout считают доказательством готовностиПроцент подменяет проверку контракта и данныхСверить cohort key, effective config version, stop condition и recovery pathЗаморозить процент и провести решение о final variant
После возврата к fallback часть запросов получает ошибкуCandidate записал состояние, которое fallback не читаетПроверить read/write compatibility и миграционный контрактОстановить расширение; исправить совместимость отдельно от flag value
После удаления ключ снова появляется в UIОстался client check, stale cache или старый payloadПроверить server response, client bundle, cache key и configУдалить presentation toggle и оставить только итоговый UI contract
Никто не может назвать дату удаленияУ флага нет owner, final variant и cleanup gateЗапросить карточку решения с evidence и списком referencesНе расширять rollout; назначить владельца и новый change
\n

Как работает решение

\n

Сначала evaluator получает ключ флага и контекст. Контекст должен однозначно описывать subject, окружение и другие поля, которые участвуют в targeting. Для процентного rollout особенно важен стабильный cohort key. Если сегодня используется user id, а завтра случайный request id, один пользователь будет переходить между вариантами. Это уже не gradual rollout, а непредсказуемое распределение.

\n

Evaluator возвращает выбранный вариант и, если система это поддерживает, детали оценки: key, value, variant, reason и версию конфигурации. Приложение применяет результат в одной точке. Второй слой не должен пересчитывать то же правило с другим контекстом. Иначе сервер отдаст новый ответ, а клиент решит, что пользователь относится к fallback-группе.

\n

Процент не описывает весь жизненный цикл. Он не говорит, обновился ли client cache, дошёл ли запрос до нового обработчика и можно ли откатить данные. Для каждого этапа нужны вопрос наблюдения и заранее выбранное действие. При остановке меняют одну существенную переменную: аудиторию, правило или код. Одновременная смена процента, evaluator и схемы данных уничтожает полезность сравнения.

\n
type FlagDecision = {\n  key: string;\n  variant: 'fallback' | 'candidate';\n  cohortKey: string;\n  configVersion: string;\n};\n\nfunction renderCheckout(decision: FlagDecision, order: Order) {\n  if (decision.variant === 'candidate') {\n    return renderNewCheckout(order);\n  }\n\n  return renderLegacyCheckout(order);\n}
\n

Этот фрагмент — учебный. Он показывает границу между результатом оценки и применением варианта. В нём нет настоящего flag provider, хранилища, телеметрии, миграции или команды rollback. В рабочей системе нужно дополнительно определить формат контекста, источник версии конфигурации, правила кеширования и поведение при ошибке evaluator.

\n

Команды для проверки ревизии

\n

Если candidate доставляется отдельным Kubernetes Deployment, сначала проверьте историю и состояние rollout. Эти команды требуют настроенного кластера, namespace и права чтения. Значение 7 — пример ревизии: его нужно заменить результатом первой команды.

\n
kubectl rollout history deployment/checkout -n shop\nkubectl rollout status deployment/checkout -n shop --timeout=60s\n\n# Изменение состояния: выполняйте только по утверждённому stop condition\nkubectl rollout undo deployment/checkout -n shop --to-revision=7\nkubectl rollout status deployment/checkout -n shop --timeout=60s
\n

rollout undo меняет состояние Deployment, поэтому это операционная команда, а не безопасная проверка «на всякий случай». Kubernetes откатывает Pod template к выбранной ревизии; он не возвращает записи в базе, события и внешние побочные эффекты. Если команда относится только к конфигурации feature flag, применяйте аналогичный runbook вашего провайдера и сохраняйте effective version.

\n

Учебная лестница rollout

\n
ЭтапВопросСтоп-условиеДействие
0%Fallback выполняет согласованный smoke scenario?Старый путь уже не работаетНе включать candidate; исправить базовый контракт
5%Одна стабильная cohort получает candidate в объявленной версии?Сигнал нарушает заранее записанную границуВернуть аудиторию к 0%, сохранить конфигурацию и вопрос расследования
25%Candidate можно сопоставить с fallback в одном окне?Версия или источник сигнала неизвестныОстановить расширение и не менять правило вместе с кодом
100%Вся объявленная аудитория получает выбранный вариант?Final variant не утверждён или fallback нужен для recoveryНе удалять флаг; открыть решение о завершении
Cleanup gateКод и конфигурация больше не требуют alternate branch?Осталась runtime reference или зависимость данныхВернуть cleanup в доработку
\n

Значения 0, 5, 25 и 100 процентов в таблице — фиксированный учебный пример. Они не являются рекомендацией, нормативом или результатом измерения. Реальный шаг выбирают по размеру риска, качеству сигнала, обратимости и размеру cohort. Если команда не может назвать population, control, окно и владельца решения, процент не даёт полезной информации.

\n
\"Rollout
Rollout расширяет аудиторию, rollback возвращает fallback, cleanup удаляет alternate branch. Проценты на схеме относятся к учебной модели.
\n

Rollback возвращает поведение, но не переписывает историю

\n

При stop condition минимальное действие — прекратить дальнейшее расширение candidate. Для этого возвращают аудиторию к fallback или к заранее определённому safe value. Записывают effective config version и причину остановки. Не нужно в тот же момент удалять ветки, менять evaluator и переписывать миграцию. Иначе нельзя будет понять, что именно остановило симптом.

\n

Rollback значения флага не равен rollback данных. Если candidate уже создал записи, отправил события или изменил схему ответа, старый путь должен уметь прочитать это состояние. Фича-флаг не добавляет backward compatibility автоматически. Если fallback не понимает результат candidate, ответом должен стать отдельный migration contract, а не уверенность, что false решает всё.

\n

Также не стоит называть rollback мгновенным. Сервис, edge-кеш и браузер могут получить конфигурацию в разное время. В карточке изменения укажите, где вычисляется вариант, сколько живёт кеш и что происходит с активной сессией. Разница версий допустима только тогда, когда она входит в контракт и не ломает recovery path.

\n

Cleanup — отдельное решение

\n

Cleanup начинается после выбора единственного поведения. Сначала владелец фиксирует final variant. Затем команда перестаёт добавлять новые условия и открывает отдельное изменение удаления. В его области должны быть server branches, client checks, configuration, permissions, tests, документация и миграционные заметки.

\n

Проверка cleanup строится на отрицательных утверждениях. Runtime больше не вызывает evaluator для ключа. Клиент не переключает UI по старому payload. Конфигурация не содержит targeting rules и environment overrides, нужные только флагу. Тесты проверяют итоговый business contract, а не сохранение двух boolean-веток. Документ объясняет принятое поведение, но не предлагает включить исчезнувший путь.

\n

Глобальный поиск по имени ключа полезен, но недостаточен. Ключ может быть собран из частей, сохранён в типе, скрыт в конфиге или пришит к кешу. Ищите вызовы evaluator, schema fields, token scopes, generated client types и тестовые данные. Если reference нужна для обратимой миграции, cleanup ещё не завершён: у неё должен быть owner и срок следующей проверки.

\n

Порядок действий

\n
  1. Опишите контракт. Назовите owner, evaluator, cohort key, fallback, config version и данные, которые меняет candidate.
  2. Проверьте fallback. Выполните smoke scenario и убедитесь, что старый путь доступен до включения нового.
  3. Запишите stop condition. Свяжите её с конкретным сигналом, окном, источником и действием на остановке.
  4. Запускайте один этап. Не меняйте одновременно аудиторию, правило, код и схему данных.
  5. При проблеме остановите расширение. Верните cohort к fallback, сохраните effective config version и отделите mitigation от поиска причины.
  6. Проверьте совместимость данных. Убедитесь, что fallback читает состояние, которое оставил candidate, либо оформите отдельную миграцию.
  7. После выбора final variant заморозьте flag policy. Не добавляйте новые условия и не продлевайте флаг без новой причины.
  8. Проведите cleanup search. Проверьте server, client, config, кеши, права, тесты и документацию.
  9. Удалите артефакты отдельным change. Сохраните тест итогового поведения и обычный rollback plan для самого cleanup-релиза.
  10. Закройте решение. Зафиксируйте final variant, owner, причину удаления и evidence отсутствия runtime-зависимости.
\n

Ограничения

\n

Этот подход не выбирает конкретный SDK, размер cohort, TTL кеша, SLO или схему миграции. OpenFeature даёт общий API для evaluation context и результата, но не задаёт правила вашей платформы; при ошибке базовой оценки клиент возвращает переданное default value. Kubernetes rollback относится к версии Deployment и его Pod template; он не доказывает обратимость бизнес-данных. Система флагов также не доказывает качество эксперимента: для этого нужны отдельные метрики, контрольная группа и методика измерения.

\n

Не следует удалять fallback только потому, что candidate получил 100 процентов. Не следует сохранять flag навсегда только потому, что когда-то существовал инцидент. Если данные, кеш или долгоживущий consumer не описаны, это блокер cleanup. Если evaluator недоступен, нужен явный default и проверяемая политика ошибки. Если неизвестно, какой вариант является итоговым, сначала принимают это решение, а потом удаляют код.

\n

Проверяемый критерий готовности

\n

Cleanup готов, если независимая проверка отвечает «да» на все вопросы: final variant записан владельцем; fallback больше не нужен для recovery или миграции; runtime не зависит от ключа; client и server не пересчитывают старое правило; configuration и permissions не содержат флаговые остатки; тесты сохраняют итоговое поведение; обычный релизный rollback не требует возвращать удалённый flag.

\n

До этого момента выключенный флаг нужно считать остановленным, но не удалённым. Такое различие сохраняет причину, границу действия и следующий шаг. Оно также не даёт спокойному графику скрыть технический долг, который снова станет пользовательской ошибкой.

\n

Проверяемые источники

\n

OpenFeature: Evaluation Context — официальный материал о контексте оценки, targeting key и рисках передачи персональных данных.

\n

OpenFeature: Evaluation API — официальное описание результата оценки и его деталей.

\n

Kubernetes: Update a Deployment Without Downtime — официальная документация о проверке rollout, возврате Deployment к ревизии и ограничении rollback только Pod template.

" }