Перейти к основному содержимому

Перенести данные из управляемого PostgreSQL Yandex Cloud в облачную базу данных PostgreSQL

Как устроена миграция

Данные переносятся между двумя управляемыми кластерами PostgreSQL:

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

В облачных базах данных Selectel не предоставляется прямой доступ к серверу баз данных и права суперпользователя, поэтому все действия выполняются на уровне SQL-запросов и встроенных механизмов СУБД.

Что нужно для миграции

Инфраструктура

  • административный доступ к облачной инфраструктуре в Yandex Cloud и Selectel;
  • пользователь в СУБД исходного кластера с ролями mdb_superuser, mdb_replication, mdb_monitor, mdb_admin и доступом к переносимым базам;
  • стабильный сетевой канал между площадками и настроенная маршрутизация — особенно важно для крупных баз, подробнее в подразделе Сетевая связность;
  • настроенные сетевые политики (группы безопасности, файрвол), чтобы кластеры могли обмениваться трафиком друг с другом;
  • при связи площадок по внутреннему каналу (VPN или Direct Connect) — DNS-резолвер для внутренней зоны yandexcloud.net, чтобы обращаться к мастеру Yandex по имени хоста, подробнее в подразделе DNS-резолвер для зоны yandexcloud.net;
  • cвободное место на диске мастера исходного кластера под растущий объем WAL — не менее трети от размера базы;
  • версия PostgreSQL в Selectel не ниже версии в исходном кластере.

Рабочее окружение

  • сервер с сетевым доступом к обоим кластерам для выполнения SQL-запросов и мониторинга. Можно использовать рабочую станцию с VPN, откуда доступны оба кластера;
  • консольные клиенты PostgreSQL нужной версии: psql, pg_dump, pg_restore;
  • опционально: утилиты для проверки сети: ping, telnet, traceroute, mtr, iftop, а также терминальный мультиплексор (tmux), чтобы сессии не обрывались при потере связи с рабочим местом.

Риски

Нехватка места на источнике

После создания публикации и подписки сервер резервирует WAL под слот репликации. Чем больше записей, изменений и удалений, тем быстрее растет объем WAL. Для базы от 1 ТБ при канале около 1 Гбит/с за время первичного копирования может накопиться около 300 ГБ WAL-файлов. При заполнении диска кластер будет работать только на чтение, операции записи для приложения прервутся

Нестабильный канал

Обрыв связи во время первичного копирования прерывает репликацию, ее придется запускать заново. Короткий обрыв на этапе дозагрузки изменений обычно восстанавливается автоматически

Запись в целевой кластер

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

Смена мастера в источнике

Если мастер в Yandex Cloud переедет на другой хост, репликация прервется. Особые FQDN Yandex, которые указывают на текущий мастер, использовать для репликации не рекомендуется: после смены ролей они могут указывать на старый хост до 10 минут

Пересечение подсетей

Подсети в Yandex Cloud и Selectel не должны пересекаться, иначе маршрутизация не заработает

Сетевая связность

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

Варианты соединения между Yandex Cloud и Selectel

  • вариант 1 — Direct Connect. Рекомендуется для крупных баз данных. Выделенное подключение между Selectel и Yandex Cloud через точку присутствия операторов связи: создается L2/L3-соединение, дальше взаимодействие выполняется через глобальный роутер Selectel. Услугу заказывают на обеих сторонах, каждый провайдер коммутирует свою часть. Маршрутизация настраивается на облачном роутере в Selectel и в таблице маршрутизации в Yandex Cloud. Условия тарификации трафика на время миграции уточняйте у вашего менеджера в Selectel;

  • вариант 2 — VPN-туннель. На каждой площадке создается виртуальная машина (облачный сервер — в Selectel), между ними строится туннель через интернет (WireGuard, OpenVPN, IPsec). Виртуальные машины должны находиться в облачных сетях, доступных для управляемых баз данных. Маршрутизация — через облачный роутер в Selectel и таблицу маршрутизации в Yandex Cloud. Мы рекомендуем настраивать статические маршруты на облачном роутере, а не в настройках подсети: тогда они применятся ко всем продуктам, для которых роутер является шлюзом по умолчанию, включая управляемые базы данных. Сделать это можно через OpenStack CLI или API, в том числе Terraform.

DNS-резолвер для зоны yandexcloud.net

К хосту PostgreSQL в Yandex Cloud обращаются только по FQDN: внутренний IP-адрес хоста может измениться в любой момент. При этом FQDN относится к внутренней зоне Yandex и не резолвится снаружи, поэтому кластер в Selectel не увидит его через свой DNS.

Рекомендуем создать временный DNS-резолвер на базе unbound на обеих площадках (Например, можно использовать виртуальные машины с VPN):

  • Unbound-01 в Yandex Cloud пересылает запросы к зоне yandexcloud.net во внутренний DNS Yandex и принимает запросы на порту 53 на своем локальном интерфейсе;
  • Unbound-02 в Selectel пересылает запросы к зоне yandexcloud.net на Unbound-01 и принимает запросы на порту 53 на своем локальном интерфейсе.

Кластер в Selectel использует Unbound-02 как DNS-сервер и получает актуальный внутренний IP-адрес мастера Yandex.

Если не использовать Unbound-02 и указать в Selectel адрес Unbound-01 напрямую, то при потере связи между площадками хосты останутся без единственного DNS-сервера.

В Yandex Cloud DNS-сервер — первый адрес подсети. Например, для подсети 10.2.0.0/24 это 10.2.0.1. Можно посмотреть в параметрах облачной сети в консоли.

Пример конфигурации Unbound-01 (Yandex Cloud):

server:
port: 53
interface: 127.0.0.1
interface: 10.1.0.10 # IP локального интерфейса
access-control: 127.0.0.0/8 allow
access-control: 10.0.0.0/8 allow
do-ip4: yes
do-ip6: no
do-udp: yes
do-tcp: yes
num-threads: 2
num-queries-per-thread: 1024
hide-identity: yes
hide-version: yes
prefetch: yes
verbosity: 1
forward-zone:
name: "yandexcloud.net"
forward-addr: 10.1.0.1 # внутренний DNS Yandex
Пример конфигурации Unbound-02 (Selectel):
server:
port: 53
interface: 127.0.0.1
interface: 10.200.0.10 # IP локального интерфейса
access-control: 127.0.0.0/8 allow
access-control: 10.0.0.0/8 allow
do-ip4: yes
do-ip6: no
do-udp: yes
do-tcp: yes
num-threads: 2
num-queries-per-thread: 1024
hide-identity: yes
hide-version: yes
prefetch: yes
verbosity: 1
forward-zone:
name: "yandexcloud.net"
forward-addr: 10.1.0.10 # адрес Unbound-01

Здесь 10.200.0.10 — пример IP-адреса DNS-сервера для кластера в Selectel.

Шаги миграции

В инструкции используются названия параметров из примера — замените их на свои.

ПараметрИмя в примере
DNS-адрес мастера в кластере Selectelmaster.destination-srv.c.dbaas.selcloud.ru
FQDN мастера в кластере Yandex Cloudrc1a-source-srv.mdb.yandexcloud.net
Порт в кластере Selectel5432 — прямое подключение к PostgreSQL, не через пулер соединений
Порт в кластере Yandex Cloud6432 — прямое подключение к PostgreSQL, не через пулер соединений
my_databaseИмя базы данных в Selectel
my_databaseИмя базы данных в Yandex Cloud
my_userПользователь — владелец базы данных в Selectel
selectelПользователь для переноса баз данных в Yandex Cloud
  1. Организуйте связность между Yandex Cloud и Selectel.

  2. Проведите анализ базы данных перед миграцией.

  3. Подготовьте исходный кластер в Yandex Cloud.

  4. Подготовьте целевой кластер в Selectel.

  5. Перенесите схему базы данных из исходного кластера в целевой кластер.

  6. Запустите репликацию.

  7. Проверьте состояние репликации.

  8. Переключите приложение.

1. Организовать связность между Yandex Cloud и Selectel

  1. Убедитесь, что вы организовали соединение между Yandex Cloud и Selectel и настроили маршрутизацию, подробнее в подразделе Сетевая связность.
  2. Убедитесь, что исходный и целевой кластеры находятся в разных подсетях, иначе маршрутизация не сработает.
  3. Убедитесь, что настроенные сетевые политики (группы безопасности, файрвол) позволяют кластерам обмениваться трафиком друг с другом.

2. Провести анализ базы данных перед миграцией

Примеры запросов для проведения анализа смотрите в подразделе Полезные запросы.

  1. Определите, какие базы нужно перенести.
  2. Проверьте локали LC_COLLATE, LC_CTYPE. В целевом кластере они задаются при создании базы данных. После создания базы данных изменить локали нельзя.
  3. Соберите список пользователей и ролей.
  4. Выпишите параметры кластера, измененные относительно значений по умолчанию.
  5. Определите схемы, из которых реплицируются таблицы (public и другие при наличии).
  6. Убедитесь, что все таблицы поддерживаются логической репликацией. Foreign-таблицы, временные таблицы и материализованные представления исключаются из публикации.
  7. Найдите внешние источники (foreign servers) и обеспечьте их доступность из Selectel.
  8. Остановите изменение данных на время миграции. Таблицы, созданные после запуска репликации, в том числе новые партиции и таблицы из миграций приложения, не попадут в нее.
  9. Проверьте таблицы без первичного ключа: для UPDATE и DELETE должен быть REPLICA IDENTITY, иначе изменения не реплицируются.
  10. Составьте список расширений — их нужно установить в целевом кластере до запуска репликации.

Полезные запросы

  • таблицы схемы public:

    SELECT table_name FROM information_schema.tables WHERE table_schema = 'public';
  • таблицы, поддерживаемые логической репликацией:

    SELECT n.nspname, c.relname, c.relkind
    FROM pg_class c
    JOIN pg_namespace n ON n.oid = c.relnamespace
    WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
    AND c.relkind IN ('r', 'p')
    AND c.relpersistence = 'p';
  • таблицы без первичного ключа:

    SELECT n.nspname, c.relname
    FROM pg_class c
    JOIN pg_namespace n ON n.oid = c.relnamespace
    WHERE c.relkind = 'r'
    AND n.nspname NOT IN ('pg_catalog', 'information_schema')
    AND NOT EXISTS (SELECT 1 FROM pg_constraint
    WHERE conrelid = c.oid AND contype = 'p');
  • расширения:

    \dx
  • foreign-серверы, таблицы, обертки:

    \des+
    \det+
    \dew+
  • базы и пользователи:

    \l+
    \du+

3. Подготовить исходный кластер в Yandex Cloud

  1. Сделайте бэкап баз данных.
  2. Создайте отдельного пользователя для миграции, например selectel, с ролями mdb_superuser, mdb_replication, mdb_monitor, mdb_admin и доступом к переносимым базам.
  3. В группах безопасности разрешите трафик из подсетей Selectel. Если группа в значении default, ограничений нет.
  4. Проверьте запас свободного места на мастере, подробнее в подразделе Риски.

4. Подготовить целевой кластер в Selectel

  1. Создайте кластер PostgreSQL. При создании кластера:

    • выберите версию PostgreSQL не ниже версии исходной базы данных;

    • выберите конфигурацию нод с размером диска, достаточным для объема переносимых данных.

  2. Создайте пользователя - будущего владельца базы.

  3. Создайте базу данных. Укажите локали такие же, как в источнике. После создания базы данных изменить локали нельзя.

  4. Опционально: создайте остальных пользователей, как в источнике.

  5. Добавьте расширения, которые вы определили при проведении анализ базы данных перед миграцией на шаге 10.

  6. Для кастомного DNS (адрес Unbound-02) создайте тикет в техподдержку Selectel — в панели управления для уже созданного кластера этот параметр не меняется.

5. Перенести схему базы данных из исходного кластера в целевой кластер

  1. Выгрузите схему базы данных из исходного кластера:

    pg_dump -h rc1a-source-srv.mdb.yandexcloud.net -p 6432 -U selectel \
    -d my_database --schema-only -f /data/my_database.sql
  2. Загрузите схему в целевой кластер:

    psql -h master.destination-srv.c.dbaas.selcloud.ru -p 5432 -U my_user \
    -d my_database -f /data/my_database.sql
  3. Проверьте вывод на ошибки. Ошибки GRANT допустимы, если дополнительные пользователи еще не созданы. Сравните объекты в источнике и приемнике:

    \dt+ *.*
    \dv
    \des
    \dew

6. Запустить репликацию

В облачной базе данных PostgreSQL нет прав на команду CREATE PUBLICATION ... FOR ALL TABLES, поэтому список таблиц задается вручную.

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

  1. Получите список таблиц в исходном кластере:

    psql -h rc1a-source-srv.mdb.yandexcloud.net -p 6432 -U selectel -d my_database

    SELECT table_schema || '.' || table_name
    FROM information_schema.tables
    WHERE table_schema = 'public' AND table_type = 'BASE TABLE';
  2. Подготовьте команду для создания публикации в исходном кластере, но не выполняйте ее. В команде укажите все необходимые таблицы, которые получили на шаге 1. Имя таблицы указывается обязательно со схемой, иначе будет ошибка:

    CREATE PUBLICATION my_pub FOR TABLE
    public.table_1,
    public.table_2,
    public.table_3;
  3. Подготовьте команду для создания подписки в целевом кластере, но не выполняйте ее:

    CREATE SUBSCRIPTION my_sub
    CONNECTION 'host=rc1a-source-srv.mdb.yandexcloud.net port=6432
    dbname=my_database user=selectel password=password'
    PUBLICATION my_pub
    WITH (copy_data = true);

    Здесь:

    • host и port — адрес и порт мастера источника;
    • dbname — база источника;
    • user и password — имя и пароль пользователя для миграции;
    • my_pub — имя публикации;
    • copy_data = true — параметр, который указывает на обязательное копирование существующих данных перед потоком изменений. Значение выставляется по умолчанию, но мы рекомендуем указать его явно.
  4. Выполните подготовленную команду CREATE PUBLICATION в исходном кластере.

  5. Выполните подготовленную команду CREATE SUBSCRIPTION в целевом кластере.

7. Проверить состояние репликации

  1. Проверьте метрики кластеров:

    • исходный кластер — нагрузка CPU и памяти на мастере, Disk usage on primary, Total size of WAL files, Free space, сетевой трафик и пакеты;
    • целевой кластер — vCPU и память кластера, размер WAL-файлов, размер базы, нагрузка сети. Подробнее об отслеживании состояния кластера в инструкции Мониторинг кластера, нод и баз данных PostgreSQL.
  2. Если свободное место на мастере источника заканчивается, а первичное копирование еще не завершено, остановите репликацию, иначе кластер источника перейдет в режим read-only. Подробнее об остановке репликации в шаге 5 подраздела Переключить приложение.

  3. В целевом кластере проверьте статус копирования по каждой таблице:

    SELECT sub.subname, c.relname, sr.srsubstate
    FROM pg_subscription_rel sr
    JOIN pg_subscription sub ON sr.srsubid = sub.oid
    JOIN pg_class c ON sr.srrelid = c.oid
    ORDER BY sr.srsubstate, c.relname;

    Значения srsubstate:

    • i - инициализация,
    • d - идет копирование данных,
    • f - копирование таблицы завершено,
    • s - синхронизирована,
    • r - готово, идет штатная репликация.

    Миграция готова к переключению, когда все таблицы будут в статусе r.

  4. В целевом кластере проверьте общее состояние подписки:

    SELECT subname, pid, received_lsn, last_msg_send_time,
    last_msg_receipt_time, latest_end_lsn
    FROM pg_stat_subscription;

    SELECT * FROM pg_stat_subscription_stats;
  5. В исходном кластере проверьте слот репликации и размер удерживаемого WAL:

    SELECT slot_name, active,
    pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(),
    confirmed_flush_lsn)) AS lag
    FROM pg_replication_slots;

    SELECT * FROM pg_publication;

8. Переключить приложение

  1. Остановите запись в базу источника — остановите приложение или переведите его в режим обслуживания.

  2. Проверьте лаг на источнике:

    SELECT slot_name, active,
    pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(),
    confirmed_flush_lsn)) AS lag
    FROM pg_replication_slots;

    SELECT * FROM pg_publication;
  3. Дождитесь, пока лаг на источнике станет равен 0.

  4. Сверьте количество строк. Запрос может выполняться долго для больших баз данных:

    DO $$
    DECLARE
    rec RECORD;
    cnt BIGINT;
    BEGIN
    FOR rec IN
    SELECT n.nspname, c.relname
    FROM pg_class c
    JOIN pg_namespace n ON n.oid = c.relnamespace
    WHERE c.relkind = 'r'
    AND n.nspname NOT IN ('pg_catalog', 'information_schema')
    ORDER BY 1, 2
    LOOP
    EXECUTE format('SELECT count(*) FROM %I.%I', rec.nspname, rec.relname)
    INTO cnt;
    RAISE NOTICE '%.%: %', rec.nspname, rec.relname, cnt;
    END LOOP;
    END $$;

    Размер таблиц на источнике и приемнике может отличаться примерно до десятки гигабайт. В целевом кластере размер таблиц обычно меньше. Это возникает из-за того, что в исходном кластере хранятся «мертвые» кортежи до их очистки, а репликация переносит только актуальные данные.

  5. Восстановите значения последовательностей в целевом кластере — логическая репликация их не переносит, значения последовательностей в целевом кластере остаются начальными. Если не выставить их, приложение получит ошибку при вставке:

    4.1. Выполните скрипт в целевом кластере в переносимой базе. Скрипт находит последовательности, привязанные к столбцам (serial и identity), и выставляет каждой значение MAX(<column>) + 1:

    DO $$
    DECLARE
    seq RECORD;
    max_id BIGINT;
    BEGIN
    FOR seq IN
    SELECT c.oid::regclass AS seqname,
    n.nspname AS schemaname,
    t.relname AS tablename,
    a.attname AS columnname
    FROM pg_class c
    JOIN pg_depend d ON d.objid = c.oid AND d.deptype IN ('a', 'i')
    JOIN pg_class t ON t.oid = d.refobjid
    JOIN pg_namespace n ON n.oid = t.relnamespace
    JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = d.refobjsubid
    WHERE c.relkind = 'S'
    LOOP
    EXECUTE format('SELECT max(%I) FROM %I.%I',
    seq.columnname, seq.schemaname, seq.tablename)
    INTO max_id;
    IF max_id IS NOT NULL THEN
    PERFORM setval(seq.seqname, max_id + 1, false);
    END IF;
    END LOOP;
    END $$;

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

    SELECT last_value FROM <sequences>;

    Укажите <sequences> — имя последовательности.

    4.3. Выставьте значения последовательностей, которые вы получили на шаге 4.2, в целевом кластере через setval.

  6. Остановите репликацию:

    5.1. Удалите подписку в целевом кластере:

    DROP SUBSCRIPTION my_sub;

    5.2. Удалите публикацию в исходном кластере:

    DROP PUBLICATION my_pub;

    DROP SUBSCRIPTION удаляет и слот репликации в исходном кластере, после чего накопленный WAL освобождается.

    5.3. Если исходный кластер в этот момент недоступен, слот останется — удалите его на исходном кластере вручную, иначе WAL продолжит расти:

    SELECT pg_drop_replication_slot('my_sub');
  7. Переключите строку подключения приложения на целевой кластер в Selectel и запустите приложение.

  8. Опционально: удалите временные ресурсы, которые вы создавали для миграции данных.