backend · intermediate · 5d
Сервис фич-флагов
Собери небольшой сервис флагов с правилами таргетинга, процентными раскатками и типизированным SDK, который вычисляет флаги на клиенте из закешированного набора правил.
Результат
API, отдающее набор правил флагов, и SDK, где flagOn('x', user) возвращает детерминированный, процентно-корректный boolean.
Этапы
0/2 · 0%- 01Кэшируемый ETag-набор правил
Смоделируй флаги + правила и отдай их через закешированный эндпоинт с ETag.
Критерии готовности- GET возвращает набор правил с ETag; совпавший If-None-Match отдаёт 304 без тела.
- Флаги и правила таргетинга смоделированы типизированной валидируемой схемой.
- 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 при изменении несвязанных флагов.