Postgres-схемы
| Схема | Домен | Ключевые таблицы | Владелец |
|---|---|---|---|
| auth | Аутентификация и RBAC | user, refresh_token, oauth_connections, api_key, roles, permissions, user_roles, role_permissions, user_permission_deny | auth-service |
| players | Идентичность игрока | user, social_account, social_account_visibility, user_merge_audit | app-service |
| public | Воркспейсы, сетки дивизионов, инфра | workspace, workspace_member, division_grid*, settings, event_outbox | app-service |
| tournament | Структура турнира и сетка | tournament, stage, stage_item, team, player, standing, encounter, encounter_link, challonge_*, computation_job | tournament-service |
| overwatch | Справочник игры | hero, map, gamemode | app / parser |
| overwatch_rank | Телеметрия рангов OW | rank_snapshot, battle_tag_state, fetch_log | parser-service |
| matches | Разобранные матч-логи | match, statistics, kill_feed, assists, mv_hero_global_stats (MV) | parser-service |
| balancer | Регистрация и балансировка | registration*, balance*, team*, tournament_config, draft_* | balancer-service |
| achievements | Движок достижений | rule, evaluation_result, override, evaluation_run | parser / app |
| analytics | Аналитика и ML | tournament, shifts, performance, standings_distribution, match_quality, ml_*, job, … | analytics-service |
| log_processing | Загрузка/парсинг логов | record, discord_channel | parser / discord |
| realtime | Журнал realtime-событий | workspace_event | gateway (Go) |
| subscriptions | Подписки и энтайтлменты | provider_config, requirement, entitlement, check_log | tournament / parser |
Общие хабы
public.workspace
арендатор (мультитенантность). Почти всё скоупится по
workspace_id.players.user
доменная идентичность игрока (может существовать без аккаунта — «shadow player» из логов/CSV).
auth.user
учётная запись для входа; линк к
players.user — 1:0..1.workspace_member
якорь принадлежности игрока к воркспейсу (уникальность по
workspace_id + player_id); на него по workspace_member_id ссылаются ростер (tournament.player), регистрации (balancer.registration), драфт (draft_team/draft_player/draft_pick) и достижения (evaluation_result/override). Денормализованной роли нет — роль выводится из RBAC.tournament.tournament
турнир; корень для стадий, команд, матчей, регистраций, аналитики.
overwatch.hero
герой; на него ссылаются статистика, топ-герои регистрации, достижения.
Карта доменов
Какие схемы на что ссылаются. Пунктирное ребро — связь «1:0..1» между auth и players.
100%
Рендер диаграммы…
История изменений схемы
Документ актуализирован под финальное состояние (Alembic head — wsreq0002).
Identity/workspace-рефактор
players.user.auth_user_id (unique nullable; NULL = shadow-player) — линк 1:0..1 к auth.user. public.workspace_member ключуется на player_id (FK → players.user) с уникальностью (workspace_id, player_id); денормализованная роль убрана. На workspace_member_id теперь якорятся balancer.registration (dbarch02), tournament.player (iwrefac07, NOT NULL), draft_team/draft_player/draft_pick (dbarch03) и achievements.evaluation_result/override.Challonge-нормализация (dbarch04 + dbarch04b)
Удалены
tournament.tournament.challonge_id/challonge_slug, tournament.stage.challonge_id/challonge_slug, tournament.encounter.challonge_id и таблица tournament.challonge_team. Источник правды — challonge_source + challonge_participant_mapping + challonge_match_mapping + challonge_sync_log. Таблица tournament.group (вместе со своими challonge_id/challonge_slug) снесена миграцией dropgrp01; группа Challonge связывается со стадией только через stage.settings_json.challonge_group_id, по которому и роутится match.group_id. Одноимённые поля ответов API живы, но ВЫВОДЯТСЯ из challonge_source/challonge_match_mapping резолверами shared/services/challonge_refs.py — сериализатор, который их не прокинул, отдаёт тихий null.JSON-нормализация (dbarch05)
map_veto_config.map_pool_ids (JSON) → дочерняя map_veto_config_map; veto_sequence_json остался JSON.Драфт без ролей и рангов (draftreg1)
Роли и ранги в драфте не хранятся вообще.
dbarch03 вынес их из JSON в balancer.draft_player_role + draft_player_role_hero, а draftreg1 снёс обе таблицы вместе с колонками draft_player.primary_role/sub_role/is_flex/division_number/rank_value/battle_tag/additional_info. Осталась ссылка draft_player.registration_id → balancer.registration (NOT NULL, RESTRICT, UK (session_id, registration_id) вместо uq_draft_player_session_member), а роли и ранги резолвятся на чтении из balancer.registration_role движком shared.services.roster. RESTRICT намеренно: заявка удаляется мягко, поэтому жёсткое удаление — это попытка стереть строку, от которой зависит драфт, и она отклоняется, а не рушит историю. Единственная сохранённая производная — draft_pick.target_role/target_rank_value, замороженные как исторический факт о состоявшемся пике.Predictions (dbarch06)
analytics.predictions (v1) удалена; analytics.standings_distribution (v2) — единственный источник прогноза мест.Гигиена индексов/FK (dbarch01)
Индексы на
auth.user_roles/auth.role_permissions; новые FK: achievements.evaluation_result.run_id → evaluation_run, players.user_merge_audit.source_user_id/target_user_id → players.user, auth.user_permission_deny.created_by → auth.user; перенос типа encounterstatus public → tournament; частичный unique-индекс на players.social_account для NULL-хендлов.Правило подписки на воркспейсе (wsreq0001 + wsreq0002)
Новая таблица
subscriptions.requirement (workspace_id, name, requirement_json, is_default; UK (workspace_id, name) плюс частичный unique на дефолтную строку) — единственный источник правила допуска, общего для всех турниров воркспейса. Пара expand/contract, порядок обязателен: wsreq0001 создаёт таблицу и бэкфиллит её из форм (до раскатки кода, потому что новый load_requirement селектит эту таблицу), затем wsreq0002 удаляет столбец с правилом из balancer.registration_form (после раскатки, потому что старая ORM всё ещё маппит его и SQLAlchemy эмитит его в каждом SELECT). Бэкфилл не выбирает правило за организатора: если у воркспейса больше одного различного правила, wsreq0001 падает и откатывается. Тумблер require_subscription остался на форме — он и есть пер-турнирное решение.Заметки по чтению диаграмм
Мультитенантность
Почти каждая бизнес-таблица несёт
workspace_id (напрямую или транзитивно через tournament/workspace_member). Глобальные сущности (роль/деней/сетка/статус) допускают workspace_id = NULL.Двойная идентичность
auth.user (вход) и players.user (игрок) — разные таблицы, связь 1:0..1. Ростер (tournament.player), регистрации (balancer.registration), драфт (draft_*) и достижения (evaluation_result/override) якорятся на workspace_member (= уникальность workspace_id + player_id), что и есть суть identity/workspace-рефактора. Денормализованной роли на workspace_member нет — роль выводится из RBAC.Legacy не осталось
tournament.group снесена миграцией dropgrp01 (группы = stage_item типа GROUP). achievements.achievement/achievements.user и analytics.predictions (v1) — удалены (см. «История изменений схемы»).Циклические FK
draft_session.current_pick_id ↔ draft_pick.session_id (создаётся с use_alter); division_grid_version.created_from_version_id и tournament.player.related_player_id — само-ссылки.Enum encounterstatus
Тип перенесён из схемы
public в tournament (dbarch01); хранит имя члена (COMPLETED/PENDING/OPEN), не .value.mv_hero_global_stats
materialized view (не в диаграммах как таблица): глобальные рекорды по (hero, stat), обновляется вне транзакций.