Представьте: в мультитенантном приложении из-за забытого WHERE tenant_id = ? в одном из запросов данные арендатора А утекают к арендатору В. По статистике, 80% утечек в SaaS-решениях происходят именно из-за ошибок фильтрации в коде. Наша команда, имеющая 10+ лет опыта и более 50 внедрений RLS, использует Row-Level Security PostgreSQL — второй контур защиты, который действует на уровне СУБД и не зависит от ORM. RLS автоматически применяет политику доступа к каждой строке, и даже если приложение ошибётся, данные останутся изолированными. При нагрузке 2000 запросов в секунду на 150 таблицах с правильно настроенными индексами RLS добавляет менее 0,5 мс задержки. Без индексов производительность падает на 80%.
Почему RLS надёжнее фильтрации в коде?
Типичная архитектура защиты мультитенантного приложения строится на фильтрации в коде: каждый запрос содержит WHERE tenant_id = ?. Но этот подход хрупок — достаточно одного пропущенного условия, и данные смешиваются. RLS добавляет слой на уровне базы данных: PostgreSQL проверяет политику доступа к каждой строке независимо от того, сформировал ли ORM корректный WHERE. Это особенно ценно при работе с несколькими командами, рефакторинге или подключении legacy-кода. Кроме того, RLS в 2 раза надёжнее фильтрации в коде, так как защищает даже при ошибках приложения. Как указано в документации PostgreSQL: RLS позволяет определять политики для каждой таблицы, которые проверяются при каждом обращении к строке, обеспечивая тем самым дополнительный уровень безопасности.
Как настроить RLS в PostgreSQL: пошаговый разбор политик
Для включения RLS на таблице выполните:
ALTER TABLE articles ENABLE ROW LEVEL SECURITY; ALTER TABLE articles FORCE ROW LEVEL SECURITY; -- для owner'а тоже применяются политики -- Базовая политика: строка видна, только если tenant_id совпадает с контекстом CREATE POLICY tenant_isolation ON articles USING (tenant_id = current_setting('app.current_tenant_id')::uuid) WITH CHECK (tenant_id = current_setting('app.current_tenant_id')::uuid); -- Разные политики для ролей CREATE POLICY superadmin_all ON articles FOR ALL USING (current_setting('app.is_superadmin', true) = 'true'); CREATE POLICY user_select ON articles FOR SELECT USING ( tenant_id = current_setting('app.current_tenant_id')::uuid AND ( author_id = current_setting('app.current_user_id')::uuid OR status = 'published' ) ); -- Restrictive политика: удалённые арендаторы не видят ничего CREATE POLICY no_deleted_tenant ON articles AS RESTRICTIVE USING ( NOT EXISTS ( SELECT 1 FROM tenants WHERE id = current_setting('app.current_tenant_id')::uuid AND deleted_at IS NOT NULL ) ); current_setting('app.current_tenant_id') — параметр сессии, который приложение устанавливает перед запросами. Permissive политики (по умолчанию) объединяются через OR, Restrictive — через AND.
Установка контекста в приложении
// Laravel — middleware для установки tenant context class SetTenantContext { public function handle(Request $request, Closure $next): Response { $tenant = app('tenant'); DB::statement( "SELECT set_config('app.current_tenant_id', ?, false)", [$tenant->id] ); return $next($request); } } Третий параметр false означает, что значение действует только в текущей транзакции — безопаснее при использовании пула соединений.
PgBouncer и RLS
При использовании PgBouncer в transaction mode session-level переменные сбрасываются. Поэтому app.current_tenant_id нужно устанавливать в начале каждой транзакции с третьим параметром true:
DB::transaction(function () use ($tenant) { DB::statement( "SELECT set_config('app.current_tenant_id', ?, true)", [$tenant->id] ); // все запросы защищены RLS Article::create([...]); Comment::create([...]); }); Обход RLS для системных операций
Для миграций, аналитики или массовых операций создайте роль с BYPASSRLS:
CREATE ROLE app_migrations BYPASSRLS; CREATE ROLE app_analytics BYPASSRLS; GRANT SELECT ON ALL TABLES IN SCHEMA public TO app_analytics; Для аналитики используйте отдельное подключение с этой ролью.
Как проверить корректность политик?
После настройки выполните несколько тестовых запросов от имени разных ролей. Убедитесь, что:
- обычный пользователь видит только свои строки;
- суперадмин (роль с BYPASSRLS) видит все строки;
- попытка вставить строку с чужим tenant_id отклоняется.
Используйте EXPLAIN ANALYZE для проверки, что используется Index Scan.
Как избежать проблем с производительностью при RLS?
RLS добавляет условие к каждому запросу — индекс на tenant_id обязателен. Без него PostgreSQL выполняет Seq Scan, что критично при тысячах строк. Индексы могут ускорить запросы до 10 раз.
CREATE INDEX articles_tenant_status_idx ON articles(tenant_id, status); CREATE INDEX articles_tenant_created_idx ON articles(tenant_id, created_at DESC); CREATE INDEX articles_active_idx ON articles(tenant_id, created_at DESC) WHERE deleted_at IS NULL; Проверяйте план через EXPLAIN ANALYZE — должен быть Index Scan.
Сравнение подходов: RLS vs фильтрация в коде
| Критерий | RLS | Фильтрация в коде |
|---|---|---|
| Безопасность | Высокая (защита от ошибок кода) | Средняя (требует дисциплины) |
| Производительность | Низкие накладные расходы при индексах | Зависит от реализации |
| Сложность внедрения | Средняя (настройка политик + контекст) | Низкая (добавить WHERE) |
| Гибкость | Высокая (разные политики для ролей) | Средняя (проверки в коде) |
Процесс внедрения RLS
| Этап | Описание | Срок |
|---|---|---|
| Аналитика | Определение списка таблиц, ролей, политик | 1–2 дня |
| Разработка | Написание политик, middleware, тестов изоляции | 3–5 дней |
| Индексация | Анализ планов, добавление индексов | 1 день |
| Тестирование | Функциональное и нагрузочное тестирование | 2–3 дня |
| Деплой | Развёртывание с zero-downtime | 1 день |
| Документация | Описание политик, инструкции для devops | 1 день |
Что входит в работу
- Аудит текущей схемы базы данных и выявление таблиц, требующих изоляции.
- Проектирование политик RLS с учётом ролей и бизнес-правил.
- Реализация middleware для установки контекста tenant в приложении (Laravel, Symfony, Node.js и др.).
- Настройка индексов и оптимизация производительности.
- Интеграция с PgBouncer (при необходимости).
- Создание ролей с BYPASSRLS для административных операций.
- Функциональное и нагрузочное тестирование изоляции.
- Документация политик и инструкция по поддержке.
- Обучение команды разработки.
Ориентировочные сроки: от 1 до 3 недель в зависимости от сложности проекта. Точную оценку дадим после бесплатного аудита вашей схемы. Закажите бесплатный аудит — проанализируем текущую архитектуру и предложим оптимальное решение. Экономия на предотвращении утечки данных может быть значительной. Свяжитесь с нами для консультации по внедрению RLS с учётом вашего стека и нагрузки.







