Overwatch Tournaments нужны функциональные cookie, чтобы сохранять вход и язык интерфейса. Аналитические cookie необязательны и только показывают, как используются страницы, — подробнее в политике конфиденциальности.

Перейти к содержимому

Модель данных

Все сервисы делят одну базу PostgreSQL, а границы доменов проведены схемами Postgres: схема принадлежит одному сервису, и он один в неё пишет. Эта статья объясняет карту схем, четыре слоя идентичности, привязку строк к воркспейсу и две инфраструктурные таблицы, поведение которых видно снаружи: аутбокс событий и журнал realtime.

Схемы как границы доменов

СхемаДоменВладелец
authАккаунты входа, сессии, OAuth, ключи API, RBACidentity-service
playersИдентичность игрока и привязанные социальные аккаунтыapp-service
publicВоркспейсы, участники, сетки дивизионов, настройки, аутбоксapp-service
tournamentСтруктура турнира, сетка, встречи, pick/ban, отчётыtournament-service
casualКазуальные матчи и скримы вне турнирного дереваtournament-service
overwatchКаталог игры — герои, карты, режимыapp-service / parser-service
overwatch_rankТелеметрия рангов Overwatch из OverFastparser-service
matchesРазобранные логи матчей и производная статистикаparser-service
balancerРегистрация, балансировка, живой драфт, ранги участниковbalancer-service
achievementsПравила достижений, вычисления и ручные правкиparser-service / app-service
analyticsАналитические сигналы и реестр ML-моделейanalytics-service
log_processingЗаписи о загрузке и разборе логовparser-service / discord-service
realtimeЖурнал событий для доигрывания по WebSocketшлюз (Go)
subscriptionsПровайдеры подписок, требования и вердиктыtournament-service / parser-service
quotaПланы квот, стоимость операций и переопределениявсе сервисы, через shared.quota

Схема balancer шире своего названия: в ней живут и заявки на турнир, и результаты балансировки, и ранги участников. Имя пакета моделей тоже не всегда совпадает со схемой — например, модели рангов пишут в overwatch_rank, а модели загрузки логов — в log_processing.

Узлы, в которые сходится почти всё: public.workspace — арендатор; public.workspace_member — участие игрока в одном воркспейсе; players.user — игрок; auth.user — аккаунт входа; tournament.tournament — корень стадий, команд, встреч и заявок; overwatch.hero — на него ссылаются статистика, предпочтения в заявке и достижения.

Четыре слоя идентичности

Единой сущности «пользователь» в системе нет. Есть четыре слоя, которые иногда сходятся на одном человеке, а иногда годами живут порознь.

СлойТаблицаНа какой вопрос отвечает
Аккаунт входаauth.userКто может войти и что эта сессия вправе делать
Игрокplayers.userКто этот человек в турнирах, логах и статистике
Участие в воркспейсеpublic.workspace_memberСуществует ли этот игрок в этом сообществе
Рангиbalancer.member_rank и registration_role.rank_valueКакой у участника SR на роли

Связь аккаунта и игрока — 1:0..1 через уникальную nullable-колонку players.user.auth_user_id. Пустое значение означает виртуального игрока: он пришёл из лога матча, импорта CSV или был добавлен организатором в ростер, и войти не может. Разделять эти слои приходится потому, что игрок без аккаунта — нормальное состояние, а аккаунт без игрока — дыра: на него нельзя повесить участие в воркспейсе, поэтому регистрация аккаунта сразу создаёт пустую строку игрока.

public.workspace_member уникален по паре workspace_id + player_id и не хранит ни auth_user_id, ни колонку роли. Это и есть якорь арендатора: ростеры, заявки, драфт, ранги и достижения ссылаются на участника, а не на голого игрока, поэтому изоляция сообществ получается по построению, а виртуальный игрок спокойно живёт в составе и накапливает историю. Права при этом остаются на аккаунте: RBAC вычисляется по ролям auth.user, и до привязки аккаунта у участника прав нет.

Ранг — четвёртый слой, потому что «SR игрока» не одно число. В balancer.member_rank два уровня различаются одним полем author_user_id: NULL — канон воркспейса, который видят все, любое другое значение — личная книга конкретного автора. Остальные слои лежат вне этой таблицы: значение из заявки (registration_role.rank_value) и снимок ранга Overwatch. Побеждает первый слой, в котором есть число, а порядок задаёт контекст: микс читает автор → канон воркспейса → Overwatch, турнир — заявка → канон воркспейса → Overwatch. Поэтому «следовать канону» — это отсутствие строки, а не копия значения, и очистка ранга удаляет запись, а не пишет ноль.

Привязка к воркспейсу

Почти каждая бизнес-строка несёт workspace_id — напрямую либо транзитивно через tournament или workspace_member. Строки, которые намеренно глобальны (системные роли, точечные запреты прав, сетки дивизионов, статусы заявок), допускают workspace_id = NULL. Для API это значит ровно то, что описано в обзоре: доменное чтение без названного воркспейса отклоняется, а не возвращает чужие строки.

Аутбокс событий

public.event_outbox — транзакционный аутбокс: доменное событие пишется в той же транзакции, что и сама бизнес-правка, поэтому «поменяли, но не опубликовали» невозможно. Строку разгребает единственный подметальщик в tournament-сервисе и публикует её с повторами; event_id уникален, чтобы потребитель мог дедуплицировать, а status, attempts и next_attempt_at хранят состояние повторов. Доставка at-least-once: потребители обязаны быть идемпотентными, а пока подметальщик лежит, события всех сервисов копятся в очереди, но не теряются.

Журнал realtime

realtime.workspace_event — журнал, из которого шлюз доигрывает пропущенное. У строки есть topic, event_type, payload, время и schema_version, позволяющая менять форму полезной нагрузки, не обесценивая старые записи; при переподключении клиент называет последний виденный id, и шлюз отдаёт всё, что было после него по его топикам. Записан в журнал не каждый сигнал: часть топиков намеренно недолговечна — подписки воркспейса, статусы трансляций турнира и личные уведомления не оставляют строки и не доигрываются, потому что клиент всё равно перечитывает список, ради которого он их ждал. Подробности подписок — в статье Realtime (WebSocket).

Ключи воркспейса и турнира в журнальных таблицах (включая аудит и уведомления) хранятся как обычные числа без внешних ключей. Это не недосмотр: журнал должен пережить строки, которые описывает, и не должен утягиваться каскадным удалением.

Где смотреть точную схему

  • Схема БД — интерактивные диаграммы таблиц прямо на сайте, сгенерированные из моделей.
  • docs/database_erd.md — тот же источник в репозитории: каждая колонка, связь и уникальный индекс. Диаграммы генерируются из метаданных ORM, а CI падает, если они разошлись с кодом.
  • docs/users-identity.md — полная семантика идентичности: виртуальные игроки, привязка аккаунта, слияние.

См. также