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

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

Что такое тенант и почему это ключевое решение

Тенант (арендатор) — это клиент сервиса, обычно организация со своими пользователями, настройками и данными. Мультиарендная, или multi-tenant, архитектура означает, что одно приложение обслуживает всех тенантов сразу, а не разворачивается отдельно для каждого.

Это дешевле в эксплуатации и проще в обновлении: один релиз — и новая версия у всех. Обратная сторона — риск утечки между клиентами. Если в одном запросе забыли фильтр по организации, сотрудник одной компании увидит заказы другой. Для B2B-сервиса это не баг, а кризис доверия.

Способ изоляции влияет на модель данных, миграции, резервное копирование и стоимость серверов. Поменять его, когда в базе уже живые данные клиентов, — долгий и рискованный проект. Поэтому, как мы пишем в статье о разработке SaaS с нуля, это одно из немногих решений, которое нужно принять до первой строки кода.

Три подхода к изоляции данных

Общая база и tenant_id в каждой таблице

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

Отдельная схема на каждого клиента

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

Отдельная база на каждого клиента

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

Сравнение подходов

КритерийОбщая база + tenant_idСхема на клиентаБаза на клиента
ИзоляцияЛогическая, зависит от кода и политик базыСредняя, на уровне схемСильная, физическое разделение
Стоимость инфраструктурыНизкаяСредняяВысокая, растёт с числом клиентов
МиграцииОдин прогонПо каждой схемеПо каждой базе, нужна оркестрация
МасштабированиеИндексы, реплики, затем шардирование по tenant_idПеренос схем между серверамиПеренос баз между серверами
Восстановление одного клиентаСложно: выборка строк из бэкапаПроще: восстановить одну схемуПросто: восстановить одну базу
Когда подходитБольшинство B2B-сервисов, много небольших клиентовДесятки клиентов с повышенными требованиямиКрупные клиенты, отраслевые требования, отдельный контур

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

Row-level security: страховка на уровне базы

В подходе с общей базой главная угроза — забытый фильтр. Защита строится в два слоя.

Первый слой — приложение. Текущий тенант определяется один раз при обработке запроса (из сессии или токена) и автоматически подставляется во все запросы через базовый репозиторий или настройки ORM. Писать фильтр вручную в каждом месте — гарантированно где-нибудь забыть.

Второй слой — политики безопасности строк в базе данных. Например, в PostgreSQL можно включить row-level security: в начале транзакции приложение записывает идентификатор тенанта в переменную сессии, а политика на таблице пропускает только строки с этим идентификатором. Даже если в коде ошибка, база не вернёт чужие данные.

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

Изоляция данных — фундамент, на котором строится и разграничение прав внутри одной организации; об этом — в статье роли и права доступа в SaaS.

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

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

  • Ограничение частоты запросов на уровне тенанта, а не только пользователя или IP.
  • Тяжёлые операции — в фоновые очереди: отчёты, импорт, массовые рассылки. Справедливое распределение задач, чтобы один клиент не занимал всю очередь.
  • Индексы, начинающиеся с tenant_id, чтобы запросы одного клиента не сканировали данные остальных.
  • Квоты тарифа: объём хранилища, число записей, лимит API-вызовов.
  • Метрики по тенантам: видно, кто потребляет больше всего ресурсов, и можно принять решение — оптимизировать запросы, перевести на тариф выше или вынести в отдельную базу.

Бэкапы, восстановление и выгрузка данных клиента

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

  • Продумайте процедуру: бэкап поднимается во временное окружение, оттуда выбираются строки нужного тенанта и аккуратно переносятся в рабочую базу.
  • Мягкое удаление с корзиной на несколько дней снимает большую часть таких запросов.
  • Выгрузка всех данных организации в открытом формате нужна и для доверия, и на случай, если клиент уходит. Её удобно делать фоновой задачей со ссылкой на скачивание.
  • Полное удаление данных тенанта после окончания договора — отдельный сценарий: все таблицы, файлы в хранилище, копии в поисковом индексе. Сроки фиксируйте в оферте.
  • Регулярно проверяйте восстановление на практике. Бэкап, который ни разу не разворачивали, — это предположение, а не гарантия.

Хранение персональных данных и требования клиентов

Если сервис обрабатывает персональные данные граждан России — а почти любой SaaS хранит хотя бы имена и email пользователей, — действует 152-ФЗ. В числе прочего он требует, чтобы при сборе такие данные записывались и хранились в базах на территории России. Поэтому основную базу и резервные копии разумно размещать в российском дата-центре.

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

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

Что выбрать на старте

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

Схему или базу на клиента стоит выбирать сразу, если вы заранее знаете, что клиентов будет немного, они крупные и у них есть формальные требования к изоляции. А для первой версии продукта, как мы пишем в статье о MVP SaaS, важнее заложить правильный фундамент, чем строить инфраструктуру под гипотетических корпоративных клиентов.

Частые вопросы

Можно ли перейти с общей базы на отдельные базы позже?

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

Достаточно ли фильтра по tenant_id в коде?

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

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

Для персональных данных граждан России действует требование о локализации. Если сервис их собирает, базу нужно размещать в России; конкретную схему стоит согласовать с юристом.

Как часто нужно делать резервные копии?

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

Обсудим проект

Нужна похожая разработка?

Расскажите о задаче — уточним детали, предложим формат запуска и подготовим предварительную оценку. Это бесплатно.

Написать напрямую