---
title: 'Перенос управляемого PostgreSQL из Yandex Cloud в облачную базу данных PostgreSQL'
sidebar_label: 'Перенести данные из управляемого PostgreSQL Yandex Cloud в облачную базу данных PostgreSQL'
sidebar_position: 8
description: 'Как перенести данные из управляемого PostgreSQL Yandex Cloud в облачную базу данных PostgreSQL'
---

import {CustomTable} from '@selectel/docux/components'
import Formbricks from '@theme/MDXComponents/Formbricks'

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

## Как устроена миграция \{#principle-of-operation}

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

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

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

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

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

## Что нужно для миграции \{#what-is-required}

### Инфраструктура \{#infrastructure}

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

### Рабочее окружение \{#environmemt}

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

## Риски, которые нужно учесть заранее \{#risks}

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

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

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

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

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

## Сетевая связность \{#network-connectivity}

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

### Варианты соединения между Yandex Cloud и Selectel \{#connection-options}

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

* вариант 2 — VPN-туннель. На каждой площадке поднимается ВМ, между ними строится туннель через интернет (WireGuard, OpenVPN, IPsec). ВМ должны находиться в облачных сетях, доступных для DBaaS. Маршрутизация — через облачный роутер в Selectel и таблицу маршрутизации в Yandex Cloud.
  Статические маршруты в Selectel лучше [задавать на облачном роутере](/cloud-servers/cloud-networks/static-routes.mdx#configure-static-routing-on-cloud-router), а не в настройках подсети: тогда они применятся ко всем продуктам, для которых роутер является шлюзом по умолчанию, включая DBaaS. Сделать это можно через OpenStack CLI или API, в том числе Terraform.

### DNS-резолвер для зоны yandexcloud.net \{#dns-resolver}

К хосту 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):

```bash
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.

## Шаги миграции \{#migration-steps}

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

<CustomTable>
  <table>
    <thead>
      <tr>
        <th>Параметр</th>
        <th>Имя в примере</th>
      </tr>
    </thead>

    <tbody>
      <tr>
        <td>FQDN мастера в кластере Selectel</td>
        <td>master.destination-srv.c.dbaas.selcloud.ru</td>
      </tr>

      <tr>
        <td>FQDN мастера в кластере Yandex Cloud</td>
        <td>rc1a-source-srv.mdb.yandexcloud.net</td>
      </tr>

      <tr>
        <td>Порт (не пулер) в кластере Selectel</td>
        <td>5432</td>
      </tr>

      <tr>
        <td>Порт (не пулер) в кластере Yandex Cloud</td>
        <td>6432</td>
      </tr>

      <tr>
        <td>my_database</td>
        <td>Имя базы данных в Selectel</td>
      </tr>

      <tr>
        <td>my_database</td>
        <td>Имя базы данных в Yandex Cloud</td>
      </tr>

      <tr>
        <td>my_user</td>
        <td>Пользователь — владелец базы данных в Selectel</td>
      </tr>

      <tr>
        <td>selectel</td>
        <td>Пользователь для переноса в Yandex Cloud</td>
      </tr>
    </tbody>
  </table>
</CustomTable>

Подробнее о работе с кластером PostgreSQL в Selectel в разделе [PostgreSQL](/managed-databases/postgresql/).

1. [Организуйте связность между Yandex Cloud и Selectel](#set-connection).

2. [Проведите анализ базы данных перед миграцией](#perform-analysis).

3. [Сделайте резервную копию базы данных](#create-backup).

4. [Подготовьте исходный кластер в Yandex Cloud](#prepare-initial-cluster).

5. [Подготовьте целевой кластер в Selectel](#prepare-target-cluster).

6. [Перенесите схему базы данных из исходного кластера в целевой кластер](#move-schema).

7. [Запустите репликацию](#start-replication).

8. [Проверьте состояние репликации](#check-replication-status).

9. [Переключите приложение](#switch-application).

### 1. Организовать связность между Yandex Cloud и Selectel \{#set-connection}

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

### 2. Провести анализ базы данных перед миграцией \{#perform-analysis}

Примеры запросов для проведения анализа смотрите в подразделе [Полезные запросы](#useful-requests).

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

#### Полезные запросы \{#useful-requests}

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

  ```bash
  SELECT table_name FROM information_schema.tables WHERE table_schema = 'public';
  ```

* таблицы, поддерживаемые логической репликацией:

  ```bash
  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';
  ```

* таблицы без первичного ключа:

  ```bash
  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');
  ```

* расширения:

  ```bash
  \dx
  ```

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

  ```bash
  \des+
  \det+
  \dew+
  ```

* базы и пользователи:

  ```bash
  \l+
  \du+
  ```

### 3. Сделать резервную копию \{#create-backup}

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

### 4. Подготовить исходный кластер в Yandex Cloud \{#prepare-initial-cluster}

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

### 5. Подготовить целевой кластер в Selectel \{#prepare-target-cluster}

Имена пользователя и базы задайте такими же, как в источнике: это упростит импорт схемы. Подробнее о работе с кластером PostgreSQL в Selectel в разделе [PostgreSQL](/managed-databases/postgresql/).

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

### 6. Перенести схему базы данных из исходного кластера в целевой кластер \{#move-schema}

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

   ```bash
   pg_dump -h rc1a-source-srv.mdb.yandexcloud.net -p 6432 -U selectel \
     -d my_database --schema-only -f /data/my_database.sql
   ```

2. Загрузите схему в целевой кластер:

   ```bash
   psql -h master.destination-srv.c.dbaas.selcloud.ru -p 5432 -U my_user \
     -d my_database -f /data/my_database.sql
   ```

3. Проверьте вывод на ошибки. Ошибки `GRANT` допустимы, если дополнительные пользователи еще не созданы. Сравните объекты в источнике и приемнике:

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

### 7. Запустить репликацию \{#start-replication}

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

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

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

   ```bash
   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. Имя таблицы указывается обязательно со схемой, иначе будет ошибка:

   ```bash
   CREATE PUBLICATION my_pub FOR TABLE
       public.table_1,
       public.table_2,
       public.table_3;
   ```

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

   ```bash
   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. Проверить состояние репликации \{#check-replication-status}

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

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

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

   ```bash
   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. В целевом кластере проверьте общее состояние подписки:

   ```bash
   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:

   ```bash
   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. Переключить приложение \{#switch-application}

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

2. Дождитесь, пока лаг на источнике станет равен 0. Используйте запрос к `pg_replication_slots` из этапа [Проверить состояние репликации](#check-replication-status), шаг 4.

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

   ```bash
   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. Восстановите значения последовательностей в целевом кластере — логическая репликация их не переносит, значения последовательностей в целевом кластере остаются начальными. Если не выставить их, приложение получит ошибку при вставке:

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

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

   ```bash
   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. Удалите подписку в целевом кластере:

   ```bash
   DROP SUBSCRIPTION my_sub;
   ```

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

   ```bash
   DROP PUBLICATION my_pub;
   ```

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

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

   ```bash
   SELECT pg_drop_replication_slot('my_sub');
   ```

6. Переключите строку подключения приложения на целевой кластер в Selectel и запустите приложение.

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

<Formbricks />
