Перейти к содержимому
Bitmason
Платформы

Мультитенантная архитектура SaaS: как разделить данные клиентов

Общая схема, схема на тенанта или база на тенанта. Как работает каждая модель изоляции, где она протекает и почему мультитенантность должна охватывать авторизацию, кэши, задачи и бэкапы, а не только базу.

5 мин чтения

SaaS-продукт обслуживает многих клиентов из одной системы. Каждый клиент, или тенант (tenant), ожидает, что увидит только свои данные, получит стабильную производительность, что бы ни делали остальные, и что его данные можно восстановить или удалить, не затрагивая никого другого. Мультитенантная архитектура складывается из решений, которые делают эти обещания правдой.

Большинство этих решений дёшевы в начале и дороги потом. В этой статье разберём основные модели изоляции, возможности базы данных, которые их подкрепляют, проблему шумного соседа, части системы, о которых забывают, и то, как сменить курс, когда продукт уже работает.

Что такое мультитенантность на практике

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

У изоляции три стороны:

  • Изоляция данных. Ни один запрос, отчёт, экспорт или поиск никогда не возвращает записи другого тенанта.
  • Изоляция производительности. Интенсивная нагрузка одного тенанта не замедляет всех остальных.
  • Операционная изоляция. Одного тенанта можно отдельно забэкапить, восстановить, мигрировать, перенести или удалить.

Модели изоляции ниже по-разному размениваются этими тремя сторонами на стоимость и операционную нагрузку.

Три способа разделить данные тенантов

Общая схема с tenant id

Строки всех тенантов лежат в одних и тех же таблицах, и в каждой строке есть столбец tenant_id. Каждый запрос фильтрует по нему.

Эту модель дешевле всего эксплуатировать и проще всего масштабировать на множество небольших тенантов. Одна миграция обновляет всех, а отчёты по всем тенантам строятся просто. Риск тоже прост: один пропущенный фильтр ведёт к утечке данных. Изоляция зависит от того, правилен ли каждый запрос в кодовой базе, поэтому её нужно обеспечивать структурой, а не аккуратностью.

Схема на тенанта

Каждый тенант получает собственную схему (пространство имён таблиц) внутри одной базы данных. Запросы выполняются в схеме тенанта, поэтому пропущенный фильтр не доберётся до таблиц другого тенанта.

Изоляция сильнее, а восстановить или выгрузить одного тенанта проще. Затраты растут вместе с числом тенантов. Миграции нужно запускать для каждой схемы, и миграция, упавшая на полпути, оставляет тенантов на разных версиях. Тысячи схем нагружают пулы подключений и системный каталог базы. Модель лучше подходит продуктам с десятками или сотнями крупных тенантов, чем с тысячами мелких.

База данных на тенанта

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

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

Row-level security как подстраховка

В модели с общей схемой фильтр по тенанту может обеспечивать сама база данных. Row-level security (защита на уровне строк) в PostgreSQL позволяет привязать к таблице политику, по которой сессия видит только строки своего текущего тенанта. Обычно тенант читается из параметра сессии, который приложение устанавливает в начале каждого запроса.

Так забытое условие WHERE превращается из утечки данных в пустой результат. Чтобы это держалось, нужно несколько правил:

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

Row-level security служит второй линией защиты, а не единственной. Слой доступа к данным всё равно должен ограничивать каждый запрос тенантом, чтобы до утечки дошло, только если откажут оба.

Шумные соседи

В любой общей модели тенанты конкурируют за одни и те же процессор, память, подключения и диск. Один тенант с большим импортом или тяжёлым отчётом может замедлить продукт для всех.

Защита строится слоями:

  • Лимиты и квоты на тенанта для вызовов API, фоновых задач и хранилища, привязанные к его тарифу.
  • Справедливые очереди для фоновой работы, чтобы накопившиеся задачи одного тенанта не лишали ресурсов остальных.
  • Бюджеты запросов: таймауты, лимиты пагинации и индексы, которые начинаются с tenant id.
  • Метрики по тенантам, чтобы видеть, кто что использует, а не только то, что система тормозит.
  • Запасной выход: возможность перенести тяжёлого тенанта в собственную базу или пул без изменения кода.

Мультитенантность за пределами базы данных

Изоляцию часто проектируют для базы данных и забывают обо всём остальном. Тенант должен присутствовать в каждом из этих мест:

  • Аутентификация и авторизация. Тенант должен браться из проверенной сессии или токена, а не из параметра запроса, который клиент может изменить. Роли хранятся для каждого тенанта отдельно, поскольку один и тот же человек может быть администратором в одном рабочем пространстве и наблюдателем в другом.
  • Кэширование. Каждый ключ кэша включает tenant id. Закэшированная страница или результат запроса, общие для разных тенантов, на практике становятся одной из самых частых причин утечек.
  • Фоновые задачи. Каждая задача несёт своего тенанта и устанавливает контекст тенанта до того, как коснётся данных, точно так же, как веб-запрос.
  • Файлы и поиск. Пути в объектном хранилище и поисковые индексы разделены по тенантам, а подписанные ссылки на скачивание проверяются на соответствие запрашивающему тенанту.
  • Логи и аналитика. Tenant id в логах делает возможной работу поддержки и разбор инцидентов; персональные данные в логах требуют той же осторожности, что и в базе.
  • Бэкапы и удаление. Нужно уметь восстановить одного тенанта на момент времени и полностью удалить одного тенанта, когда заканчивается его договор. В общей схеме для этого нужен специальный инструментарий, и его стоит построить до того, как об этом попросит первый клиент.

Выбор модели и переход потом

Разумный вариант по умолчанию для нового B2B SaaS-продукта: общая схема с tenant id в каждой таблице, изоляцию которой обеспечивают слой доступа к данным и row-level security, а кэширование и фоновые задачи с первого дня учитывают тенанта. Это дёшево, масштабируется на множество тенантов и оставляет дверь открытой.

Держите дверь открытой намеренно:

  • Получайте подключение к базе данных каждого тенанта через реестр тенантов, даже пока все тенанты указывают в одно место.
  • Используйте идентификаторы, уникальные для всех тенантов, чтобы строки тенанта можно было скопировать в другое место без конфликтов.
  • Чётко отделяйте данные тенантов от глобальных данных, таких как тарифы и справочные таблицы.

Когда всё это есть, перенос крупного или регулируемого клиента в собственную базу становится миграцией данных и изменением в реестре, а не переписыванием. Обратный путь, от многих баз к одной, гораздо сложнее, и это ещё одна причина начинать с общей модели.

Выбирайте базу данных на тенанта с самого начала, только когда этого требует рынок: договоры, требующие физического разделения, размещение данных в регионе по выбору каждого клиента или несколько очень крупных клиентов, чья нагрузка подавила бы общий пул.

Коротко

  • Мультитенантность решается до первой таблицы и доходит до каждого слоя: авторизации, кэшей, задач, файлов, логов и бэкапов.
  • Общая схема обычно выбирается по умолчанию; схема на тенанта и база на тенанта дают более сильную изоляцию ценой более дорогой эксплуатации.
  • Обеспечивайте изоляцию дважды, в коде и в базе данных, и проверяйте тестами, что она держится.
  • Готовьтесь к шумным соседям: квоты, справедливые очереди и метрики по тенантам.
  • Направляйте подключения через реестр тенантов, чтобы любой тенант мог позже переехать без переписывания.

Вопросы по статье

Достаточно ли безопасна общая схема с tenant id для бизнес-клиентов?

Для большинства B2B-продуктов да, если изоляция обеспечивается больше чем в одном месте, например в слое доступа к данным и через row-level security в базе, и это проверено тестами. Некоторые клиенты из регулируемых отраслей всё равно попросят выделенную базу данных.

Можно ли дать выделенную базу данных только одному крупному клиенту?

Да. Многие продукты держат большинство тенантов в общем пуле, а нескольких в собственных базах. Лучше всего это работает, когда код получает подключение каждого тенанта из реестра, а не рассчитывает на одну базу.

Когда закладывать мультитенантность?

До создания первой таблицы. Добавить tenant id в работающий продукт значит затронуть каждый запрос, каждый ключ кэша и каждую фоновую задачу, а это гораздо сложнее, чем заложить его с самого начала.

Исключает ли мультитенантность настройку под отдельного клиента?

Нет, но настройки должны жить в конфигурации и фиче-флагах, которые хранятся для каждого тенанта, а не в отдельных ветках кода. Ветки под каждого клиента превращают один продукт во множество.

  • Инженерия

    Как выбрать стек для SaaS с жёсткими требованиями комплаенса

    Илья Исматов5 мин чтения

  • ИИ-⁠инженерия

    ИИ-агенты в команде разработки: где они помогают, а где решают люди

    Илья Исматов7 мин чтения

Что вы создаёте?

Расскажите о своём проекте. Мы свяжемся с вами, чтобы обсудить объём работ, сроки и первые шаги.

Начать