A/B-платформа для продуктовых экспериментов. Позволяет запускать эксперименты на feature flags, отслеживать метрики, автоматически останавливать деградирующие варианты и принимать решения на данных.
Автор: Вадим Христенко
Проект создан с любовью и небольшим юмором (Спасибо Астольфо за вдохновение и стиль!). ШаховПобеда и БалюкПобеда, молодцы - всё заработает, всё будет хорошо, на защиту приду уверенно. Код скомпилирован, тесты пройдены (72 E2E check ✓), Telegram работает, learnings протестирован. Ничего не сломалось (вроде).
| Компонент | Технология | Зачем |
|---|---|---|
| Язык | Rust (edition 2024) | Скорость, безопасность памяти, zero-cost abstractions |
| HTTP | Axum 0.8 | Async-роутер с extractors, совместим с Tower |
| БД | PostgreSQL 18 | Основное хранилище: флаги, эксперименты, события, решения |
| Кэш | Valkey 9 (Redis-совместимый) | Кэш флагов/экспериментов, дедупликация, rate limiting |
| SQL | SQLx 0.8 | Async-драйвер, compile-time проверка запросов |
| Хеширование | xxHash3 (xxh3_64) | Детерминированный бакетинг и выбор вариантов |
| Пароли | Argon2 | Безопасное хеширование |
| Токены | jsonwebtoken | JWT-аутентификация |
| OpenAPI | utoipa 5.4 | Генерация OpenAPI 3.1 спецификации из derive-макросов |
| Swagger UI | rust-embed 8.11 | Встраивание Swagger UI в бинарник (Astolfo Edition) |
| Контейнеры | Docker + Compose V2 | Один docker compose up -d поднимает всё |
Платформа решает три задачи:
Выдача решений (POST /decide). Продукт спрашивает: «что показать пользователю user-42 для флага button_color?». Платформа проверяет эксперименты, применяет таргетинг DSL, детерминированно выбирает вариант через xxHash3 и возвращает значение + decision_id.
Сбор событий (POST /events, POST /events/batch). Продукт сообщает: «user-42 кликнул кнопку». События привязываются к decision_id, проходят валидацию по каталогу типов, дедупликацию и атрибуцию.
Автоматическая реакция. Фоновый guardrail-чекер каждые N секунд пересчитывает метрики из каталога. Если порог превышен, эксперимент ставится на паузу или откатывается к контролю.
Детерминизм через хеширование. Один и тот же пользователь всегда попадает в один вариант. Для бакетинга используется xxh3_64(user_id:experiment_id), для выбора варианта xxh3_64(user_id:experiment_id:variant). Никакого дополнительного хранилища для sticky-сессий не нужно.
Каталоги, а не хардкод. Типы событий и метрики задаются в каталогах через API. Guardrail-ы привязываются к метрикам из каталога. Новые типы событий и метрики добавляются без правки кода.
Fire-and-forget на hot path. Запись решений и участия в /decide выполняется через tokio::spawn без ожидания. Счётчик участия кэшируется в Valkey с TTL 60с. Цель: минимальная латенция на 100k RPS.
DSL-таргетинг. Собственный лексер и парсер: age >= 18 AND country IN ("RU", "KZ"). Поддерживает AND/OR/NOT, сравнения, множества IN/NOT IN, вложенные скобки. Если атрибут отсутствует, условие считается false.
Уведомления (C7). Telegram, Discord, Slack. Каждая команда заводит свой канал с bot_token. Правила уведомлений настраиваются через API: какие события куда слать.
Learnings Library (C9). После завершения эксперимента сохраняется structured learning: гипотеза, результат, impact, теги. Поиск по похожим экспериментам через tags, flag_key, free-text.
Insights (C8). Аналитика качества данных: доля отклонённых событий, traffic skew, полнота атрибуции, сводка платформы.
docker compose up -d
# Ждём готовности (~30 секунд)
curl http://localhost:80/ready
Подробная инструкция: docs/RUNBOOK.md
После запуска доступна интерактивная документация:
Документация генерируется автоматически из кода через utoipa 5.4. Все 62 API-эндпоинта аннотированы с описаниями на русском. Swagger UI встроен прямо в бинарник через rust-embed - никаких внешних файлов или CDN.
Примечание: НЕ используется
utoipa-swagger-ui- этот крейт загружает ассеты по сети при сборке, что ломает Docker Buildx в сетевой изоляции. Вместо него Swagger UI dist-файлы лежат вswagger-ui/и компилируются в бинарник.
| Метод | Путь | Назначение |
|---|---|---|
| GET | /health | Здоровье сервиса |
| GET | /ready | Готовность к работе |
| POST | /decide | Получить решение по флагам |
| POST | /events | Отправить событие |
| POST | /events/batch | Отправить пачку событий |
| Метод | Путь | Назначение |
|---|---|---|
| POST | /auth/register | Регистрация |
| POST | /auth/login | Получить JWT-токен |
| Метод | Путь | Назначение |
|---|---|---|
| GET/POST | /flags | Список / создание флагов |
| GET/PUT/DELETE | /flags/{key} | Чтение / обновление / удаление флага |
| GET/POST | /experiments | Список / создание экспериментов |
| GET/PUT | /experiments/{id} | Чтение / обновление (только draft) |
| POST | /experiments/{id}/transition | Смена статуса |
| POST | /experiments/{id}/complete | Завершение с выводом |
| POST | /experiments/{id}/archive | Архивирование |
| GET | /experiments/{id}/versions | История версий |
| GET | /experiments/{id}/guardrail-triggers | Срабатывания guardrail |
| GET/POST | /experiments/{id}/review | Ревью |
| GET | /experiments/{id}/report | Отчёт по вариантам |
| GET | /experiments/{id}/learnings | Learnings этого эксперимента |
| GET/POST | /event-types | Каталог типов событий |
| PUT | /event-types/{name} | Обновление типа |
| GET/POST | /metrics | Каталог метрик |
| PUT | /metrics/{name} | Обновление метрики |
| GET | /audit | Аудит-лог |
| POST | /dsl/validate | Проверка DSL-правила |
| GET/PUT/DELETE | /users/{id}/approver-group | Группы аппруверов |
| GET/POST | /notifications/channels | Каналы уведомлений |
| PUT/DELETE | /notifications/channels/{id} | Управление каналом |
| GET/POST | /notifications/rules | Правила уведомлений |
| DELETE | /notifications/rules/{id} | Удаление правила |
| GET | /notifications/log | Лог отправок |
| GET/POST | /learnings | Библиотека выводов |
| GET/PUT/DELETE | /learnings/{id} | Конкретный learning |
| GET | /insights/event-quality | Качество данных |
| GET | /insights/traffic-skew/{id} | Перекос трафика |
| GET | /insights/attribution | Здоровье атрибуции |
| GET | /insights/summary | Сводка платформы |
| Роль | Создаёт эксперименты | Ревьюит | Читает | Администрирует |
|---|---|---|---|---|
| Admin | + | + | + | + |
| Experimenter | + | + | ||
| Approver | + | + | ||
| Viewer | + |
draft -> review -> approved -> launched -> paused -> completed -> archived
На любом шаге возможен reject (возврат в draft для доработки). Guardrail может автоматически поставить на паузу или откатить к контролю.
cargo test
81 тест (61 unit + 20 inline). Интеграционные: bash scripts/integration_tests.sh.
| Файл | Содержание |
|---|---|
| docs/ARCHITECTURE.md | C4-диаграммы, компоненты, критический путь, OpenAPI |
| docs/RUNBOOK.md | Пошаговый запуск и happy path |
| docs/COMPLIANCE.md | Матрица соответствия критериям |
| docs/TEST_REPORT.md | Отчёт о тестировании |
| docs/DEMO_SCENARIOS.md | Сценарии демонстрации |
| /docs/ | Swagger UI - интерактивная документация API (Astolfo Edition) |
| /api-docs/openapi.json | OpenAPI 3.1 спецификация (62 paths, 75 schemas) |
Rust
80.9%
Shell
13.0%
Dockerfile
5.5%
A/B-платформа для продуктовых экспериментов. Позволяет запускать эксперименты на feature flags, отслеживать метрики, автоматически останавливать деградирующие варианты и принимать решения на данных.
Автор: Вадим Христенко
Проект создан с любовью и небольшим юмором (Спасибо Астольфо за вдохновение и стиль!). ШаховПобеда и БалюкПобеда, молодцы - всё заработает, всё будет хорошо, на защиту приду уверенно. Код скомпилирован, тесты пройдены (72 E2E check ✓), Telegram работает, learnings протестирован. Ничего не сломалось (вроде).
| Компонент | Технология | Зачем |
|---|---|---|
| Язык | Rust (edition 2024) | Скорость, безопасность памяти, zero-cost abstractions |
| HTTP | Axum 0.8 | Async-роутер с extractors, совместим с Tower |
| БД | PostgreSQL 18 | Основное хранилище: флаги, эксперименты, события, решения |
| Кэш | Valkey 9 (Redis-совместимый) | Кэш флагов/экспериментов, дедупликация, rate limiting |
| SQL | SQLx 0.8 | Async-драйвер, compile-time проверка запросов |
| Хеширование | xxHash3 (xxh3_64) | Детерминированный бакетинг и выбор вариантов |
| Пароли | Argon2 | Безопасное хеширование |
| Токены | jsonwebtoken | JWT-аутентификация |
| OpenAPI | utoipa 5.4 | Генерация OpenAPI 3.1 спецификации из derive-макросов |
| Swagger UI | rust-embed 8.11 | Встраивание Swagger UI в бинарник (Astolfo Edition) |
| Контейнеры | Docker + Compose V2 | Один docker compose up -d поднимает всё |
Платформа решает три задачи:
Выдача решений (POST /decide). Продукт спрашивает: «что показать пользователю user-42 для флага button_color?». Платформа проверяет эксперименты, применяет таргетинг DSL, детерминированно выбирает вариант через xxHash3 и возвращает значение + decision_id.
Сбор событий (POST /events, POST /events/batch). Продукт сообщает: «user-42 кликнул кнопку». События привязываются к decision_id, проходят валидацию по каталогу типов, дедупликацию и атрибуцию.
Автоматическая реакция. Фоновый guardrail-чекер каждые N секунд пересчитывает метрики из каталога. Если порог превышен, эксперимент ставится на паузу или откатывается к контролю.
Детерминизм через хеширование. Один и тот же пользователь всегда попадает в один вариант. Для бакетинга используется xxh3_64(user_id:experiment_id), для выбора варианта xxh3_64(user_id:experiment_id:variant). Никакого дополнительного хранилища для sticky-сессий не нужно.
Каталоги, а не хардкод. Типы событий и метрики задаются в каталогах через API. Guardrail-ы привязываются к метрикам из каталога. Новые типы событий и метрики добавляются без правки кода.
Fire-and-forget на hot path. Запись решений и участия в /decide выполняется через tokio::spawn без ожидания. Счётчик участия кэшируется в Valkey с TTL 60с. Цель: минимальная латенция на 100k RPS.
DSL-таргетинг. Собственный лексер и парсер: age >= 18 AND country IN ("RU", "KZ"). Поддерживает AND/OR/NOT, сравнения, множества IN/NOT IN, вложенные скобки. Если атрибут отсутствует, условие считается false.
Уведомления (C7). Telegram, Discord, Slack. Каждая команда заводит свой канал с bot_token. Правила уведомлений настраиваются через API: какие события куда слать.
Learnings Library (C9). После завершения эксперимента сохраняется structured learning: гипотеза, результат, impact, теги. Поиск по похожим экспериментам через tags, flag_key, free-text.
Insights (C8). Аналитика качества данных: доля отклонённых событий, traffic skew, полнота атрибуции, сводка платформы.
docker compose up -d
# Ждём готовности (~30 секунд)
curl http://localhost:80/ready
Подробная инструкция: docs/RUNBOOK.md
После запуска доступна интерактивная документация:
Документация генерируется автоматически из кода через utoipa 5.4. Все 62 API-эндпоинта аннотированы с описаниями на русском. Swagger UI встроен прямо в бинарник через rust-embed - никаких внешних файлов или CDN.
Примечание: НЕ используется
utoipa-swagger-ui- этот крейт загружает ассеты по сети при сборке, что ломает Docker Buildx в сетевой изоляции. Вместо него Swagger UI dist-файлы лежат вswagger-ui/и компилируются в бинарник.
| Метод | Путь | Назначение |
|---|---|---|
| GET | /health | Здоровье сервиса |
| GET | /ready | Готовность к работе |
| POST | /decide | Получить решение по флагам |
| POST | /events | Отправить событие |
| POST | /events/batch | Отправить пачку событий |
| Метод | Путь | Назначение |
|---|---|---|
| POST | /auth/register | Регистрация |
| POST | /auth/login | Получить JWT-токен |
| Метод | Путь | Назначение |
|---|---|---|
| GET/POST | /flags | Список / создание флагов |
| GET/PUT/DELETE | /flags/{key} | Чтение / обновление / удаление флага |
| GET/POST | /experiments | Список / создание экспериментов |
| GET/PUT | /experiments/{id} | Чтение / обновление (только draft) |
| POST | /experiments/{id}/transition | Смена статуса |
| POST | /experiments/{id}/complete | Завершение с выводом |
| POST | /experiments/{id}/archive | Архивирование |
| GET | /experiments/{id}/versions | История версий |
| GET | /experiments/{id}/guardrail-triggers | Срабатывания guardrail |
| GET/POST | /experiments/{id}/review | Ревью |
| GET | /experiments/{id}/report | Отчёт по вариантам |
| GET | /experiments/{id}/learnings | Learnings этого эксперимента |
| GET/POST | /event-types | Каталог типов событий |
| PUT | /event-types/{name} | Обновление типа |
| GET/POST | /metrics | Каталог метрик |
| PUT | /metrics/{name} | Обновление метрики |
| GET | /audit | Аудит-лог |
| POST | /dsl/validate | Проверка DSL-правила |
| GET/PUT/DELETE | /users/{id}/approver-group | Группы аппруверов |
| GET/POST | /notifications/channels | Каналы уведомлений |
| PUT/DELETE | /notifications/channels/{id} | Управление каналом |
| GET/POST | /notifications/rules | Правила уведомлений |
| DELETE | /notifications/rules/{id} | Удаление правила |
| GET | /notifications/log | Лог отправок |
| GET/POST | /learnings | Библиотека выводов |
| GET/PUT/DELETE | /learnings/{id} | Конкретный learning |
| GET | /insights/event-quality | Качество данных |
| GET | /insights/traffic-skew/{id} | Перекос трафика |
| GET | /insights/attribution | Здоровье атрибуции |
| GET | /insights/summary | Сводка платформы |
| Роль | Создаёт эксперименты | Ревьюит | Читает | Администрирует |
|---|---|---|---|---|
| Admin | + | + | + | + |
| Experimenter | + | + | ||
| Approver | + | + | ||
| Viewer | + |
draft -> review -> approved -> launched -> paused -> completed -> archived
На любом шаге возможен reject (возврат в draft для доработки). Guardrail может автоматически поставить на паузу или откатить к контролю.
cargo test
81 тест (61 unit + 20 inline). Интеграционные: bash scripts/integration_tests.sh.
| Файл | Содержание |
|---|---|
| docs/ARCHITECTURE.md | C4-диаграммы, компоненты, критический путь, OpenAPI |
| docs/RUNBOOK.md | Пошаговый запуск и happy path |
| docs/COMPLIANCE.md | Матрица соответствия критериям |
| docs/TEST_REPORT.md | Отчёт о тестировании |
| docs/DEMO_SCENARIOS.md | Сценарии демонстрации |
| /docs/ | Swagger UI - интерактивная документация API (Astolfo Edition) |
| /api-docs/openapi.json | OpenAPI 3.1 спецификация (62 paths, 75 schemas) |
Rust
80.9%
Shell
13.0%
Dockerfile
5.5%