Multi-tenant SaaS на VDS кажется простой идеей: поднимаем один мощный сервер, ставим приложение, добавляем базу — и вперёд. На практике всё сложнее: один «шумный» клиент выедает CPU, другому не хватает памяти, у третьего отчёты падают по таймауту. А в итоге страдает вся платформа, метрики скачут, пользователи недовольны, а DevOps внезапно превращается в SRE.
В этом тексте я соберу практические подходы к multi-tenant SaaS на VDS: архитектурные варианты, уровни изоляции, использование cgroup и systemd для установки лимитов ресурсов, сетевые ограничения и подходы к мониторингу. Цель — чтобы вы могли спроектировать платформу, которая не развалится от одного неаккуратного клиента.
Модели multi-tenant: с чего вообще выбирать
Multi-tenant по факту — это ответ на два вопроса:
- Как вы изолируете данные клиентов?
- Как вы изолируете ресурсы (CPU, RAM, диски, сеть) клиентов друг от друга?
На уровне данных выбор обычно понятен: отдельные базы, отдельные схемы, разделение по колонке tenant_id. На уровне ресурсов всё гораздо интереснее, особенно на VDS с ограниченными CPU и RAM.
Основные модели изоляции для SaaS на VDS
Для одного или нескольких VDS чаще всего встречаются такие варианты:
- Общее приложение + общий runtime (PHP-FPM пул, один Node.js процесс, один Python WSGI-пул). Клиенты делят всё.
- Общее приложение, но разные пулы/процессы (отдельные PHP-FPM пулы на клиента или группу клиентов, отдельные worker-процессы). Можно навешивать лимиты cgroup.
- Контейнеры на одном VDS (Docker/Containerd/k3s и аналоги). Каждый клиент — контейнер/namespace с лимитами.
- Отдельный VDS на клиента (single-tenant на уровне сервера). Самая консервативная модель, чаще — для крупных чеков.
Мы сосредоточимся на втором и третьем варианте, потому что они позволяют реализовать настоящий multi-tenant на одном или нескольких VDS, не переходя к полному single-tenant.
Чем плох «наивный» multi-tenant без изоляции
Стартовый сценарий обычно выглядит так: на VDS крутится Nginx/Apache, один PHP-FPM/Node.js пул, одна база, несколько десятков или сотен клиентов. Пока нагрузки мало — всё норм.
Проблемы начинаются после появления «тяжёлых» клиентов:
- один клиент запускает тяжёлые отчёты, которые держат CPU на 100% по 5–10 минут. Остальные клиенты получают высокую латентность;
- кому-то завезли интеграцию с внешней системой, которая гонит много данных, и процесс стал активно жрать RAM, провоцируя OOM-killer или swap-шторм;
- клиент массово загружает или выгружает файлы, колеблется IO, всем остальным становится больно.
Ключевая идея: в multi-tenant SaaS без изоляции вы продаёте не «выделенные ресурсы», а «право пострадать вместе с соседями».
На VDS эта проблема обостряется: вы не контролируете слой ниже (hypervisor), но отвечаете за всё, что сверху. Поэтому граница изоляции между клиентами должна быть внутри вашего VDS — на уровне процессов, cgroup и сервисов.
Если вам нужен быстрый старт без администрирования железа и гипервизора, проще сразу строить SaaS на управляемом облачном VDS и сосредоточиться на логике изоляции внутри системы: cgroup, systemd и грамотное разбиение на сервисы.
Уровни изоляции на VDS: от грубого к тонкому
В Linux сегодня изоляцию для multi-tenant SaaS в основном строят вокруг двух механизмов:
- cgroup (control groups) — лимиты и учёт ресурсов: CPU, память, IO, сеть (через дополнительные подсистемы и класс трафика);
- namespaces — имитация отдельных окружений (process, mount, network и т.д.), чаще через контейнеры.
Если контейнеризация у вас ещё не заехала или по бизнес-причинам не подходит, вы всё равно можете получить хороший multi-tenant за счёт systemd и cgroup v2.
cgroup v1 vs cgroup v2 для SaaS
Большинство современных дистрибутивов (Debian 12, Ubuntu 22.04+, RHEL 9, Alma/Rocky 9) по умолчанию работают с cgroup v2 или в гибридном режиме. Для новых проектов имеет смысл сразу ориентироваться на v2:
- единая иерархия;
- более предсказуемое деление CPU;
- нормальная работа с распределением ресурсов между несколькими группами;
- хорошая интеграция с systemd (slice, service, scope).
С точки зрения SaaS это значит: вы можете выразить SLA в терминах «клиент не может съесть больше X CPU и Y RAM» и жёстко enforce-ить это на уровне ядра.
Подробно про связку systemd и cgroup применительно к PHP-FPM я разбирал в статье про разделение PHP-FPM по slice и cgroup; многие идеи оттуда напрямую переносятся и на multi-tenant SaaS.

Архитектура: как маппить клиентов на процессы и cgroup
Перед тем как крутить ручки cgroup, важно определиться, что именно вы будете ограничивать. На практике встречаются три подхода:
- Per-tenant процессы: у каждого клиента свои worker-процессы, которые легко положить в отдельную cgroup.
- Per-plan группы: клиенты пакуются в «тарифные классы», и cgroup создаются под тариф (например, Basic/Pro/Enterprise).
- Per-feature пулы: отдельные пулы под ресурсоёмкие подсистемы (отчёты, импорт/экспорт, интеграции), а внутри уже multi-tenant по данным.
В реальных SaaS часто комбинируют второй и третий варианты: тарифы определяют грубые лимиты, отдельные worker-пулы служат для самых тяжёлых задач.
Пример: PHP-FPM multi-tenant с отдельными пулами
Классическая модель для PHP SaaS:
- на уровне Nginx запросы клиентов маршрутизируются по домену или поддомену на разные PHP-FPM пулы;
- каждый пул крутится под своим Unix-пользователем;
- systemd кладёт каждый пул в свой slice или service с лимитами.
Так вы получаете изоляцию не только по ресурсам, но и по правам на файловой системе.
Пример: Node.js / Python multi-tenant с worker-процессами
Если SaaS написан на Node.js или Python, вы можете:
- поднимать отдельный процесс приложения на каждого крупного клиента или группу клиентов;
- запускать тяжёлые задачи в отдельных воркерах (Celery, RQ, custom workers) с отдельным systemd unit;
- класть эти unit в slice, который имеет свои лимиты cgroup.
Логика маршрутизации (к какому процессу шлём запрос) реализуется либо в HTTP-роутере (Nginx/Haproxy), либо на уровне внутреннего RPC.
Практичный путь для старта — развернуть SaaS на надёжном VDS, чтобы гибко масштабировать ресурсы под рост количества tenant-ов.
systemd + cgroup: как это реально выглядит
Практический минимум для multi-tenant SaaS на VDS — научиться использовать systemd как фронтенд к cgroup. Это избавляет от необходимости руками писать в /sys/fs/cgroup и дружит конфигурации с перезапусками.
Слои systemd: slice, service, scope
Для SaaS нас интересуют два основных объекта:
- slice — логическая группа для сервисов, с которой удобно работать как с контуром (например, все процессы тарифа Pro);
- service — конкретная служба: worker-пул, task runner, отдельный инстанс приложения.
Типичный паттерн: вы создаёте один slice на тариф или группу клиентов, а затем запускаете сервисы внутри этого slice.
Базовая конфигурация slice с лимитами
Например, для тарифа Pro вы хотите дать до 2 vCPU и 4 ГБ RAM, но не дать клиентам уронить весь VDS при всплеске.
[Slice]
CPUQuota=200%
MemoryMax=4G
IOReadBandwidthMax=/dev/vda 50M
IOWriteBandwidthMax=/dev/vda 20M
Важные моменты:
CPUQuota=200%на двух vCPU означает, что slice может занять оба виртуальных ядра на 100%, но не больше.MemoryMax=4G— жёсткий потолок. При его превышении процессы в этом slice будут убиваться OOM-killer внутри cgroup, а не хаотично по всей системе.IOReadBandwidthMaxиIOWriteBandwidthMaxработают только с cgroup v2 и поддерживаемыми драйверами; стоит проверить в документации вашего дистрибутива и ядра.
Привязка сервисов клиента к slice
Дальше вы описываете unit сервиса приложения или worker-а, указывая для него нужный slice.
[Unit]
Description=SaaS Pro tenant workers
[Service]
Slice=saas-pro.slice
User=tenant_pro
Group=tenant_pro
ExecStart=/usr/bin/php-fpm -y /etc/php/saas_pro_fpm.conf
Restart=always
[Install]
WantedBy=multi-user.target
Все процессы из этого сервиса автоматически попадают в cgroup saas-pro.slice и начинают жить по его лимитам.
Лимиты CPU: честный multi-tenant без «шумных соседей»
Лимитирование CPU в multi-tenant сценариях — одна из самых недооценённых тем. Часто вспоминают только про память, но именно CPU-спайки чаще всего убивают SLA.
Как распределять CPU между клиентами
Есть два основных механизма:
- CPUQuota — верхний потолок («не больше X% CPU») для slice или service;
- CPUWeight — относительный приоритет между группами («если батл за CPU — у кого выше вес, тот и молодец»).
В multi-tenant SaaS практично сочетать оба:
- через
CPUQuotaвы декларируете жёсткий максимум тарифа; - через
CPUWeight— приоритеты между тарифами (например, Pro > Basic).
Пример для трёх тарифов:
# Basic: до 50% одного vCPU, низкий приоритет
[Slice]
CPUQuota=50%
CPUWeight=50
# Pro: до 200%, средний приоритет
[Slice]
CPUQuota=200%
CPUWeight=100
# Enterprise: до 400%, высокий приоритет
[Slice]
CPUQuota=400%
CPUWeight=200
Так вы можете визуально привязать лимиты к тарифам и честно описать их в коммерческом предложении.
Лимиты памяти: баланс между OOM и UX
С памятью всё сложнее: жёсткий лимит MemoryMax спасает от падения всего VDS, но убьёт длинный отчёт клиента, если тот выберет слишком широкие параметры. Поэтому важно грамотно подобрать пороги и иметь graceful degradation.
Базовые лимиты памяти для multi-tenant
Для SaaS на одном VDS типичный подход:
- определить «технический минимум» памяти для своего стека (Nginx, база, кеш, фоновый воркер, мониторинг);
- вычесть это из общего объёма RAM VDS;
- остаток разделить между тарифами, оставив 10–20% на буферы и форс-мажоры.
Пример для VDS с 16 ГБ RAM:
- системные сервисы, база, кеши, мониторинг — 6 ГБ;
- остаток под SaaS-воркеров — 10 ГБ;
- из них 2 ГБ — буфер (никакому тенанту не отдаём как жёсткий лимит);
- 8 ГБ делим между Basic/Pro/Enterprise по выбранной пропорции.
Потом эти числа превращаются в MemoryMax для slice или отдельных service.
Что делать, когда tenant упирается в MemoryMax
Важно не только выставить лимит, но и правильно отреагировать на его достижение:
- логируйте падения воркеров с причиной OOM;
- повесьте алерты по количеству рестартов и ошибок за период;
- на уровне приложения сообщайте пользователю, что отчёт слишком тяжёлый для его тарифа или текущих лимитов;
- предлагайте решение: сузить выборку, запустить задачу ночью, перейти на более высокий тариф.
Таким образом OOM внутри slice превращается не в катастрофу, а в контролируемое ограничение, интегрированное в ваш продукт.
IO и сеть: когда диски и трафик тоже multi-tenant
CPU и RAM — не единственные ресурсы, о которых стоит думать. В multi-tenant SaaS на VDS часто возникает проблема: один клиент начал массовый импорт или экспорт и положил диск.
Ограничение IO через cgroup
В cgroup v2 IO-лимиты задаются через контроллер io. Через systemd это выражается параметрами:
IOReadBandwidthMax/IOWriteBandwidthMax— ограничение пропускной способности по устройству;IOReadIOPSMax/IOWriteIOPSMax— ограничение числа операций в секунду.
Использовать их нужно аккуратно, иначе можно навредить и самому приложению:
- ставьте лимиты только на тяжёлые batch-процессы (импорт, экспорт, генерацию отчётов), а не на весь веб-слой;
- начинайте с мягких ограничений и повышайте их по мере измерений;
- следите за метриками latency и backlog на уровне базы и приложения.
Сеть: user space лимиты и QoS
На большинстве VDS вы планируете сеть не столь агрессивно, как CPU/RAM, но в multi-tenant всё равно встречается:
- клиент, который массово скачивает или заливает файлы и забивает канал;
- внешние интеграции, создающие много исходящих соединений.
В боевых системах чаще используют:
- лимиты скорости на уровне Nginx/Haproxy (download/upload per connection или per tenant);
- троттлинг API на уровне приложения (rate limit per tenant или per token);
- prefetch и буферизацию, чтобы тяжёлые клиенты не забирали CPU во время ожидания сети.
Теоретически можно использовать eBPF/TC для сетевого QoS, но для большинства SaaS на одном-двух VDS это избыточно и сложнее поддерживать, чем лимиты на уровне приложения.

Учет ресурсов по tenant-ам: метрики и биллинг
Multi-tenant архитектура не заканчивается на лимитах: вы захотите считать, кто сколько потребляет. Это важно и для внутренней оптимизации, и для тарификации.
Что имеет смысл измерять
Минимальный набор метрик на tenant или тариф:
- CPU time (user+system) за период;
- использование памяти (среднее и пики);
- количество запросов и средняя/95p/99p латентность;
- объём операций чтения и записи (по возможности — хотя бы на уровне подсистемы, обслуживающей конкретного тенанта);
- объём переданных данных (если у вас тарификация по трафику).
Часть этих метрик можно извлечь напрямую из cgroup (через системные файлы или экспортеры), часть — на уровне приложения (например, прометки в Prometheus с label tenant_id).
Связка cgroup и метрик
Практический вариант:
- каждый tenant или тариф — это slice/service с понятным именем (например, saas-tenant-123.service);
- метрики по cgroup вытягиваются Prometheus-экспортером, который маппит имя cgroup на tenant_id или тариф;
- дальше вы строите дашборды: кто забрал сколько CPU, памяти, IO и т.д.
Это даёт прозрачность и вовремя подсказывает, когда клиенту или тарифу пора переехать на отдельный VDS или получить другие лимиты.
Когда multi-tenant на одном VDS уже не тянет
Рано или поздно один физический VDS становится узким местом. Признаки типичные:
- вы постоянно упираетесь в CPU или RAM даже при честно выставленных лимитах;
- любое серьёзное обновление требует сложной акробатики с окнами обслуживания;
- RPO и RTO резервного копирования и восстановления перестают вас устраивать;
- отдельные клиенты настолько крупные, что их лимиты конфликтуют с остальными.
На этом этапе вы переходите от «один большой VDS с multi-tenant внутри» к «несколько VDS или кластеров, каждый из которых внутри тоже multi-tenant». Архитектурно это выглядит как:
- разделение на шардированные кластеры по регионам, типам клиентов или ID-шардированию;
- выделение инфраструктуры под крупных клиентов (почти single-tenant), при этом мелкие продолжают жить на общих кластерах;
- унификация деплоймента, чтобы новые кластеры поднимались и конфигурировались автоматически.
Важно: даже при переходе на несколько VDS всё, что вы сделали с cgroup и systemd, остаётся актуальным — просто теперь это применяется на каждом сервере по отдельности.
Типичные грабли и как их обойти
И напоследок — несколько проблем, с которыми чаще всего сталкиваются при построении multi-tenant SaaS на VDS.
Грабля 1: слишком жёсткие лимиты с самого начала
Часто хочется «перестраховаться» и сразу выставить строгие CPUQuota и MemoryMax. В итоге приложение задыхается, фоновые задачи не успевают, а вы вынуждены экстренно поднимать лимиты.
Лучше подход:
- сначала померить нормальную рабочую нагрузку под синтетическим и реальным трафиком;
- добавить 30–50% запаса;
- выставить эти значения как исходные лимиты;
- дальше плавно их донастраивать по метрикам.
Грабля 2: отсутствие границы между «вебом» и «батчем»
Если веб-запросы и тяжёлые batch-задачи живут в одном пуле или сервисе, любой импорт, экспорт или отчёт легко убьёт UX всех клиентов.
Решение: разделить критичные по латентности веб-запросы и тяжёлые фоновые работы:
- отдельный пул или сервис для web (жёсткий SLA по latency, приоритет по CPU и памяти);
- отдельный пул или сервис для batch (ниже приоритет, можно сильнее ограничить CPU и IO);
- очереди задач (RabbitMQ, Redis и т.п.), чтобы гибко регулировать конкуренцию worker-ов.
По работе воркеров и очередей через systemd у меня есть отдельный разбор — как запускать очереди и worker-ы под systemd и supervisorом; он хорошо дополняет эту статью.
Грабля 3: игнорирование IO и swap
Часто думают: «главное — CPU и память, диск сам разберётся». На деле:
- одно неудачное решение по кешированию или логированию может начать активно долбить диск;
- активный swap на VDS превращает всё приложение в «слайд-шоу».
Рекомендации:
- следите за метриками IO и swap отдельно для каждого VDS и, по возможности, для тяжёлых сервисов;
- не злоупотребляйте swap; лучше добавить RAM или вынести часть воркеров на отдельный VDS;
- по возможности используйте быстрые диски (SSD или NVMe) для критичных компонентов.
Выводы
Multi-tenant SaaS на VDS — это не «поставить одно приложение и завести много клиентов». Чтобы платформа жила стабильно, нужно:
- выбрать модель изоляции: per-tenant, per-plan или hybrid;
- научиться управлять ресурсами через cgroup v2 и systemd (slice + service);
- чётко задать и документировать лимиты CPU, памяти и IO;
- развести веб-трафик и тяжёлые batch-задачи;
- собирать метрики по tenant-ам и тарифам, чтобы видеть, кто как нагружает систему;
- уметь вовремя масштабироваться на несколько VDS или выделенные инсталляции для крупных клиентов.
Тогда «multi-tenant» перестаёт быть эвфемизмом для «шумных соседей» и превращается в управляемую, прогнозируемую платформу, на которой можно уверенно расти.


