Files
Lotty/ADR/04-decisions.md

8.5 KiB
Raw Permalink Blame History

4. Ключевые архитектурные решения

Базовые решения

ID Решение Зачем Последствия
ADR-01 Модульный monolith на Django + Django Ninja Быстрый delivery и единая транзакционная модель Вертикальное масштабирование, не микросервисы
ADR-02 Разделение на apps/* и api/v1/* Чёткие границы между domain и transport Нужна дисциплина зависимостей
ADR-03 Hash-based deterministic assignment в decide Выполнение B2-4 (стабильность результата) Чувствительность к качеству subject_id
ADR-04 Review policy: ApproverGroup + fallback ReviewSettings Выполнение требований по ревью и fallback Политика плоская, без орг-иерархий
ADR-05 Lifecycle как state machine + validators + DB constraint Невалидные переходы и двойной запуск на флаг блокируются Правила статусов централизованы
ADR-06 Event ingestion с дедупом и PendingEvent Идемпотентность и out-of-order атрибуция Дополнительная сложность обработки pending
ADR-07 Guardrails как first-class модели + auto action Авто-реакция на деградацию и аудит триггера Риск ложных срабатываний при шумных данных
ADR-08 Conflict domains (mutual_exclusion, priority) Детерминированное разрешение пересечений экспериментов Дополнительные проверки на старте и в decide
ADR-09 Базовая observability (health, ready, /metrics, JSON logs) Проверяемость B9 и эксплуатационная диагностика SLA readiness подтверждается только live-demo
ADR-10 Проверка качества через just и автотесты Воспроизводимая верификация B1/B8/B10 -
ADR-11 Кэш результата decide в cache backend (Redis/Valkey в целевом окружении) Снизить CPU/DB в hot-path Ниже latency на повторяющихся запросах; stale-риск снижен revision-aware cache key и инвалидацией flag:{key} при обновлении default
ADR-12 Режим записи Decision: sync|async|disabled; async через events.persist_decision; experiment_assigned всегда sync Снять write-pressure с hot-path без потери атрибуции В async режиме нужна стабильность broker/worker и мониторинг очереди
ADR-13 RBAC через роли admin/experimenter/approver/viewer + JWT bearer + endpoint guards Выполнение требований доступа и ревью-ответственности из раздела 0 ТЗ Вся авторизация централизована в role guards; нужен контроль качества секретов JWT в окружении
ADR-14 Типизированные feature flags (string/boolean/integer) и публичный контракт обновления только default_value Исключить несогласованные значения флагов и обеспечить безопасные переключения без релиза Смена key/value_type решается созданием нового флага и миграцией использования
ADR-15 Каталог типов событий (EventType) с required_fields, is_exposure, requires_exposure Явно отделить схему событий от runtime-обработки и обеспечить корректную атрибуцию Требуется дисциплина ведения каталога; неактивные типы событий блокируют ingestion
ADR-16 Каталог метрик (MetricDefinition) + явная привязка метрик к эксперименту (ExperimentMetric) Выполнение требований 5.4 ТЗ и единый расчёт для reports/guardrails Изменение calculation_rule влияет на интерпретацию отчётов и требует управляемых изменений
ADR-17 Завершение эксперимента через отдельную сущность ExperimentOutcome с обязательным rationale и валидируемыми outcome-сценариями Выполнение требований 2.6 и B6-4/B6-5 Решение по rollout/rollback/no_effect становится аудитируемым и воспроизводимым
ADR-18 Уведомления как rule-based pipeline: NotificationRule -> NotificationLog -> async flush, с dedup/rate-limit по event_key Закрыть FX notifications без “шторма” сообщений Ограничение по каналам (telegram, smtp), требуется мониторинг failed delivery
ADR-19 Поиск по эвристике среди Learnings Реализовать поиск по базе Поиск эвристический (веса/Jaccard), не ML и требует аккуратной интерпретации

Неочевидные инварианты

  • В один момент времени на один FeatureFlag разрешён только один активный эксперимент (running|paused) через partial UniqueConstraint.
  • После старта эксперимента заморожены поля, влияющие на раздачу (traffic_allocation, targeting_rules, flag), и запрещены изменения вариантов вне draft.
  • Перед отправкой на review проверяются инварианты состава вариантов: минимум 2, ровно 1 control, сумма весов строго равна traffic_allocation.
  • Один approver может оставить только одно одобрение по эксперименту (unique_approval_per_user).
  • Для requires_exposure=True событие без exposure не атрибутируется сразу: уходит в PendingEvent, промотируется после прихода exposure, затем очищается по TTL.
  • Одна метрика не может быть прикреплена к эксперименту дважды (unique_experiment_metric).
  • Learning хранится в one-to-one связи с экспериментом.
  • После FeatureFlag.default_value update удаляется cache key flag:{key}; следующий decide читает актуальный flag из DB.

Ключевые риски

Риск Проявление Смягчение Остаток
Stale ответ из кэша decide Кратковременный возврат устаревшего результата в пределах TTL Revision-aware cache key + инвалидация flag:{key} при update default + короткий TTL Низкий
Потеря throughput при проблемах Celery в async Очередь растёт, запись Decision отстаёт Режимы `sync async
Тяжёлые запросы отчётов на больших данных Рост latency для percentile/агрегаций DB aggregate + Subquery, фильтрация attributed событий, индексы Средний
Потеря отложенных атрибуций PendingEvent истекает до прихода exposure TTL 7 дней + cleanup + промоция при exposure Средний
Сбой каналов уведомлений Отправка не доходит в Telegram/SMTP NotificationLog со статусами и ошибками Средний