Схема арендатора
Назначение
Каждый арендатор получает собственную схему с одинаковым набором таблиц внутри одной базы. При отдельных правах изоляция больше не зависит от фильтров строк; самодостаточную схему можно отдельно экспортировать, восстановить или перенести, появляется возможность индивидуальных изменений. Глава показывает маршрутизацию соединений к нужной схеме, причины выполнения миграций для каждого арендатора и влияние числа схем на системный каталог, пулы соединений и инструменты. PostgreSQL различает права доступа к схеме и выбор пространства имён и описывает зависимости экспорта схемы.
Также известен как
Separate schemas; namespace per tenant. Citus называет распределённую реализацию шардированием по схемам. Исторический документ AWS SaaS Storage Strategies относит отдельные представления внутри общего экземпляра к семейству bridge; оно шире схем PostgreSQL.
Проблема
Вы адаптируете приложение, написанное для одной компании. Все запросы обращаются к customer и customer_order, а связи используют обычный локальный ID клиента. Добавить повсюду предикаты арендатора и составные ключи возможно, но вам также нужно развернуть новый столбец у пилотного арендатора раньше остальных.
Дублирование таблиц как customer_acme и customer_birch встраивает идентичность арендатора в каждое имя таблицы. Вам нужны полное пространство имён для каждого арендатора, воспроизводимое определение схемы и авторизация доступа к этому пространству. Без прав пространство имён меняет лишь способ поиска данных, а не определяет, кому разрешено их читать.
Решение
Создайте схему для каждого арендатора и разместите в ней полный набор принадлежащих ему таблиц. Внешние ключи оставьте внутри схемы. Доверенный каталог должен сопоставлять аутентифицированного арендатора с именем схемы и рабочими учётными данными. Владение схемой и миграции закрепите за отдельной ролью; рабочие роли получают доступ к объектам без возможности создавать и изменять их структуру.
Явно выберите один из двух контрактов доступа. Общие учётные данные приложения с доступом ко всем схемам делают маршрутизатор приложения границей арендатора. Отдельные учётные данные с доступом к одной схеме отклоняют ошибочно направленный запрос с полным именем ещё и на границе базы. Глава реализует второй контракт. Роль, способная переключаться на любую роль арендатора, остаётся доверенным маршрутизатором, а не недоверенной учётной записью арендатора.
Используйте полные имена либо устанавливайте контролируемый search_path на время транзакции. PostgreSQL разрешает неполное имя в первый подходящий объект этого пути. Исключите недоверенные пространства имён, доступные для записи. Путь выбирает объекты, а права на схемы и таблицы разрешают их использование.
Альтернативы и выбор
| Соседний паттерн или механизм | Связь | Определяющее требование |
|---|---|---|
| Общая схема | Замена | Число арендаторов и единые выпуски склоняют к одному набору таблиц с ключами в области арендатора |
| База арендатора | Замена | Граница жизненного цикла должна включать базу либо нужно отдельное размещение |
| Полные имена таблиц | Вариант реализации | Каждый запрос должен явно показывать назначение |
Локальный search_path транзакции |
Вариант реализации | Существующий SQL должен сохранить неполные имена таблиц |
| Расширение и свёртывание | Дополнение | Разные схемы арендаторов временно работают на разных версиях миграций |
Полная таблица — в Атласе решений. Не выбирайте паттерн только ради небольших таблиц: он даёт границу пространства имён и прав ценой повторения объектов и миграций.
Структура
a_customer, a_order и a_migration обозначают tenant_a.customer, tenant_a.customer_order и tenant_a.schema_migration; сущности b_ — их аналоги в tenant_b. Одинаковые ID и номера клиентов могут существовать в обоих пространствах. Внешние ключи не пересекают их границы, а каждая схема хранит собственные применённые версии миграций.
Таблицы и инварианты
tenant_a.customerиtenant_b.customerнезависимо обеспечивают локальную идентичность строк и уникальность номера клиента. Глобальная уникальность клиента10не заявляется.- Каждая
customer_orderссылается на клиентов своей схемы через внешний ключ с полным именем. Клиент другой схемы не может удовлетворить отсутствующую локальную ссылку. - Каждая
schema_migrationхранит положительные уникальные номера версий. Первичный ключ запрещает повтор версии; применение по порядку и проверка содержимого остаются обязанностью исполнителя миграций. - Рабочие права дают
st_app_aдоступ только к арендатору A, аst_app_b— к B. PostgreSQL различаетUSAGEсхемы и права таблиц; одно не даёт другого. - Полнота создания — инвариант процесса: не публикуйте маршрут арендатора до установки всех таблиц, ограничений, прав и отметок версий.
Когда применять
- Приложение уже содержит значительный объём SQL, рассчитанного на одного арендатора за раз.
- Нужны отдельные права на объекты при общей базе.
- Пилотные миграции или небольшой набор управляемых вариантов схем достаточно полезны, чтобы оправдать множество независимо обновляемых схем.
- Вы можете измерять и обслуживать фактическое число создаваемых объектов схем.
Последствия и компромиссы
- ➕ Запросы в правильно выбранной схеме используют обычные локальные ID и внешние ключи.
- ➕ Совместимую миграцию можно сначала применить к одному арендатору и проверить результат.
- ➖ Каждый арендатор добавляет таблицы, индексы и ограничения. Каталог
pg_classPostgreSQL хранит, среди прочих отношений, таблицы и индексы; работа каталога и инструментов растёт с копируемой структурой. Универсального безопасного порога числа арендаторов нет. - ➖ Пулы отдельных рабочих учётных записей требуют бюджета соединений. Общая учётная запись с широким доступом позволяет уменьшить число пулов, но повышает ответственность маршрутизатора за изоляцию.
- ➖ Схема не выделяет отдельные CPU, память или ресурсы восстановления. Настройки ресурсов сервера PostgreSQL включают общие буферы; пространства имён не резервируют их арендаторам.
- ➖ Межарендаторские отчёты должны явно обходить разрешённые схемы или читать отчётную копию. Таблица одного арендатора не представляет всё приложение.
Реализация
Полный скрипт PostgreSQL 18.6 создаёт две схемы, проверяет ограниченные роли и применяет миграцию к каждой схеме. У ролей входа нет паролей, поскольку одноразовый практикум использует локальную доверенную аутентификацию; производственную аутентификацию настройте отдельно. Начните с владельца без входа и рабочих ролей арендаторов:
CREATE ROLE st_owner NOLOGIN;
CREATE ROLE st_app_a LOGIN NOSUPERUSER NOBYPASSRLS;
CREATE ROLE st_app_b LOGIN NOSUPERUSER NOBYPASSRLS;
REVOKE CREATE ON SCHEMA public FROM PUBLIC;
CREATE SCHEMA tenant_a AUTHORIZATION st_owner;
CREATE SCHEMA tenant_b AUTHORIZATION st_owner;
SET ROLE st_owner;
CREATE TABLE tenant_a.customer (
customer_id bigint PRIMARY KEY,
customer_number text NOT NULL UNIQUE,
display_name text NOT NULL
);
CREATE TABLE tenant_a.customer_order (
order_id bigint PRIMARY KEY,
customer_id bigint NOT NULL,
description text NOT NULL,
CONSTRAINT order_customer_fk FOREIGN KEY (customer_id)
REFERENCES tenant_a.customer (customer_id)
);
CREATE INDEX order_customer_idx ON tenant_a.customer_order (customer_id);
CREATE TABLE tenant_a.schema_migration (
version integer PRIMARY KEY CHECK (version > 0)
);
INSERT INTO tenant_a.schema_migration VALUES (1);
Скрипт повторяет определения в tenant_b, с внешним ключом на tenant_b.customer. CREATE SCHEMA ... AUTHORIZATION PostgreSQL назначает владельца схемы; выполнение DDL таблиц от его имени также оставляет таблицы у роли миграций. Не включайте рабочие роли в st_owner.
Выдайте ровно операции приложения над таблицами и чтение отметки миграции:
GRANT USAGE ON SCHEMA tenant_a TO st_app_a;
GRANT SELECT, INSERT, UPDATE, DELETE
ON tenant_a.customer, tenant_a.customer_order TO st_app_a;
GRANT SELECT ON tenant_a.schema_migration TO st_app_a;
RESET ROLE;
SET ROLE st_app_a;
BEGIN;
SET LOCAL search_path = pg_catalog, tenant_a;
INSERT INTO customer VALUES (10, 'C-10', 'Acme customer');
INSERT INTO customer_order VALUES (100, 10, 'First order');
UPDATE customer SET display_name = 'Acme renamed' WHERE customer_id = 10;
SELECT o.order_id, c.display_name
FROM customer_order AS o JOIN customer AS c USING (customer_id);
COMMIT;
Соединение возвращает (100, Acme renamed). Аналогичная транзакция другой роли вставляет (10, C-10, Birch customer) и собственный заказ 100 без конфликта. Администраторский SET ROLE проверяет права; настоящие соединения арендаторов входят непосредственно под рабочей ролью.
Перед неполным SQL устанавливайте путь на том же соединении и в той же транзакции. SET LOCAL заканчивается при фиксации или откате. Явно ставьте pg_catalog первым, исключайте public и запрещайте рабочей роли создание объектов, включая временные, если права базы иначе это разрешают. Скрипт отзывает общую привилегию базы TEMPORARY; PostgreSQL по умолчанию выдаёт её PUBLIC. DDL создания объектов записывайте с полными именами: путь предназначен для рабочего поиска, а не создания по неполному имени.
Имена схем — доверенные идентификаторы из каталога, а не сырые имена хостов или отображаемые имена арендаторов, склеенные с SQL. Если исполнитель миграций генерирует DDL, используйте экранирование идентификаторов драйвера. Новым объектам тоже нужны права: ALTER DEFAULT PRIVILEGES PostgreSQL влияет на будущие объекты указанной роли, а не на существующие объекты или объекты другой роли.
Под st_app_a попытка обойти маршрут полным именем завершается ошибкой:
SELECT count(*) FROM tenant_b.customer;
PostgreSQL 18.6 возвращает SQLSTATE 42501: ERROR: permission denied for schema tenant_b. Причина отказа — отсутствие права на схему; защищает не search_path. При пути арендатора A ограничение связи также отклоняет:
INSERT INTO customer_order VALUES (101, 20, 'Missing customer');
Ошибка — SQLSTATE 23503: ERROR: insert or update on table "customer_order" violates foreign key constraint "order_customer_fk". В полном скрипте каждый недопустимый оператор выполняется в своём блоке исключения, чтобы практикум подтвердил оба отказа и продолжился.
Поддержка в движках
- PostgreSQL 18: схемы сосуществуют в одной базе, а ссылки с полными именами могут пересекать их при наличии прав. Рекомендации по безопасности схем предупреждают: доверие к доступным для записи схемам в пути поиска позволяет подменять объекты. Отделяйте рабочие роли от владельцев.
- MySQL 8.4:
CREATE SCHEMA— синонимCREATE DATABASE. Дополнительного слоя схем, как в PostgreSQL, внутри базы MySQL нет; переносите границу изоляции, а не название. - SQL Server 2025 (17.x):
CREATE SCHEMA ... AUTHORIZATIONсоздаёт принадлежащее владельцу пространство имён внутри базы. Используйте полные имена и права SQL Server;search_pathPostgreSQL непереносим. - Oracle AI Database 26ai:
ALTER SESSION SET CURRENT_SCHEMAменяет разрешение неполных имён, не меняя пользователя и не выдавая прав. Сохраняйте это различие между маршрутизацией и авторизацией. - SQLite 3:
ATTACH DATABASEименует другой файл базы в соединении. Это файловая организация, а не пространство имён с независимыми ролями; права доступа SQLite — права файловой системы.
Сбои и конкурентность
Локальные ID заказов не конфликтуют, поскольку принадлежат разным таблицам. Конкурентным записям одного арендатора по-прежнему нужны обычные локальные ограничения; изоляция пространств имён не разрешает конфликт двух правок строки. При общей учётной записи с доступом ко всем схемам ошибочная маршрутизация была бы успешной — поэтому контракт учётных данных важен.
Сериализуйте исполнителей миграций для каждого арендатора и фиксируйте транзакционный DDL вместе с отметкой версии. Скрипт использует pg_advisory_xact_lock с назначенным ключом каждого арендатора. Все исполнители должны соблюдать одинаковое сопоставление и повторно проверять версию после блокировки. Рекомендательная блокировка координирует согласованные процессы, но не блокирует произвольный рабочий SQL. DDL берёт собственные блокировки: используйте короткий lock_timeout в эксплуатации и повторяйте отдельного арендатора позже вместо остановки всего набора схем.
Сбой исполнителя между миграциями арендаторов оставляет набор схем частично обновлённым. Записывайте завершение для каждого арендатора и возобновляйте лишь недостающие версии. Приложение должно поддерживать смешанное состояние. Перед повтором выясняйте, зафиксировалась ли предыдущая транзакция арендатора; потеря соединения не равна провалу миграции.
Миграция
В существующей базе одного арендатора сначала отделите его объекты от общих управляющих данных и справочников. При исходной общей схеме копируйте строки клиентов и заказов каждого арендатора в полное целевое пространство имён, сохраняя локальные связи. Не создавайте межсхемные внешние ключи лишь ради отказа от копирования дочерних данных арендатора: тогда дальнейший перенос будет зависеть от чужого пространства имён.
Переключению нужен запрет записи, а не только новое имя схемы в кеше. Дождитесь завершения транзакций источника, примените остаток изменений, проверьте количества и ссылки, затем сбросьте старые маршруты и состояние соединений. До начала целевой записи можно вновь открыть источник; после неё откат требует обратного копирования или захвата изменений. Храните источник в течение окна восстановления, не позволяя обеим копиям принимать записи.
Для обычных обновлений применяйте Расширение и свёртывание по арендаторам. Сначала добавляйте совместимые столбцы, переводите все версии приложения, затем удаляйте устаревшие объекты. Пример обновления в практикуме выполняет ADD COLUMN billing_note text и записывает версию 2 одной транзакцией на арендатора; он демонстрирует границы возобновления, а не полноценный сервис развёртывания.
Экспорт схемы упрощает перенос, но не гарантирует самодостаточности. pg_dump --schema PostgreSQL не включает автоматически объекты вне выбранной схемы, от которых она зависит. Учтите общие типы, функции, расширения, роли и справочники и отрепетируйте восстановление. Физическое восстановление на момент времени восстанавливает весь кластер, поэтому для прежней версии одной схемы восстановите временный кластер и извлеките арендатора.
Антипаттерны и частые ошибки
- Считать
SET search_pathавторизацией, когда все запросы используют учётные данные владельца всех схем. - Оставлять доступные арендатору для записи схемы в пути поиска другого арендатора.
- Применять миграцию один раз и считать изменёнными все схемы либо хранить одну глобальную версию для частично обновлённого набора.
- Разрешать произвольные изменения схем арендаторами без учёта поддерживаемых вариантов и путей обновления.
- Называть набор таблиц с суффиксами стратегией схем; см. Клонированные таблицы. Сохраняйте полное пространство имён, контракт создания и историю миграций.
- Предполагать, что экспорт схемы включает внешние зависимости или даёт физическое восстановление отдельного арендатора.
Известные применения
- Шардирование по схемам Citus, доступное с Citus 12.0, использует схему как логический шард и разрешает неодинаковые схемы арендаторов. Ограничения и плотность арендаторов относятся к расширению, а не обещаны обычными схемами PostgreSQL.
- Историческая модель RDS bridge AWS описывает отдельные представления арендаторов внутри экземпляра и постепенный переход между версиями схем. Её терминология охватывает несколько контейнерных механизмов разных движков.
Связанные паттерны
- Общая схема меняет отдельные наборы объектов на строки с указанием арендатора.
- База арендатора переносит границу маршрутизации на установление соединения.
- Расширение и свёртывание сохраняет работоспособность смешанных версий схем при поэтапном обновлении.
- Клонированные таблицы объясняют, почему повторяющиеся имена без контракта жизненного цикла усложняют сопровождение.