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

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

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

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

  • исходный кластер — Managed Service for PostgreSQL в Yandex Cloud;
  • целевой кластер — облачная база данных PostgreSQL в Selectel.

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

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

Прямое создание слота логической репликации требует привилегированных прав на сервере, которых нет в DBaaS. Схема Publication/Subscription работает в рамках прав, доступных в управляемых кластерах.

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

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

  • административный доступ к облачной инфраструктуре в Yandex Cloud и Selectel или возможность оперативно вносить изменения силами вашей команды;
  • пользователь в СУБД исходного кластера с ролями mdb_superuser, mdb_replication, mdb_monitor, mdb_admin и доступом к переносимым базам;
  • стабильный сетевой канал между площадками и настроенная маршрутизация — особенно важно для крупных баз, подробнее в подразделе Сетевая связность;
  • настроенные сетевые политики (группы безопасности, firewall): кластеры должны видеть друг друга;
  • при связи площадок по внутреннему каналу (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. При заполнении диска кластер перейдет в режим read-only, а это остановка записи для приложения;

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

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

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

К хосту DBaaS в 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.

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

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

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

Подробнее о работе с кластером PostgreSQL в Selectel в разделе PostgreSQL.

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

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

  3. Сделайте резервную копию базы данных.

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

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

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

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

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

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

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

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

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

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

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

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

  • таблицы схемы 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. Сделать резервную копию

Cделайте резервную копию базы данных. Для очень больших баз убедитесь, что есть актуальный бэкап средствами DBaaS.

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

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

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

Имена пользователя и базы задайте такими же, как в источнике: это упростит импорт схемы. Подробнее о работе с кластером PostgreSQL в Selectel в разделе PostgreSQL.

  1. Задайте параметры СУБД, близкие к источнику.
  2. Создайте пользователя - будущего владельца базы.
  3. Создайте пустую базу с этим владельцем. Локаль укажите такую же, как в источнике (например, en_US.UTF-8) - изменить ее потом нельзя.
  4. При необходимости создайте остальных пользователей, как в источнике.
  5. Установите расширения, найденные на этапе анализа.
  6. Разрешите в группах безопасности трафик из подсетей Yandex Cloud.
  7. Для кастомного DNS (адрес Unbound-02) создайте тикет в техподдержку Selectel - в панели управления для уже созданного кластера этот параметр не меняется.

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

  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

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

В DBaaS нет прав на команду 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 в целевом кластере.

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

  1. Отслеживайте метрики в консолях. Если свободное место на мастере источника подходит к порогу, а первичное копирование еще не завершено, немедленно остановите репликацию (подробнее в подразделе Переключить приложение, шаг 5), иначе кластер источника перейдет в read-only:

    • Yandex Cloud (исходный кластер) — нагрузка CPU и памяти на мастере, Disk usage on primary, Total size of WAL files, Free space (расстояние до порога перехода в read-only), сетевой трафик и пакеты;
    • Selectel (целевой кластер) — vCPU и память кластера, размер WAL-файлов, размер базы (должен расти до размера источника), нагрузка сети.
  2. В целевом кластере проверьте статус копирования по каждой таблице:

    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.

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

    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; -- счетчики ошибок
  4. В исходном кластере проверьте слот репликации и размер удерживаемого 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;

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

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

  2. Дождитесь, пока лаг на источнике станет равен 0. Используйте запрос к pg_replication_slots из этапа Проверить состояние репликации, шаг 4.

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

    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 $$;

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

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

    ERROR: duplicate key value violates unique constraint "users_pkey"
    DETAIL: Key (id)=(27) already exists.

    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 <имя_последовательности> и выставьте в целевом кластере через setval.

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

    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');
  6. Переключите строку подключения приложения на целевой кластер в Selectel и запустите приложение.

  7. Опционально: удалите временные ресурсы (ВМ с VPN и DNS-резолверами, пользователь selectel в исходном кластере).