Модуль слоёв: архитектура и разработка#
Обзор#
Модуль layer реализует систему управления слоями трафика для А/Б-тестирования. Он отвечает за размещение экспериментов на слоях с управлением конфликтами и контролем трафика.
Назначение#
Модуль решает следующие задачи:
- Управление слоями трафика: создание, редактирование, архивирование слоёв
- Размещение экспериментов: связывание экспериментов с конкретными бакетами на слоях
- Контроль конфликтов: предотвращение одновременного использования одинакового трафика конфликтующими экспериментами
- Расчёт доступного трафика: определение свободных ресурсов на слое для новых размещений
Ключевые концепции#
Слои (Layers)#
Слой представляет собой изолированное пространство трафика для экспериментов. Основные характеристики:
- Бакеты: слоты на слое (обычно 200). Каждый бакет — это 0.5% трафика
- Соль: уникальная строка для хеширования трафика на слое
- Статусы: Active (размещения разрешены), Frozen (только модификация существующих), Archived (только чтение)
- Типы: COMMON (обычные эксперименты), DFP (рекламные эксперименты)
Размещения (Allocations)#
Размещение связывает объект (эксперимент или вариант эксперимента) с конкретными bucket'ами на слое:
- Объект размещения: эксперимент или вариант эксперимента
- Бакеты: множество занятых бакетов (множество целых чисел от 0 до num_buckets-1)
- Статусы: Planned (создано, но не активно), Active (активно используется), Archived (архивировано)
- Методы размещения: Auto (автоматический выбор бакетов), Manual (ручной выбор)
- Политика совместного использования: Allow (разрешено), Deny (запрещено)
Архитектура модуля#
Слои архитектуры#
В модуле слоёв используется гибридный подход: handlers могут использовать как services напрямую, так и use cases для более сложных сценариев. Use cases активно используются другими модулями (например, модулем экспериментов).
┌───────────────────────────────────────────────────────────┐
│ API Handlers (internal) │
│ addLayerV2, findAllocations, getExperimentAllocation... │
└─────────────┬─────────────────────────┬───────────────────┘
│ │
┌─────────▼──────────┐ ┌────────▼──────────┐
│ Services │◄───┤ Use Cases │
│ (простые CRUD) │ │ (сложная логика) │
└─────────┬──────────┘ └───────────────────┘
│
│
▼
┌─────────────────────┐
│ Repositories │
│ Работа с БД (PgSQL)│
└─────────┬───────────┘
│
▼
┌─────────────────────┐
│ Models │
│ Доменные сущности │
└─────────────────────┘
┌───────────────────────────────────────────────────────────┐
│ Модуль experiments (и другие) │
│ Использует use cases для интеграции со слоями │
└───────────────────────────────────────────────────────────┘
Основные компоненты#
Models#
Domain entities:
- Layer — слой трафика
- Allocation — размещение объекта на слое
- AllocationSettings — настройки размещений для эксперимента
Value objects:
- TrafficBuckets — множество bucket'ов
- AllocationRelations — отношения между размещениями
- AllocationScope — область действия размещения
Enums:
- LayerStatus — статус слоя (Active, Frozen, Archived)
- LayerType — тип слоя (Common, DFP)
- AllocationStatus — статус размещения (Planned, Active, Archived)
- AllocationMethod — метод размещения (Auto, Manual)
- AllocationSharingPolicy — политика совместного использования
Services#
LayerService (services/layer.py):
- create() — Создание слоя и логирование в таблицах статуса и соли слоя
- update() — Обновление слоя
- get_by_id(), get_by_label() — Получение слоя
- find() — Поиск слоёв с фильтрацией по LayerCriteriaExtended
- delete() — Удаление слоя
- refresh_salt() — Обновление соли и добавление лога этого события в таблицу. Если на слое есть активные размещения,
то выбросится исключение LayerSaltUpdateError
- try_to_refresh_salt() - Попытка обновить соль слоя, подавляя исключение LayerSaltUpdateError
- archive(), freeze(), activate() — Смена статусов. Попытка заархивировать слой с активными размещениями приведет
к выбросу исключения LayerArchivationError
- update_queue() - Поставить слою флаг queue_update_required в True, чтобы пометить его как готовый на пересчёт очереди аллокаций
AllocationService (services/allocation.py):
- create() — Создание размещения и выбора необходимого количества бакетов.
1. Проверяет что слой поддерживает аллокацию по выбранным параметрам:
- Типу объекта (Experiment/Experiment Variant)
- Методу аллокации (Auto/Manual)
- количеству/списку бакетов (должно быть указано что-то одно)
2. Выбирает бакеты:
- Если указано количество бакетов:
1. Вычисляет ограничения по заданным критериям
2. Выбирает бакеты автоматически (AUTO)
- Иначе ручная установка бакетов (MANUAL)
3. Обеспечение целостности связей (???)
4. Создание аллокации (Только статус PLANNED, валидируется в AllocationCreateModel)
5. Логирование статуса аллокации
6. Помечание слоя для обновления очереди аллокаций
- update() — обновление размещения
- get() — получение размещения по ID
- get_many() — получение размещений по списку IDs
- find() — поиск размещений
- delete() — удаление размещения
- activate() — активация размещения
- archive() — архивирование размещения
Repositories#
LayerRepository (repositories/layer/repository.py):
- Сохранение/загрузка слоёв
- Логирование изменений соли и статуса
- Работа с бакетами слоя
AllocationRepository (repositories/allocation/repository.py):
- CRUD операции над размещениями
- Поиск размещений по критериям
- Работа с конфликтами размещений
Use Cases#
Сложные бизнес-сценарии, композирующие несколько сервисов:
CreateAllocationsForExperimentUseCase— создание размещений для экспериментаSaveExperimentAllocationsUseCase— сохранение/обновление размещений (единой транзакцией)GetAvailableTrafficUseCase— расчёт доступного трафикаGetLayerSharedExperimentsUseCase— получение пересекающихся экспериментовUpdateExperimentByAllocationsUseCase— синхронизация эксперимента по размещениямDeleteExperimentAllocationsUseCase— удаление размещений эксперимента
Handlers#
API эндпоинты для фронтенда (internal API). Handlers используют services напрямую для простых операций и use cases для сложной бизнес-логики:
Layer handlers (handlers/internal/layer.py) — используют Services:
addLayerV2— создание слоя (черезlayer.create())updateLayerV2— обновление слоя (черезlayer.update())getLayerV2— получение слоя (черезlayer.get_by_id())findLayersV2— поиск слоёв (черезlayer.find())deleteLayerV2— удаление слоя (черезlayer.delete())refreshLayerSaltV2— обновление соли (черезlayer.refresh_salt())
Allocation handlers (handlers/internal/allocation.py) — используют Services:
getAllocation,getAllocationRich— получение размещения (черезallocation.get())findAllocations,findAllocationsRich— поиск размещений (черезallocation.find())
Allocation Settings handlers (handlers/internal/allocation_settings/handlers.py) — используют Use Cases:
getExperimentAllocationSettings— черезget_experiment_allocation_settings_use_casegetAvailableTraffic— черезget_available_traffic_use_casegetLayerSharedExperiments— черезget_layer_shared_experiments_use_casesaveExperimentAllocationSettings— черезsave_experiment_allocation_settings_use_caseresetExperimentAllocationSettings— черезdelete_experiment_allocations_use_case
Основные потоки работы#
Создание слоя (через Service)#
1. Frontend вызывает API: addLayerV2
2. Handler вызывает Service напрямую: layer.create()
3. Service:
- Валидирует данные слоя
- Сохраняет слой через LayerRepository
- Логирует изменения
4. Возвращает созданный слой
Создание размещения (через Use Case)#
1. Frontend вызывает API: saveExperimentAllocationSettings
2. Handler вызывает Use Case: SaveExperimentAllocationsUseCase
3. Use Case:
- Получает эксперимент через ExperimentService
- Валидирует возможность создания размещений
- Создаёт/обновляет размещения через AllocationService
- Проверяет консистентность через ExperimentAllocationManager
- Обновляет эксперимент через UpdateExperimentByAllocationsUseCase
4. Транзакция коммитится
5. Конфигурируется эксперимент (ExperimentConfigurator)
Запуск эксперимента#
1. ExperimentUseCase активирует размещения
2. AllocationService.update() меняет статус на Active
3. Проверяются конфликты с другими размещениями
4. Если конфликтов нет — эксперимент запускается
5. Если playback — эксперимент отправляется в очередь
Расчёт доступного трафика#
1. Frontend вызывает: getAvailableTraffic
2. Use Case: GetAvailableTrafficUseCase
3. Алгоритм:
- Получает все активные размещения на слое
- Фильтрует по scope (платформы, версии)
- Учитывает политики sharing (Allow/Deny)
- Вычитает занятые bucket'ы
- Возвращает доступные проценты по платформам
Взаимодействие с другими модулями#
Experiment#
Модуль экспериментов активно использует use cases из модуля слоёв:
- При создании эксперимента (
CreateExperimentUseCase): - Использует
CreateAllocationsForExperimentUseCaseдля создания размещений -
Использует
SaveExperimentAllocationSettingsUseCaseдля сохранения настроек размещений -
При обновлении эксперимента (
UpdateExperimentUseCase): - Обновляет размещения через соответствующие use cases слоёв
-
Может вызывать
SaveExperimentAllocationsUseCase -
При запуске эксперимента (
StartExperimentUseCase): - Использует
AllocationServiceнапрямую для активации размещений -
Проверяет консистентность размещений через
ExperimentAllocationManager -
При остановке эксперимента (
InterruptExperimentUseCase,StopExperimentUseCase): - Архивирует размещения через
AllocationService
Splitter#
Модуль сплиттера использует информацию о размещениях для построения правил разбиения:
- В
build_layer_steps()проверяется, мигрирован ли эксперимент на новые слои - Для мигрированных — используются bucket'ы из allocations
- Для не мигрированных — используются границы из
layer_lower_bound/layer_upper_bound
Services#
Модуль слоёв регистрируется в lib/services.py:
LayerService— для работы со слоямиAllocationService— для работы с размещениямиLayerRepository,AllocationRepository— для работы с БД- Use cases модуля — для сложных сценариев