docs(): added RUNBOOK, compliance matrix, ADR, refactored C4 and
repository map zavoz
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
# 1. Цели архитектуры
|
||||
|
||||
1. Обеспечить воспроизводимый критичный поток `decide -> event -> report/guardrail` без ручных допущений в runtime.
|
||||
2. Гарантировать безопасный lifecycle эксперимента: ревью, статусные переходы, блокировка конфликтных запусков.
|
||||
3. Сохранить корректность данных для аналитики: идемпотентный приём событий, атрибуция через `decision_id`, обработка out-of-order через `PendingEvent`.
|
||||
4. Поддержать операционную проверяемость: `health/readiness`, метрики, структурированные логи, фоновые задачи.
|
||||
5. Дать артефакты, по которым жюри может пройти критерии `B1..B10` и выбранные `FX` без домысливания.
|
||||
@@ -0,0 +1,26 @@
|
||||
# 2. Контекст и границы
|
||||
|
||||
## Внутри системы
|
||||
|
||||
- Один backend-контейнер (Django + Django Ninja API).
|
||||
- Доменные модули в `apps/*`, HTTP-слой в `api/v1/*`.
|
||||
- Хранение: реляционная БД (PostgreSQL в целевом окружении, SQLite по умолчанию локально).
|
||||
- Кэш/брокер: Redis/Valkey в целевом окружении, LocMem cache локально/в тестах.
|
||||
- Фоновые задачи: Celery worker/beat (guardrails, notifications, cleanup pending).
|
||||
|
||||
## Внешние акторы
|
||||
|
||||
- Product client: вызывает `POST /api/v1/decide`, отправляет `POST /api/v1/events`.
|
||||
- Experimenter/Approver/Admin/Viewer: управляют экспериментами и смотрят результаты через API.
|
||||
- Каналы уведомлений: Telegram API и SMTP.
|
||||
- Система наблюдаемости: скрейпинг `/metrics`, чтение структурированных логов.
|
||||
|
||||
## Служебные контракты
|
||||
|
||||
- `GET /health`: liveness.
|
||||
- `GET /ready`: readiness (cache, db, storage, celery ping).
|
||||
- `GET /metrics`: prometheus endpoint.
|
||||
|
||||
## Явная граница фактов
|
||||
|
||||
Документация привязана к текущей реализации в `src/backend`.
|
||||
@@ -0,0 +1,42 @@
|
||||
# 3. Драйверы и NFR
|
||||
|
||||
## Корректность и воспроизводимость
|
||||
|
||||
- Детерминированная раздача варианта через SHA-256 hash по `subject_id` и `experiment_id`.
|
||||
- Один активный эксперимент на флаг (`running/paused`) через DB constraint.
|
||||
- Строгие переходы lifecycle и блокировка недопустимых переходов.
|
||||
- Идемпотентность событий по `event_id`.
|
||||
|
||||
## Safety
|
||||
|
||||
- Guardrails с порогом, окном наблюдения и автоматическим действием (`pause` или `rollback`).
|
||||
- Ограничение участия пользователя: max concurrent + cooldown.
|
||||
- Conflict domains для детерминированного разрешения коллизий экспериментов.
|
||||
|
||||
## Целостность аналитики
|
||||
|
||||
- Атрибуция через `decision_id`.
|
||||
- Для событий с `requires_exposure=True`: без exposure событие уходит в `PendingEvent` и промотируется позже.
|
||||
- Отчёт строится по вариантам и выбранным метрикам эксперимента.
|
||||
|
||||
## Эксплуатация
|
||||
|
||||
- Health/readiness probes.
|
||||
- Prometheus-инструментация HTTP и бизнес-счётчиков.
|
||||
- Structured logging с correlation id.
|
||||
- Регламентные фоновые задачи Celery.
|
||||
|
||||
## Производительность
|
||||
|
||||
Реализовано:
|
||||
- кэш флагов и активных экспериментов;
|
||||
- кэш результата `decide` (TTL через `DECISION_RESULT_CACHE_TTL_SECONDS`);
|
||||
- режим записи `Decision`: `sync|async|disabled` (`DECISION_WRITE_MODE`) с принудительным sync для `experiment_assigned`;
|
||||
- async persistence через `events.persist_decision`;
|
||||
- `reports` считает `average` через `Avg`, `percentile` через DB aggregate (`PERCENTILE_CONT` для PostgreSQL);
|
||||
- в `reports` убрана materialization `decision_ids`, используется `Subquery`.
|
||||
|
||||
Текущие ограничения:
|
||||
- в режиме `async` устойчивость записи зависит от здоровья Celery worker/broker;
|
||||
- SQL-ветка percentile зависит от СУБД (PostgreSQL/не-PostgreSQL fallback);
|
||||
- отдельный нагрузочный benchmark-артефакт не приложен.
|
||||
@@ -0,0 +1,45 @@
|
||||
# 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-ответа в рамках TTL |
|
||||
| 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 связи с экспериментом.
|
||||
|
||||
## Ключевые риски
|
||||
|
||||
| Риск | Проявление | Смягчение | Остаток |
|
||||
|---|---|---|---|
|
||||
| Stale ответ из кэша `decide` | Кратковременный возврат устаревшего результата в пределах TTL | Revision-aware cache key + короткий TTL | Низкий/средний |
|
||||
| Потеря throughput при проблемах Celery в `async` | Очередь растёт, запись `Decision` отстаёт | Режимы `sync|async|disabled`, force-sync для `experiment_assigned`, fallback на sync при ошибке enqueue | Средний |
|
||||
| Тяжёлые запросы отчётов на больших данных | Рост latency для percentile/агрегаций | DB aggregate + `Subquery`, фильтрация attributed событий, индексы | Средний |
|
||||
| Потеря отложенных атрибуций | `PendingEvent` истекает до прихода exposure | TTL 7 дней + cleanup + промоция при exposure | Средний |
|
||||
| Сбой каналов уведомлений | Отправка не доходит в Telegram/SMTP | `NotificationLog` со статусами и ошибками | Средний |
|
||||
@@ -0,0 +1,33 @@
|
||||
# 5. Критичный путь
|
||||
|
||||
## Decide (`apps/decision/services.py`)
|
||||
|
||||
1. `flag` из cache/DB.
|
||||
2. `active_experiment` из cache/DB.
|
||||
3. Формируется cache key результата (`flag`, `subject`, digest атрибутов, ревизии `flag/experiment`).
|
||||
4. При cache hit возвращается тот же outcome/reason/value с новым `decision_id`.
|
||||
5. При cache miss выполняются проверки: running experiment, targeting, participation limits, domain conflicts, traffic allocation.
|
||||
6. Если назначен вариант, выбирается детерминированно по hash + weights.
|
||||
7. Результат может быть закэширован (по whitelist reason).
|
||||
8. Запись `Decision` зависит от `DECISION_WRITE_MODE`: `sync|async|disabled`; для `experiment_assigned` запись всегда sync.
|
||||
|
||||
Reason-коды: `flag_not_found`, `no_active_experiment`, `targeting_mismatch`, `participation_limit`, `domain_conflict`, `outside_traffic_allocation`, `no_variants`, `experiment_assigned`.
|
||||
|
||||
## Events (`apps/events/services.py`)
|
||||
|
||||
1. Валидация типа события и payload.
|
||||
2. Дедуп по `event_id`.
|
||||
3. Exposure: `Exposure` + `Event`, затем промоция `PendingEvent`.
|
||||
4. Conversion: при `requires_exposure=True` без exposure -> `PendingEvent` (TTL 7 дней), иначе attributed `Event`.
|
||||
|
||||
## Reports (`apps/reports/services.py`)
|
||||
|
||||
- По каждому варианту: `exposures`, `unique_subjects`, значения выбранных метрик.
|
||||
- Поддерживается период `start/end`.
|
||||
- `average` считает DB `Avg`, `percentile` - DB aggregate (`PERCENTILE_CONT` в PostgreSQL).
|
||||
- Связь событий с экспозициями строится через `Subquery`, без materialization списка `decision_ids` в Python.
|
||||
|
||||
## Guardrails (`apps/guardrails/services.py`)
|
||||
|
||||
- Celery проверяет running эксперименты.
|
||||
- При breach: `GuardrailTrigger` + действие (`pause`/`rollback`) + `ExperimentLog` + enqueue уведомления.
|
||||
@@ -0,0 +1,34 @@
|
||||
# 6. Эксплуатация и наблюдаемость
|
||||
|
||||
## Endpoints
|
||||
|
||||
- `/health` -> liveness (`health_check.Memory`).
|
||||
- `/ready` -> readiness (`Cache`, `Database`, `Storage`, `Celery Ping`).
|
||||
- `/metrics` -> Prometheus metrics.
|
||||
|
||||
SLA `ready <= 180s` подтверждается только live-demo.
|
||||
|
||||
## Метрики и логи
|
||||
|
||||
- Инфраструктурные метрики через `django-prometheus` middleware.
|
||||
- Бизнес-счётчики: `lotty_decide_requests_total`, `lotty_events_ingested_total`.
|
||||
- Production logs: JSON + `Correlation-ID` (`django-guid`).
|
||||
|
||||
## Celery задачи
|
||||
|
||||
Периодические:
|
||||
- `guardrails.check_all` - 60s.
|
||||
- `notifications.flush_pending` - 30s.
|
||||
- `events.cleanup_expired_pending` - 3600s.
|
||||
|
||||
По запросу:
|
||||
- `events.persist_decision` - асинхронная запись `Decision` при `DECISION_WRITE_MODE=async`.
|
||||
|
||||
## Runtime knobs
|
||||
|
||||
- `DECISION_RESULT_CACHE_TTL_SECONDS` (по умолчанию `60`).
|
||||
- `DECISION_WRITE_MODE` (`sync|async|disabled`, по умолчанию `sync`).
|
||||
|
||||
## Команды
|
||||
|
||||
`cd src/backend && just run|test|test-coverage|lint|format`
|
||||
@@ -0,0 +1,8 @@
|
||||
# 7. Явные упрощения
|
||||
|
||||
1. Один backend-сервис (модульный monolith), без выделения микросервисов по доменам.
|
||||
2. Отдельный statistical significance engine не реализован; решения фиксируются на метриках и guardrails.
|
||||
3. Отчёты считаются синхронно в запросе, без pre-aggregation pipeline и materialized views (потому что нету ClickHouse).
|
||||
4. Режим `DECISION_WRITE_MODE=sync` используется по умолчанию; `async`/`disabled` - эксплуатационные режимы.
|
||||
5. Кэш результата `decide` основан на TTL и revision-aware ключе, без отдельной ручной инвалидции.
|
||||
6. Similar learnings считаются эвристикой (Jaccard/веса), без ML-ранжирования.
|
||||
@@ -0,0 +1,21 @@
|
||||
# 8. Проверяемость
|
||||
|
||||
## Базовый набор
|
||||
|
||||
1. `cd src/backend && just --list`
|
||||
2. `cd src/backend && just test`
|
||||
3. `cd src/backend && just test-coverage && just show-coverage`
|
||||
|
||||
## Точечные проверки по доменам
|
||||
|
||||
1. `cd src/backend && uv run python manage.py test apps.decision.tests.test_decide`
|
||||
2. `cd src/backend && uv run python manage.py test apps.events.tests.test_services tests.integration.test_events`
|
||||
3. `cd src/backend && uv run python manage.py test apps.reports.tests.test_reports apps.guardrails.tests.test_guardrails`
|
||||
4. `cd src/backend && uv run python manage.py test apps.reviews.tests.test_reviews_policy apps.experiments.tests.test_services`
|
||||
5. `cd src/backend && uv run python manage.py test apps.conflicts.tests.test_conflicts apps.notifications.tests.test_notifications apps.learnings.tests.test_learnings`
|
||||
|
||||
## Runtime
|
||||
|
||||
- `GET /health` -> `200`
|
||||
- `GET /ready` -> `200` после готовности зависимостей
|
||||
- `GET /metrics` -> есть инфраструктурные и бизнес метрики
|
||||
@@ -0,0 +1,16 @@
|
||||
# ADR - LOTTY Backend
|
||||
|
||||
## Состав
|
||||
|
||||
| Файл | Содержание |
|
||||
|---|---|
|
||||
| `01-goals.md` | Цели архитектуры |
|
||||
| `02-context.md` | Границы системы и акторы |
|
||||
| `03-drivers-nfr.md` | Драйверы и NFR |
|
||||
| `04-decisions.md` | Ключевые решения, неочевидные инварианты и ключевые риски |
|
||||
| `05-critical-path.md` | Критичный поток `decide -> event -> report/guardrail` |
|
||||
| `06-operations.md` | Эксплуатация и наблюдаемость |
|
||||
| `07-simplifications.md` | Явные упрощения |
|
||||
| `08-verification.md` | Как проверять критерии |
|
||||
|
||||
Связанная матрица соответствия: [compliance-matrix.md](../compliance-matrix.md).
|
||||
Reference in New Issue
Block a user