Как реализовать масштабируемый multi-tenant SaaS с изоляцией данных — без костылей и катастроф

Ты разрабатываешь SaaS-продукт. У тебя уже есть первые клиенты — маленькие компании, которые платят за доступ. Всё идёт хорошо. Но ты знаешь: через полгода их станет 50, потом 500. И тут начинается самое интересное — как не устроить хаос, когда все клиенты будут использовать один и тот же сервер, одну базу данных, один код?

Если ты не продумал изоляцию данных с самого начала, то в один прекрасный день один клиент случайно увидит данные другого. Или твой сервер упадёт под нагрузкой, и все клиенты потеряют доступ. Или ты не сможешь добавить новый функционал, потому что он сломает старую логику для кого-то. Это не теория. Это реальность, в которую впадают 7 из 10 стартапов, которые начинают с «а давайте просто сделаем один общий сервер».

В этой статье я расскажу, как сделать multi-tenant SaaS так, чтобы:

  • Данные одного клиента не смешивались с данными другого — даже если кто-то сломает код;
  • Сервис не тормозил при росте клиентов;
  • Ты мог легко добавлять новые функции, не ломая всё;
  • И не потратил на это 6 месяцев и 200 тысяч долларов.

Что такое multi-tenant SaaS и зачем тебе изоляция

Multi-tenant — это когда один экземпляр твоего приложения обслуживает сразу много клиентов («тенантов»). Это выгодно: ты не развертываешь отдельный сервер для каждого клиента. Это экономит ресурсы, упрощает обновления и снижает стоимость поддержки.

Но есть ловушка: если ты не изолируешь данные, то всё, что делает один клиент, может повлиять на другого. Допустим, у тебя есть CRM-система. Клиент А — агентство недвижимости. Клиент Б — маленький магазин. Если ты храните все данные в одной таблице clients без фильтрации по tenant_id, то:

  • Злоумышленник может подобрать ID и вытащить чужие данные;
  • Ошибки в запросах — и ты случайно удалишь контакты из двух компаний;
  • Когда клиент А захочет кастомный отчёт, ты не сможешь его сделать, не сломав отчёт для клиента Б.

Изоляция — это не про «хорошо бы». Это про юридическую безопасность, репутацию и выживание бизнеса. GDPR, CCPA, ФЗ-152 — все требуют, чтобы данные одного клиента не попадали к другому. Нарушение — это штрафы, иски, потеря клиентов. И ты не сможешь продать продукт крупной компании, если не докажешь, что изоляция есть.

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

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

Подход Как работает Плюсы Минусы Когда подходит
Общая база, общая схема Все клиенты в одной таблице. Каждая запись имеет поле tenant_id. Все запросы фильтруются по нему. Просто в разработке. Легко обновлять. Мало затрат на хостинг. Один баг — и все клиенты страдают. Сложно кастомизировать. Риск утечки, если забыл фильтр. Стартап с 1–50 клиентами. Нет требований к кастомизации. Нет чувствительных данных.
Общая база, отдельные схемы Одна БД, но для каждого клиента — отдельная схема (в PostgreSQL — schema, в MySQL — database). Код работает с динамическим выбором схемы. Хорошая изоляция. Можно кастомизировать структуру для каждого клиента. Легко бэкапить по клиентам. Сложнее миграции. Труднее делать аналитику по всем клиентам. Нужно больше памяти. 50–500 клиентов. Есть клиенты с особыми требованиями к структуре данных. Нужна гибкость.
Отдельные базы Каждому клиенту — отдельная база данных на отдельном сервере или в отдельном контейнере. Полная изоляция. Можно масштабировать по клиентам. Легко переносить клиентов между серверами. Безопасно. Сложно управлять. Высокие затраты на хостинг. Миграции — кошмар. Требует автоматизации. 500+ клиентов. Клиенты — корпорации с жёсткими требованиями к безопасности. Данные — финансовые, медицинские, юридические.

Самый частый выбор — первый вариант. Он работает. Пока клиентов мало. Но если ты планируешь расти — не останавливайся на нём. Это как строить дом на песке и надеяться, что ветер не подует.

Как не сломать всё при переходе на изоляцию

Ты уже запустил продукт с общим подходом. Клиенты есть. И ты понял: пора менять архитектуру. Это страшно. Но можно сделать это без катастрофы.

  1. Добавь tenant_id везде. Даже если сейчас всё в одной таблице — начни с этого. Добавь поле в каждую таблицу, где есть данные клиента. Не жди «когда будет время» — делай прямо сейчас.
  2. Запусти фильтрацию на уровне приложения. Всё, что обращается к БД — должно автоматически добавлять WHERE tenant_id = current_tenant. Не доверяй разработчикам — сделай это в ORM или на уровне репозитория. Например, в Django — через middleware. В Node.js — через интерцепторы запросов.
  3. Проверь все старые запросы. Найди все места, где ты используешь SELECT * FROM users без фильтрации. Добавь тесты, которые проверяют, что никто не может получить данные другого клиента.
  4. Сделай миграцию постепенно. Не переноси всех клиентов сразу. Выбери одного-двух «добровольцев» — тех, кто согласен на тест. Перенеси их данные в отдельную схему. Проверь, всё ли работает. Потом — ещё двух. И так до 10%. Потом — до 50%. Потом — до 100%.
  5. Автоматизируй развертывание. Если ты перешёл на отдельные схемы или базы — тебе нужно, чтобы при регистрации нового клиента автоматически создавалась схема, мигрировались таблицы, настраивались права. Используй Terraform, Ansible или просто скрипты на Python/Node.js. Не делай это вручную.

Не пытайся всё переделать за неделю. Ты не спасёшь мир за 7 дней. Ты спасёшь бизнес за 3 месяца — если будешь двигаться осторожно.

Частые ошибки — и как их избежать

Я видел, как падали продукты. Вот что ломает multi-tenant SaaS чаще всего:

  • Забыли фильтр в одном запросе. Случайно написали SELECT * FROM invoices — и один клиент увидел все счета. Это случается, когда кто-то пишет «быстрый фикс» и не проверяет контекст.
  • Кеширование без учёта тенанта. Ты кешируешь результат запроса по ключу user_id, но не по tenant_id. Один пользователь видит чужие данные из кеша. Решение: всегда включай tenant_id в ключ кеша.
  • Используют общие последовательности ID. Например, все клиенты используют одну последовательность ID для заказов. Один клиент видит, что у другого заказ №12345 — и понимает, что у него 12 тысяч заказов. Это раскрывает масштаб бизнеса. Решение: используй UUID или генерируй ID в пределах тенанта.
  • Не проверяют права доступа на уровне API. Допустим, API принимает /api/invoices/123. Если ты не проверяешь, что этот invoice принадлежит текущему тенанту — злоумышленник может перебирать ID. Решение: всегда проверяй принадлежность в контроллере, а не только в UI.
  • Слишком рано перешли на отдельные базы. Начали с 10 клиентов и сделали по базе на каждого. В итоге — 100 БД, 30 серверов, и ты тратишь 80% времени на поддержку инфраструктуры, а не на продукт.

Проверяй всё. Даже если кажется, что «это же очевидно». Всё, что работает с данными — должно быть протестировано на изоляцию. Добавь в CI/CD тест, который имитирует запрос от одного клиента к данным другого. Если он проходит — ты в опасности.

Что выбрать в зависимости от ситуации

Вот как принимать решение, если ты ещё не начал:

  • Если у тебя 1–20 клиентов, нет требований к безопасности, и ты хочешь запустить быстро — используй общую базу с tenant_id. Это нормально. Не стыдись. Многие успешные SaaS-продукты начинали так. Но добавь фильтрацию везде. И запланируй миграцию на схемы через 6 месяцев.
  • Если клиентов 20–300, и ты хочешь дать им возможность кастомизировать поля, отчёты, интеграции — переходи на отдельные схемы. Это даёт гибкость. Ты можешь добавить поле custom_field_1 для одного клиента, не трогая других. Это стоит больше, чем общий подход, но меньше, чем отдельные базы.
  • Если клиенты — банки, госорганы, медицинские учреждения, или ты планируешь продавать за $1000+/месяц — сразу иди на отдельные базы. Это не «перестраховка». Это требование. Большинство корпоративных клиентов потребуют аудит безопасности. И если ты не сможешь показать, что данные физически разделены — они не купят.

Если ты не уверен — начни с общего подхода. Но сделай так, чтобы миграция на схемы была возможна через 3–6 месяцев. Заложи это в архитектуру с самого начала.

Как сделать это правильно — практические рекомендации

Вот что реально работает, если ты не хочешь спать ночами, боясь утечки данных:

  • Создай «тенант-контекст». В каждом HTTP-запросе определяй, кто клиент (через JWT, хедер, домен). Сохраняй его в контексте запроса. Используй этот контекст везде — в ORM, в логах, в бэкапах.
  • Не пиши SQL-запросы вручную без фильтрации. Используй ORM с поддержкой tenant. Например, в Django — django-tenants, в Ruby on Rails — acts_as_tenant. Если пишешь на чистом SQL — напиши обёртку, которая автоматически добавляет WHERE tenant_id = ?.
  • Сделай мониторинг на утечки. Запиши в логи все запросы, где tenant_id отсутствует. Настрой алерт, если такой запрос появляется. Это твой «детектор утечек».
  • Проводи тесты на изоляцию еженедельно. Создай тест, который делает запрос от клиента A к данным клиента B. Если он проходит — ты в опасности. Это должен быть автоматический тест в CI/CD.
  • Используй отдельные базы только тогда, когда действительно нужно. Не потому что «все так делают». Если твой клиент — маленький магазин, и ты не хранишь персональные данные — тебе не нужны отдельные базы. Это перерасход.

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

Итог: что делать прямо сейчас

Если ты ещё не запустил продукт — сделай так:

  1. Добавь tenant_id в каждую таблицу, где есть данные клиента.
  2. Настрой ORM или слой доступа к БД, чтобы он автоматически фильтровал по tenant_id.
  3. Напиши тест, который проверяет, что один клиент не может получить данные другого.
  4. Запусти продукт. Собирай обратную связь.

Если ты уже запустил — сделай так:

  1. Найди все места, где нет tenant_id в запросах. Исправь их.
  2. Добавь мониторинг на отсутствующие фильтры.
  3. Сделай план миграции на отдельные схемы — на 6–12 месяцев.
  4. Начни с одного клиента. Перенеси его данные. Проверь. Потом — ещё одного.

Ты не должен делать всё идеально с первого раза. Но ты должен делать так, чтобы в любой момент можно было исправить ошибку — без потери клиентов, без штрафов, без паники.

Multi-tenant — это не про архитектурный шик. Это про то, чтобы твой продукт не стал причиной катастрофы для твоих клиентов. И чтобы ты не потерял всё из-за одной ошибки в SQL-запросе.

Начни с малого. Проверь фильтр. Добавь тест. Запусти. И не жди, пока кто-то украдёт данные. Сделай это до того, как это сделает кто-то другой.

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

Dfncfg.ru