Один образ на 18 клубов

Как устроена мультитенантность в PokerBot Online, почему у каждого клуба своя база и почему бесплатный таймер сделан наоборот.

2 мин чтенияархитектурапокерDocker

PokerBot Online обслуживает 18 клубов спортивного покера. У каждого свой бот, свой домен, свой вид приложения и свои игроки. Код при этом один, и Docker-образы тоже одни на всех.

Что такое клуб с точки зрения системы

Клуб - это строка TENANT_ID и файл с настройками. Все остальное выводится из него:

Что Как разделено
База данных отдельная база Postgres на клуб в общем контейнере
Кэш и очереди префикс ключей в Redis
Живые обновления свои каналы в Centrifugo
Домен маршрут в Traefik
Внешний вид тема клуба в настройках

Три образа - бэкенд, бот и фронтенд - собираются один раз в CI и раскатываются на все клубы одним скриптом.

Фронтенд без пересборки

Самое неочевидное место - фронтенд. Vite при сборке вшивает переменные окружения прямо в бандл, и тогда на каждый клуб нужен свой образ. Вместо этого настройки читаются при запуске: контейнер пишет env.js, страница подключает его раньше приложения. Образ один, а адрес API, тема и имя клуба у каждого свои. Как это сделать - в фишке «Один фронтенд на все окружения».

Почему отдельная база, а не колонка tenant_id

Колонка дешевле: одна база, одна миграция, один бэкап. Но цена ошибки другая. Забытое условие WHERE tenant_id = ... в одном запросе показывает игрокам одного клуба чужой рейтинг или чужие деньги. С отдельной базой такой ошибки просто не может быть: подключение уже смотрит в базу нужного клуба.

Плюсы отдельной базы для клубов:

  • данные клуба можно выгрузить, восстановить или удалить целиком, не трогая остальных;
  • тяжелый отчет одного клуба не тормозит другие;
  • миграции катятся по клубам по очереди, и если что-то пошло не так, задет один клуб, а не все.

Минус - миграций и бэкапов столько, сколько клубов. Это решается автоматизацией: миграции Alembic выполняются на старте контейнера, бэкапы идут по списку клубов.

Когда я сделал наоборот

Бесплатный таймер - тоже для клубов, но там один экземпляр на всех, одна база и колонка club_id. Почему:

  • данных мало и они не про деньги: структура блайндов и список игроков на один вечер;
  • клубов может быть сотни, и контейнер на каждый - перебор;
  • утечка между клубами тут неприятна, но не страшна.

Правило получилось простое. Где деньги и персональные данные - изоляция на уровне базы. Где нет - достаточно колонки и тестов на нее.

Esc

↑↓ выбор Enter открыть