Короткий ответ
Выбор зависит от числа и размера тенантов, требований к изоляции и операционной нагрузки: общая таблица с tenant_id дешевле в эксплуатации и проще масштабируется на много мелких тенантов, но требует дисциплины, чтобы ни один запрос не забыл фильтр по tenant_id; отдельная БД на тенанта даёт максимальную изоляцию ценой значительно более сложной эксплуатации при росте числа тенантов.
Как это работает подробнее
При общей таблице изоляция обеспечивается на уровне кода приложения и/или row-level security в самой БД (например, PostgreSQL RLS), которая принудительно добавляет условие по tenant_id к каждому запросу независимо от того, вспомнил ли об этом разработчик. Отдельная схема или БД физически разделяют данные, снижая риск «протекания» данных из-за ошибки в одном SQL-запросе, но увеличивают число объектов для миграций, мониторинга и резервного копирования пропорционально числу тенантов.
- Компромисс. Общая таблица с RLS дешевле и проще масштабируется на множество тенантов, но инцидент из-за одной ошибки в фильтрации потенциально затрагивает все данные платформы; отдельная БД на тенанта радикально снижает радиус поражения при ошибке, но кратно увеличивает сложность миграций и стоимость инфраструктуры при росте числа тенантов. Критерии выбора — число и размер тенантов, регуляторные требования конкретных клиентов и зрелость автоматизации управления множеством инстансов БД в команде.
Пример кода
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON orders
USING (tenant_id = current_setting('app.current_tenant')::uuid);
-- PostgreSQL применяет tenant-фильтр независимо от запроса приложенияЧто использовать на практике
- Практика. Для платформы с тысячами мелких тенантов схема «одна таблица + tenant_id + RLS» обычно единственно реалистична операционно; для десятков крупных корпоративных клиентов с жёсткими требованиями к изоляции отдельная БД на тенанта может быть оправдана, вплоть до требования заказчика о физически отдельном хранилище.
Подводные камни
- Риск. Полагаются только на дисциплину разработчиков добавлять
WHERE tenant_id = ?вручную без технического принуждения (RLS, обязательный scoped query builder), что рано или поздно приводит к утечке данных между тенантами; выбирают отдельную БД на тенанта с самого начала при небольшом числе клиентов, создавая непропорциональную операционную нагрузку на ранней стадии продукта.