open atlas
← Все проекты

backend · intermediate · 5d

Сервис фич-флагов

Собери небольшой сервис флагов с правилами таргетинга, процентными раскатками и типизированным SDK, который вычисляет флаги на клиенте из закешированного набора правил.

Фич-флаги кажутся тривиальными, пока не понадобятся детерминированные раскатки и вычисление с низкой задержкой. Спроектируешь модель правил, сделаешь процентные раскатки стабильными при изменениях через consistent hashing, агрессивно закешируешь через ETag и выпустишь типизированный SDK — ядро систем уровня LaunchDarkly.

Результат

API, отдающее набор правил флагов, и SDK, где flagOn('x', user) возвращает детерминированный, процентно-корректный boolean.

Этапы

0/2 · 0%
  1. 01Кэшируемый ETag-набор правил

    Смоделируй флаги + правила и отдай их через закешированный эндпоинт с ETag.

    Критерии готовности
    • GET возвращает набор правил с ETag; совпавший If-None-Match отдаёт 304 без тела.
    • Флаги и правила таргетинга смоделированы типизированной валидируемой схемой.
  2. 02Детерминированная %-раскатка

    Реализуй детерминированную процентную раскатку через хеш id пользователя + ключа флага.

    Критерии готовности
    • hash(userId + flagKey) раскладывает пользователя по корзинам: один пользователь всегда получает один ответ, а при X% включены ~X%.
    • Изменение процента раскатки двигает границу монотонно, не перетасовывая уже включённых пользователей.

Рубрика

Джуниор Миддл Сеньор
Детерминированная раскатка Процентная раскатка использует Math.random() на каждый запрос; один пользователь получает разные ответы на последовательных вызовах, а включённая доля лишь приближённа. hash(userId + flagKey) mod 100 детерминированно раскладывает пользователя по корзинам: один пользователь всегда попадает в одну корзину, при X% включены ~X%, и монотонное расширение корзин означает, что увеличение раскатки никогда не выключает уже включённых пользователей. Ты рассуждаешь о столкновениях корзин между флагами: если хешировать только userId (а не userId+flagKey), пользователи в корзине 0–10% — всегда одни и те же люди для каждого флага, систематическое смещение, искажающее результаты A/B. Примешивание flagKey разрывает корреляцию. Ты демонстрируешь это тестом распределения хи-квадрат на смоделированной популяции пользователей.
Кеширование набора правил и латентность распространения SDK запрашивает полный набор правил из API при каждом вызове оценки; высоконагруженный сервис добавляет сетевой round trip к каждому запросу. Набор правил отдаётся с ETag; SDK-клиенты кешируют его локально и ревалидируют через If-None-Match, получая 304 (без передачи тела) при отсутствии изменений — оценка в процессе без сетевого I/O на каждый вызов. Ты рассуждаешь о латентности распространения kill-switch: TTL кеша 60с означает, что флаг, выключенный при инциденте безопасности, остаётся включённым до 60с в каждом SDK. Стриминг (SSE push) устраняет это окно, но добавляет постоянное соединение на каждый экземпляр SDK. Ты документируешь выбранный компромисс и наихудшую задержку распространения при своём интервале поллинга.
Согласованность оценки и корректность таргетинга Правила таргетинга применяются в произвольном порядке; пользователь, попадающий под несколько правил, получает недетерминированный результат в зависимости от порядка оценки. Правила оцениваются в порядке приоритета с явным переходом к процентной раскатке; одна и та же модель правил и один пользователь всегда дают один boolean, а схема типизирована и валидируется, так что некорректное правило отклоняется при записи, а не при оценке. Ты рассматриваешь окно устаревшего набора правил: SDK-клиент, оценивающий кешированный набор правил, пока сервер уже изменил правило, производит расщепление оценки — одни пользователи видят старое поведение, другие — новое. Ты документируешь, когда это приемлемо (постепенная раскатка) и когда нет (kill-switch безопасности, требующий распространения за секунды), и показываешь, как стриминговый канал закрывает разрыв.
Эталонный разбор (спойлер)

Почему hash(userId + flagKey): хеширование только userId создаёт систематическую корреляцию корзин — одни и те же 10% пользователей всегда попадают в первый дециль по каждому флагу. Это смещает любой A/B-эксперимент, опирающийся на независимые назначения лечения. Примешивание flagKey разрывает корреляцию корзин между флагами; примешивание experiment-id разрывает её между перезапусками одного флага.

Латентность распространения kill-switch — скрытый SLO: сервис флагов обычно второстепенен, пока инцидент безопасности не требует немедленного выключения чего-либо. SDK на основе поллинга добавляют до одного TTL воздействия после переключения kill-switch. SSE стриминг устраняет это окно ценой постоянного соединения на экземпляр SDK — компромисс: количество соединений (fan-out) против времени распространения.

ETag-кеширование для раздачи набора правил: отдача версионного ETag с набором правил и принятие If-None-Match означает, что неизменённые наборы правил передают только 200 байт заголовков, а не полный payload. Это делает частый поллинг дешёвым и позволяет многим экземплярам SDK ревалидироваться одновременно без пиков пропускной способности — правильный дефолт для SDK на основе поллинга до добавления стриминга.

Сделай по-сеньорски

  • Добавь стриминговый канал обновлений (SSE), чтобы SDK обновлялись без поллинга.
  • Докажи стабильность раскатки: пользователь не мерцает между on/off при изменении несвязанных флагов.

Навыки

rule evaluationconsistent hashing for rolloutscaching + ETagtyped SDK design

Рекомендуемый стек

nodehonozod