Перейти к содержанию

Модуль слоёв: архитектура и разработка#

Обзор#

Модуль 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_case
  • getAvailableTraffic — через get_available_traffic_use_case
  • getLayerSharedExperiments — через get_layer_shared_experiments_use_case
  • saveExperimentAllocationSettings — через save_experiment_allocation_settings_use_case
  • resetExperimentAllocationSettings — через 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 модуля — для сложных сценариев

Ссылки#