TL;DR
  • Подходит, когда база большая, простой недопустим, а к WAL нет доступа.

  • Схему переносим заранее: pg_dump -s | pg_restore, индексы/триггеры/последовательности создаём на приёмнике.

  • На источнике: wal_level=logical, проверить лимиты слотов/отправителей/рабочих процессов; открыть доступ по сети; завести роль для репликации с правами SELECT.

  • Для всех таблиц обеспечить уникальную идентификацию строк: первичный ключ, уникальный индекс (REPLICA IDENTITY USING INDEX) или REPLICA IDENTITY FULL.

  • Создать публикацию (CREATE PUBLICATION FOR ALL TABLES), на новом хосте — подписку (CREATE SUBSCRIPTION).

  • Следить за первичной загрузкой: pg_stat_subscription, состояния таблиц — в pg_subscription_rel (i/d/f/s/r). Для сверки использовать COUNT(*), а не размеры.

  • Переключение: остановить запись на старой базе, переключить приложение, синхронизировать последовательности командами setval.

  • Важно: репликация однонаправленная, схемы должны совпадать; логическая репликация переносит данные, но не переносит саму схему и значения последовательностей.

Перенос базы данных Postgres — задача не из простых. Обычно для пользователей Postgres это один из самых крупных проектов. Если вы переходите на новую мажорную версию Postgres или переносите базу на другую платформу/хост, у вас есть несколько вариантов.

  • Использование pg_dump и pg_restore: pg_dump — надёжный способ выгрузить всю базу данных и восстановить её в новом месте. В выгрузку попадут вся схема, все таблицы и специальные объекты базы. Если вы переносите небольшую базу — порядка 50, 100, 150 ГБ — это, вероятно, самый простой путь. На современном «железе» дамп и восстановление этим способом можно выполнить меньше чем за час.

  • Использование WAL: если у вас настроено резервное копирование на основе журнала предзаписи (WAL), например через pgBackRest, WAL-G или WAL-E, вы можете выполнить крупную миграцию Postgres, сделав полную базовую резервную копию и организовав потоковую передачу WAL на новый хост. Когда вы будете готовы к финальному переключению на новую базу, у вас уже будет точная копия, готовая к работе. Этот способ отлично подходит для больших баз данных, вплоть до терабайтного масштаба, — позволяет провести основную часть миграции с минимальным простоем.

Но что, если ваша база слишком велика для дампа/восстановления и вы не можете позволить себе простой? И при этом у вас нет доступа к WAL (например, вы используете сервис вроде Amazon RDS, который не предоставляет доступ к WAL). Есть третий вариант:

  • Логическая миграция: с помощью логической миграции Postgres вы можете развернуть копию базы данных в новом месте. Тогда как WAL содержит «всё», логическая репликация переносит только данные и не переносит схему, индексы, последовательности и ещё пару тонкостей. Однако с несколькими приёмами из этого материала вы сможете охватить всё необходимое и выполнить полноценную миграцию, используя логическую репликацию.

Архитектура логической репликации проста; если вы совсем новичок в теме, посмотрите наш вводный пост «Data To Go: Postgres Logical Replication». Ваша текущая база будет издателем (publisher), а принимающая база — подписчиком (subscriber). Во время первичной загрузки все данные копируются с издателя на подписчика. После завершения первичной загрузки любые транзакции, выполняемые на стороне издателя, передаются подписчику.

Шаг 1: Перенос схемы

Логическая репликация переносит только изменения данных (INSERT, UPDATE, DELETE), поэтому заранее убедитесь, что в целевой базе данных корректно создана схема. Чтобы получить дамп только схемы из источника и применить его к целевой базе, выполните, например:

pg_dump -Fc -s $SOURCE_DB_URI | pg_restore --no-acl --no-owner -d $TARGET_DB_URI

Если миграция идёт параллельно с продолжающейся разработкой приложения, необходимо синхронно вносить изменения схемы и на стороне подписчика (в целевой базе): всякий раз, когда вы меняете схему в исходной базе, обновляйте её и на целевой стороне.

Шаг 2: Настройка издателя (текущий хост)

Логическая репликация включается параметром wal_level. У некоторых управляемых сервисов Postgres может быть иной способ включения этого режима. Установите wal_level = logical.

Конфигурация слотов

Проверьте настройки слотов репликации, чтобы ресурсов было достаточно. Для очень крупных проектов репликации значений по умолчанию может оказаться мало.

  • max_replication_slots

  • max_wal_senders

  • max_logical_replication_workers

  • max_worker_processes

  • max_sync_workers_per_subscription

Подробные рекомендации по настройке этих параметров смотрите в главе документации PostgreSQL о конфигурации логической репликации.

Сетевое взаимодействие

Убедитесь, что правила сети/межсетевой экран разрешают входящие подключения к исходной базе с адресов нового хоста.

Пользователь репликации для нового подписчика

Можно создать отдельного пользователя с атрибутом роли REPLICATION. Также убедитесь, что у этой роли есть права на чтение таблиц, которые вы реплицируете.

CREATE ROLE elizabeth WITH REPLICATION LOGIN PASSWORD 'my_password';
GRANT SELECT ON ALL TABLES IN SCHEMA public TO elizabeth;

Найдите таблицы без первичных ключей или уникальных индексов

Для логической репликации Postgres требуется способ уникально идентифицировать строки для операций обновления/удаления. В таблицах с первичным ключом используется именно он, поэтому сначала определите таблицы, в которых первичный ключ отсутствует:

select tab.table_schema,
       tab.table_name
from information_schema.tables tab
left join information_schema.table_constraints tco
          on tab.table_schema = tco.table_schema
          and tab.table_name = tco.table_name
          and tco.constraint_type = 'PRIMARY KEY'
where tab.table_type = 'BASE TABLE'
      and tab.table_schema not in ('pg_catalog', 'information_schema')
      and tco.constraint_name is null
order by table_schema,
         table_name;

Для таблиц без первичных ключей можно использовать уникальный индекс, который подходит под REPLICA IDENTITY:

ALTER TABLE tablename REPLICA IDENTITY USING INDEX idx_some_unique_index;

Если уникальных индексов нет, их можно создать, либо установить для таблицы режим REPLICA IDENTITY FULL — в этом случае каждая строка рассматривается как её собственный «ключ»:

ALTER TABLE tablename REPLICA IDENTITY FULL;

Далее создайте публикацию — группу таблиц, которые вы планируете реплицировать. В большинстве случаев публикацию делают ДЛЯ ВСЕХ ТАБЛИЦ:

CREATE PUBLICATION bridge_migration FOR ALL TABLES;

Проверьте, что таблицы готовы к публикации: здесь должны отображаться все таблицы, включённые в публикацию (для FOR ALL TABLES — все подходящие таблицы в БД).

SELECT * FROM pg_publication_tables;

Шаг 3: Настройки подписчика (новый хост)

На новом хосте создайте подписку на каждую публикацию, чтобы начать получать публикуемые данные. Используя параметры подключения к старому хосту и учётные данные, созданные на шаге 2, настройте подписку на эту репликацию данных.

CREATE SUBSCRIPTION bridge_migration CONNECTION 'host={host} port=5432 dbname={datatbase} user={login} password={password}' PUBLICATION bridge_migration;

Создание подписки таким образом создаст слот репликации на издателе и запустит копирование данных из таблиц, указанных в публикации. Для каждой таблицы на время её первичной синхронизации данных будет создан отдельный временный слот.

Число таблиц, синхронизируемых одновременно, можно ограничить параметром max_sync_workers_per_subscription.

Шаг 4: Мониторинг первичной загрузки

Скорее всего, вы захотите наблюдать за этим первичным копированием. Системное представление pg_stat_subscription на стороне подписчика покажет данные о ходе процесса.

select * from pg_stat_subscription;

-[ RECORD 1 ]---------+------------------------------
subid                 | 27183
subname               | bridge_migration
worker_type           | table synchronization
pid                   | 1197139
leader_pid            |
relid                 | 26721
received_lsn          |
last_msg_send_time    | 2025-09-26 15:54:45.095215+00
last_msg_receipt_time | 2025-09-26 15:54:45.095215+00
latest_end_lsn        |
latest_end_time       | 2025-09-26 15:54:45.095215+00
-[ RECORD 2 ]---------+------------------------------
subid                 | 27183
subname               | bridge_migration
worker_type           | apply
pid                   | 47075
leader_pid            |
relid                 |
received_lsn          | 4E32/7092F6F8
last_msg_send_time    | 2025-09-26 15:55:11.020012+00
last_msg_receipt_time | 2025-09-26 15:55:11.021989+00
latest_end_lsn        | 4E32/7092F3E0
latest_end_time       | 2025-09-26 15:55:10.843251+00

Также можно посмотреть системный каталог pg_subscription_rel, чтобы увидеть состояние синхронизации каждой таблицы (через запрос select * from pg_subscription_rel;).

Здесь код состояния (state_code) подсказывает, что происходит с объектом:

  • i — инициализация

  • d — копирование данных

  • f — копирование завершено

  • s — синхронизация выполнена

  • r — режим обычной репликации

Из-за разбухания таблиц и других факторов, связанных с внутренней статистикой, сравнивать размеры таблиц напрямую не получится. Однако можно сравнить количество строк через select count(*).

Шаг 5: Тестирование и переключение

Теперь можно начать тестировать приложение на новой базе. После того как вы убедитесь, что все данные на месте, можно выполнить финальное переключение. Для этого потребуется остановить запись на исходном хосте (заблокировать DML), поправить последовательности и переключить приложение на новую базу данных.

Шаг 6: Правим последовательности

Хотя логическая репликация перенесёт все данные из источника, сами последовательности она не обновляет. Поэтому мы рекомендуем синхронизировать последовательности после переключения, до начала боевой эксплуатации. Наилучший способ — сгенерировать команды setval для всех последовательностей в исходных базах; это можно сделать таким запросом:

SELECT
    'SELECT setval(' || quote_literal(quote_ident(n.nspname) || '.' || quote_ident(c.relname)) || ', ' || s.last_value || ');'
FROM
    pg_class c
    JOIN pg_namespace n ON n.oid = c.relnamespace
    JOIN pg_sequences s ON s.schemaname = n.nspname
        AND s.sequencename = c.relname
WHERE
    c.relkind = 'S';

Результирующий файл с командами можно выполнить на новом хосте, чтобы синхронизировать все последовательности.

Заключение

Логическая репликация — безопасная и эффективная стратегия миграции. Согласованность данных для реплицируемых таблиц гарантируется, если схема подписчика идентична схеме издателя и репликация однонаправленная, без конфликтующих записей на стороне подписчика.


Научиться миграциям без простоя, проектированию и эффективному управлению PostgreSQL можно на курсе «PostgreSQL для администраторов и разработчиков». На курсе достаточно практики: настройка кластера под нагрузку, резервное копирование, индексы/джойны/статистика для оптимизации, диагностика блокировок и deadlock, работа с большими объёмами. Пройдите тест, чтобы узнать, подойдет ли вам программа.

А ещё — приходите на открытые уроки, которые преподаватели курса проведут бесплатно в рамках набора:

  • 27 октября: «Ликбез по типам данных в PostgreSQL». Записаться

  • 5 ноября: «Работа с json в PostgreSQL: давно не просто текст». Записаться

  • 19 ноября: «PostgreSQL как векторная БД: строим AI-приложения с pgvector». Записаться

Комментарии (1)


  1. anyafit
    22.10.2025 19:19

    Тогда как WAL содержит «всё», логическая репликация переносит только данные и не переносит схему, индексы, последовательности и ещё пару тонкостей.

    Странно, логическая репликация не через WAL работает?