До 15-й версии логическая репликация в PostgreSQL работала по принципу «всё или ничего»: если таблица попадала в публикацию, подписчик получал все её строки и все столбцы. Для многих сценариев это было избыточно: плодились лишние данные, росла нагрузка, страдала безопасность. С выходом PostgreSQL 15 появились два механизма, которые меняют правила игры, — row filters (фильтры строк) и column lists (списки столбцов). Теперь можно публиковать ровно тот объём данных, который нужен конкретному подписчику, не расплачиваясь за универсальность костылями на уровне приложения. Разберём, как это устроено и на что обратить внимание, чтобы не потерять данные.
Row filters: фильтруем строки
Фильтр строк — это условие WHERE, которое привязывается к таблице внутри публикации. Строка попадёт к подписчику только тогда, когда условие истинно. Механизм действует как при первичном копировании данных, так и во время репликации изменений.
Синтаксис и первые шаги
Фильтр задаётся при создании или изменении публикации. Для каждой таблицы можно определить собственное выражение, заключённое в круглые скобки:
CREATE PUBLICATION us_orders_pub
FOR TABLE orders WHERE (region = 'US');
Теперь подписчик, подключённый к us_orders_pub, увидит исключительно заказы из региона 'US'. Все остальные строки таблицы orders останутся на мастере и не будут переданы даже при полной начальной синхронизации.
Если таблица уже включена в публикацию, фильтр добавляется через ALTER PUBLICATION ... SET:
ALTER PUBLICATION us_orders_pub
SET TABLE orders WHERE (status = 'active');
Обратите внимание: без указания WHERE фильтр сбрасывается, и таблица снова публикуется целиком.
Поведение при разных операциях
Row filter применяется к каждой изменяемой строке, но логика зависит от типа операции:
- INSERT. Если новая строка удовлетворяет условию — подписчик получает
INSERT. Не удовлетворяет — операция игнорируется. - DELETE. Если удаляемая строка подходила под фильтр, реплицируется
DELETE. Иначе удаление не передаётся. - UPDATE. Проверяется как старое, так и новое состояние строки.
- Обе версии проходят фильтр → отправляется обычный
UPDATE. - Ни одна не проходит → операция пропускается.
- Новое состояние проходит, а старое нет →
UPDATEпревращается в логическийINSERTна стороне подписчика. - Старое состояние проходит, новое нет → подписчик получает
DELETE.
- Обе версии проходят фильтр → отправляется обычный
Такая трансформация гарантирует, что данные на подписчике всегда соответствуют row-фильтру, заданному на публикующем сервере.
- TRUNCATE. Фильтр строк игнорируется — любая операция
TRUNCATEна таблице всегда публикуется целиком, если в публикации разрешена репликация усечений.
Инициальная синхронизация (когда copy_data = true) также подчиняется фильтру: в снимок попадают только те строки, которые удовлетворяют условию на момент старта копирования. Это помогает избежать передачи огромных объёмов исторических данных, если подписчику нужен лишь актуальный срез.
Ограничения, которые нельзя игнорировать
Свобода фильтрации ограничена несколькими строгими правилами, основанными на официальной документации:
- Выражение может ссылаться только на столбцы самой таблицы. Никаких подзапросов, JOIN или ссылок на другие таблицы.
- Для публикации операций
UPDATEиDELETEвыражение должно использовать только столбцы, входящие вREPLICA IDENTITY. Обычно это первичный ключ, реже — уникальный индекс. Если попытаться в фильтре задействовать другие колонки, PostgreSQL выдаст ошибку или будет вынужден читать всю старую версию строки из кучи, что ударит по производительности. - Для публикации, рассчитанной только на
INSERT, можно использовать любые столбцы без оглядки наREPLICA IDENTITY. - Допускаются лишь иммутабельные операторы и встроенные функции. Пользовательские функции, не детерминированные конструкции (например,
random()) и ссылки на системные столбцы запрещены.
Главный камень преткновения — REPLICA IDENTITY. Если на таблице нет первичного ключа или подходящего уникального индекса, а публикация включает UPDATE/DELETE, полноценная row-фильтрация окажется невозможной. Выход — создать индекс или перевести таблицу в режим REPLICA IDENTITY FULL, что увеличит объём WAL.
Column lists: реплицируем только нужные столбцы
Если row filter отсекает ненужные строки, то column list убирает из потока репликации лишние столбцы. Это удобно, когда подписчику не нужны чувствительные или редко используемые колонки — например, хеши паролей, токены или большие бинарные объекты.
Синтаксис элементарный: столбцы перечисляются в круглых скобках сразу после имени таблицы, до или вместе с WHERE:
CREATE PUBLICATION limited_pub
FOR TABLE users (id, name, email)
WHERE (active = true);
Теперь подписчик получит только три столбца и только активные записи. Остальные колонки таблицы users не будут фигурировать в сообщениях репликации.
Важный нюанс: если вы используете row filter, то столбцы, на которые он ссылается, обязаны присутствовать в column list. Иначе система не сможет вычислить условие на стороне подписчика (или во время начального копирования), и CREATE PUBLICATION завершится ошибкой. В примере выше условие WHERE (active = true) ссылается на столбец active, который не указан в списке. Такую публикацию PostgreSQL не создаст — придётся либо добавить active в column list, либо убрать фильтр.
Практические сценарии и предостережения
Кейс: раздача данных по подписчикам. У вас есть общая база заказов, а региональные биллинги должны видеть только свои записи. Создаёте публикацию с фильтром WHERE (region = 'EMEA'), и подписчик в Европе получает европейские заказы, а азиатский подписчик — азиатские. Это более элегантный подход, чем ручной партишнинг и танцы с триггерами.
Что будет, если забыть про REPLICA IDENTITY? Попытка создать фильтр, использующий столбец не из REPLICA IDENTITY, при включённых UPDATE/DELETE приведёт к ошибке на этапе создания публикации. В худшем случае, если REPLICA IDENTITY не задан вовсе, PostgreSQL не сможет эффективно идентифицировать старую версию строки: каждое изменение будет генерировать полный образ строки в WAL, что заметно увеличит нагрузку на диск и сеть. Сложные фильтры без правильно настроенного REPLICA IDENTITY — прямой путь к нестабильной репликации.
TRUNCATE не дружит с фильтрами. Как уже упоминалось, усечение таблицы проходит без оглядки на WHERE. Если на мастере очистили всю таблицу, подписчик тоже получит TRUNCATE, даже если ему по фильтру не доставалось ни одной строки. Учитывайте это при проектировании логики очистки данных.
Обновление фильтров и повторная синхронизация. Изменение row-фильтра через ALTER PUBLICATION ... SET TABLE ... не приводит к автоматической ресинхронизации уже скопированных данных. Подписчик продолжит хранить строки, которые теперь не проходят фильтр, если только не будет выполнена ручная пересинхронизация таблицы командой ALTER SUBSCRIPTION ... REFRESH PUBLICATION. И наоборот, новые строки, начавшие соответствовать обновлённому условию, появятся на подписчике только после того, как сработает первичное копирование.
FAQ: частые вопросы и короткие ответы
Можно ли использовать столбец, не указанный в column list, внутри row-фильтра?
Нет. Такая публикация просто не создастся — PostgreSQL потребует добавить недостающие колонки в column list, иначе он не сможет вычислить фильтр.
Как добавить фильтр, если таблица уже реплицируется?
Используйте ALTER PUBLICATION ... SET TABLE table WHERE (условие). Учтите, что уже существующие строки на подписчике не исчезнут сами по себе. Потребуется вручную обновить подписку с опцией перекопирования данных (REFRESH PUBLICATION WITH (copy_data = true) для конкретной таблицы), чтобы привести набор данных в соответствие фильтру.
Влияет ли row-фильтр на размер WAL?
Непосредственно на генерацию WAL на мастере фильтр не влияет — логика записи изменений остаётся прежней. Однако сокращается объём данных, передаваемых по сети и записываемых на подписчике. Косвенно это может разгрузить канал репликации и дисковую подсистему подписчика. Точных цифр производительности для типовых сценариев в открытых источниках найти не удалось.
Поддерживаются ли подзапросы в row-фильтре?
Нет. Разрешены только ссылки на столбцы самой таблицы. Любая попытка вставить подзапрос или ссылку на другую таблицу будет отклонена.
Что делать, если нужно перестать реплицировать часть колонок?
Измените публикацию, установив новый column list: ALTER PUBLICATION pub_name SET TABLE table_name (col1, col2). Уже существующие записи на подписчике не потеряют данные — просто новые изменения перестанут затрагивать исключённые столбцы.
Можно ли отфильтровать строки по данным из связанной таблицы?
Штатными средствами без джойнов — нельзя. Но вы можете добавить в основную таблицу денормализованное поле и использовать его в фильтре, либо построить более сложную архитектуру с несколькими публикациями на одной таблице, управляемыми через триггерные обновления такого поля.
Заключение
Row filters и column lists делают логическую репликацию PostgreSQL по-настоящему гибкой. Вот минимальный чек-лист, который стоит держать перед глазами, прежде чем внедрять выборочную репликацию:
- Для таблиц с
UPDATE/DELETEубедитесь, что все столбцы, участвующие в row-фильтре, входят вREPLICA IDENTITY(обычно это первичный ключ). - Если используете column list, явно включите в него все колонки, на которые ссылается фильтр.
- Помните, что
TRUNCATEигнорирует любые фильтры, а изменение фильтра не вызывает автоматической пересинхронизации — её придётся запустить вручную. - Выражения должны быть простыми и детерминированными: никаких функций, подзапросов и обращений к другим таблицам.
Когда эти правила соблюдены, row filters и column lists становятся мощным и безопасным инструментом для построения многоподписочных систем, выборочной репликации защищённых данных и оптимизации сетевого трафика.




.svg.webp)

