SaaS-продукт обслуживает многих клиентов из одной системы. Каждый клиент, или тенант (tenant), ожидает, что увидит только свои данные, получит стабильную производительность, что бы ни делали остальные, и что его данные можно восстановить или удалить, не затрагивая никого другого. Мультитенантная архитектура складывается из решений, которые делают эти обещания правдой.
Большинство этих решений дёшевы в начале и дороги потом. В этой статье разберём основные модели изоляции, возможности базы данных, которые их подкрепляют, проблему шумного соседа, части системы, о которых забывают, и то, как сменить курс, когда продукт уже работает.
Что такое мультитенантность на практике
Тенантом называют единицу, которая владеет данными и платит: обычно компанию, иногда команду или рабочее пространство. У одного тенанта много пользователей, а один пользователь может состоять в нескольких тенантах, как консультант, который работает в рабочих пространствах нескольких клиентов.
У изоляции три стороны:
- Изоляция данных. Ни один запрос, отчёт, экспорт или поиск никогда не возвращает записи другого тенанта.
- Изоляция производительности. Интенсивная нагрузка одного тенанта не замедляет всех остальных.
- Операционная изоляция. Одного тенанта можно отдельно забэкапить, восстановить, мигрировать, перенести или удалить.
Модели изоляции ниже по-разному размениваются этими тремя сторонами на стоимость и операционную нагрузку.
Три способа разделить данные тенантов
Общая схема с tenant id
Строки всех тенантов лежат в одних и тех же таблицах, и в каждой строке есть столбец tenant_id. Каждый запрос фильтрует по нему.
Эту модель дешевле всего эксплуатировать и проще всего масштабировать на множество небольших тенантов. Одна миграция обновляет всех, а отчёты по всем тенантам строятся просто. Риск тоже прост: один пропущенный фильтр ведёт к утечке данных. Изоляция зависит от того, правилен ли каждый запрос в кодовой базе, поэтому её нужно обеспечивать структурой, а не аккуратностью.
Схема на тенанта
Каждый тенант получает собственную схему (пространство имён таблиц) внутри одной базы данных. Запросы выполняются в схеме тенанта, поэтому пропущенный фильтр не доберётся до таблиц другого тенанта.
Изоляция сильнее, а восстановить или выгрузить одного тенанта проще. Затраты растут вместе с числом тенантов. Миграции нужно запускать для каждой схемы, и миграция, упавшая на полпути, оставляет тенантов на разных версиях. Тысячи схем нагружают пулы подключений и системный каталог базы. Модель лучше подходит продуктам с десятками или сотнями крупных тенантов, чем с тысячами мелких.
База данных на тенанта
Каждый тенант получает собственную базу данных, иногда собственный сервер. Это даёт самую сильную изоляцию по всем трём сторонам: отдельная производительность, отдельные бэкапы, возможность разместить данные тенанта в конкретном регионе и понятный ответ на проверку безопасности со стороны клиента.
Это и самая дорогая в эксплуатации модель. Каждую базу нужно развернуть, мониторить, бэкапить и мигрировать, поэтому модель работает только при основательной автоматизации. Она подходит для небольшого числа крупных или регулируемых клиентов, и многие продукты предлагают её как премиальный тариф поверх общего пула.
Row-level security как подстраховка
В модели с общей схемой фильтр по тенанту может обеспечивать сама база данных. Row-level security (защита на уровне строк) в PostgreSQL позволяет привязать к таблице политику, по которой сессия видит только строки своего текущего тенанта. Обычно тенант читается из параметра сессии, который приложение устанавливает в начале каждого запроса.
Так забытое условие WHERE превращается из утечки данных в пустой результат. Чтобы это держалось, нужно несколько правил:
- Приложение подключается под ролью, которая не может обойти политики. Владельцы таблиц и суперпользователи по умолчанию их пропускают.
- Параметр тенанта устанавливается на каждую транзакцию и потом сбрасывается, чтобы соединение из пула никогда не переносило контекст одного тенанта в следующий запрос.
- Политики покрывают каждую таблицу, принадлежащую тенантам, и тест проверяет, что ни одна такая таблица не осталась без политики.
Row-level security служит второй линией защиты, а не единственной. Слой доступа к данным всё равно должен ограничивать каждый запрос тенантом, чтобы до утечки дошло, только если откажут оба.
Шумные соседи
В любой общей модели тенанты конкурируют за одни и те же процессор, память, подключения и диск. Один тенант с большим импортом или тяжёлым отчётом может замедлить продукт для всех.
Защита строится слоями:
- Лимиты и квоты на тенанта для вызовов API, фоновых задач и хранилища, привязанные к его тарифу.
- Справедливые очереди для фоновой работы, чтобы накопившиеся задачи одного тенанта не лишали ресурсов остальных.
- Бюджеты запросов: таймауты, лимиты пагинации и индексы, которые начинаются с tenant id.
- Метрики по тенантам, чтобы видеть, кто что использует, а не только то, что система тормозит.
- Запасной выход: возможность перенести тяжёлого тенанта в собственную базу или пул без изменения кода.