Главная » Хабрахабр » Бесшовная (почти) миграция между мажорными релизами PostgreSQL с помощью логической репликации

Бесшовная (почти) миграция между мажорными релизами PostgreSQL с помощью логической репликации

У нас в True Engineering на одном проекте назрела необходимость в смене версии PostgreSQL с 9.6 на 11.1.

База данных на проекте уже объемом 1,5 Tb и растет. Зачем? А сама структура данных эволюционирует: добавляются новые колонки, меняются существующие. Перформанс – одно из основных требований к системе. Ещё в новой версии добавили несколько новых способов партиционирования таблиц, что тоже крайне полезно в условиях большого объема данных. Новая версия Postgres научилась эффективно работать с добавлением новых колонок с дефолтным значением, так что не нужно городить кастомных костылей на уровне приложения.

Конечно, можно поднять параллельно со старой новую версию сервера PostgreSQL, остановить приложение, через dump/restore (или pg_upgrade) переместить базу и снова запустить приложение. Итак, решено, мигрируем. Нам это решение не подошло из-за большого размера базы, к тому же, приложение работает в боевом режиме, и на даунтайм есть считанные минуты.

Поэтому мы решили попробовать миграцию с помощью логической репликации в PostgreSQL с использованием стороннего плагина под названием pglogical.

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

TL;DR

  • Всё получилось (не без костылей, о них и статья).
  • Мигрировать можно в рамках PostgreSQL версии от 9.4 до 11.x, с любой версии на любую, вниз или вверх.
  • Даунтайм равен времени, которое требуется вашему приложению, чтобы переподключиться к новому серверу БД (в нашем случае это был перезапуск всего приложения, но в дикой природе, очевидно, «возможны варианты»).

Почему нам не подошло решение «в лоб»

Как мы уже сказали, самый простой выход: поднять параллельно со старой новую версию сервера PostgreSQL, остановить приложение, через dump/restore (или pg_upgrade) переместить базу и снова запустить приложение. Для баз небольшого объёма, в принципе, это вполне подходящий вариант (или, в общем случае — объём неважен, когда у вас есть возможность даунтайма приложения на время «переливания» БД со старого сервера на новый, каким бы долгим это время ни было). Но в нашем случае база занимает порядка 1,5 Tb на диске, и её перемещение — это вопрос не минут, а нескольких часов. Приложение же, в свою очередь, работает в боевом режиме, и даунтайма дольше пары минут очень хотелось избежать.

А значит, для переключения приложения со старой версии PostgreSQL на новую после миграции Master-сервера требовалось бы готовить и новый Slave-сервер до запуска приложения. Также против этого варианта играл ещё и тот факт, что мы используем Master-Slave репликацию и не можем безболезненно выключить Slave-сервер из рабочего процесса. А это ещё несколько часов простоя, пока создастся Slave (хотя и значительно меньше, чем миграция Master).

Поэтому решили попробовать миграцию с помощью логической репликации в PostgreSQL с помощью стороннего плагина под названием pglogical.

Общая информация

pglogical — это система логической репликации, использующая нативный Logical Decoding в PostgreSQL и реализованная в виде PostgreSQL extension. Позволяет настраивать выборочную репликацию с помощью модели подписок/публикаций. Не требует создания триггеров в базе или использования каких-либо внешних утилит для репликации.

4 (поскольку Logical Decoding впервые появился в 9. Расширение работает на любой версии PostgreSQL, начиная с 9. 4), и позволяет осуществлять миграцию между любыми поддерживаемыми версиями PostgreSQL в любом направлении.

К счастью, существует сторонняя утилита pgrepup для автоматизации процесса настройки, которой мы и воспользуемся. Настройка репликации с помощью pglogical вручную не очень тривиальна, хотя в принципе и вполне возможна.

Памятка о свободном месте на диске

Поскольку мы планируем поднимать новую версию PostgreSQL на тех же серверах параллельно со старой, требования к диску под БД на серверах Master и Slave удваиваются. Казалось бы, это очевидно, но… Просто позаботьтесь о достаточном количестве свободного места перед запуском репликации, чтобы не жалеть о бесцельно прожитых годах.

6 и 11 «пухнет» не в пользу свежей версии, поэтому место на диске пришлось в итоге увеличивать не в 2, а примерно в 2. В нашем случае потребовались модификации базы, плюс формат хранения при миграции между 9. Хвала LVM, это можно сделать в процессе миграции на лету. 2 раза.

В общем, take care of it.

Устанавливаем PostgreSQL 11 на Master

Не исключено, что другие дистрибутивы Linux потребуют небольшой доработки напильником, но вряд ли она будет существенной. Note: Мы используем Oracle Linux, и всё нижеследующее будет заточено под этот дистрибутив.

# добавляем репозиторий
yum install https://download.postgresql.org/pub/repos/yum/11/redhat/rhel-7-x86_64/pgdg-oraclelinux11-11-2.noarch.rpm # устанавливаем пакеты postgresql11
yum install postgresql11 postgresql11-devel postgresql11-server postgresql11-contrib # инициализируем базу
/usr/pgsql-11/bin/postgresql-11-setup initdb

Старый datadir расположен в /var/lib/pgsql/9.6/data, новый, соответственно, ложится в /var/lib/pgsql/11/data

6 в 11. Копируем настройки доступа (pg_hba.conf) и настройки сервера (postgresql.conf) из 9.

Чтобы запустить два сервера PostgreSQL на одной машине, в конфиге postgresql.conf 11 версии меняем порт на 15432 (port = 15432).

В нашем случае требовалось установить в новую версию используемые нами расширения PostgreSQL. Здесь нужно плотно подумать, что ещё нужно сделать в новой версии PostgreSQL конкретно в вашем случае, чтобы он запустился с вашим postgresql.conf (и ваше приложение могло в итоге с ним работать). Это выходит за рамки статьи, просто сделайте так, чтобы новый PostgreSQL запустился, работал и полностью вас устраивал 🙂

# ставим расширения, тюним конфиги, добавляем shared libraries, whatever...
# ....
# запускаемся
systemctl enable postgresql-11
systemctl start postgresql-11

Смотрим в /var/lib/pgsql/11/data/pg_log/. Всё хорошо? Продолжаем!

Устанавливаем и настраиваем pgrepup

# ставим python
yum install python
yum install python2-pip # ставим pgrepup
pip install pgrepup # создаём конфигурацию
pgrepup config

Нюансы:

  1. В качестве app_owner указываем пользователя, под которым запущены серверы PostgreSQL.
  2. В качестве Database указываем template1.
  3. Username и Password — данные для доступа суперюзера. В нашем случае в pg_hba.conf для локальных подключений пользователя postgres был прописан метод trust, поэтому пароль можно указать произвольный.

Настраиваем репликацию

# запускаем проверку
pgrepup check

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

Пример результатов проверки:

В настройках обоих серверов должен быть выставлен wal_level=LOGICAL (для работы Logical Decoding), нужные настройки для движка репликации (количество слотов и wal_senders). Все ошибки при проверке нужно будет устранить. Подсказки утилиты pgrepup достаточно информативны, по большинству пунктов вопросов возникнуть не должно.

Вносим все необходимые настройки, которые просит pgrepup.

В оба файла pg_hba.conf добавляем права доступа для пользователя, который будет делать репликацию, всё по подсказке pgrepup:

host replication pgrepup_replication 127.0.0.1/32 md5
host all pgrepup_replication 127.0.0.1/32 md5

Добавляем Primary Keys

Для работы репликации во всех таблицах должен быть определён Primary Key.

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

Для всех таблиц из этого списка нужно добавить primary key любым приемлемым для вас способом. Список таблиц без PK, среди прочего, выдаёт pgrepup check. В нашем случае это было нечто вида:

ALTER TABLE %s ADD COLUMN temporary_pk BIGSERIAL NOT NULL PRIMARY KEY

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

Устанавливаем pglogical extension

Инструкции по установке расширения можно почитать тут. Расширение необходимо установить в оба сервера.

# добавляем репозитории с нужными нам версиями
curl https://access.2ndquadrant.com/api/repository/dl/default/release/9.6/rpm | bash
curl https://access.2ndquadrant.com/api/repository/dl/default/release/11/rpm | bash # устанавливаем пакеты
yum install postgresql96-pglogical postgresql11-pglogical

Добавляем загрузку библиотеки в postgresql.conf обоих серверов:

shared_preload_libraries = 'pglogical'

Устанавливаем pgl_ddl_deploy extension

Это вспомогательное расширение, которое pgrepup использует для логической репликации DDL.

# и его придётся собирать вручную
git clone https://github.com/enova/pgl_ddl_deploy.git # сборка и установка для старого сервера
PATH=/usr/pgsql-9.6/bin/:$PATH
USE_PGXS=1 make
USE_PGXS=1 make install
make clean # сборка и установка для нового сервера
PATH=/usr/pgsql-11/bin/:$PATH
make CLANG=true
make install

Добавляем загрузку библиотеки в postgresql.conf обоих серверов:

shared_preload_libraries = 'pglogical,pgl_ddl_deploy'

Проверяем внесённые изменения

# перезапускаем новый postgresql
systemctl restart postgresql-11

Теперь с помощью pgrepup check необходимо убедиться, что с целевым сервером всё стало ок и все замечания касательно целевого сервера полностью устранены.

Здесь нужно подумать о том, как к перезагрузке сервера БД отнесётся ваше приложение, возможно, следует его предварительно остановить. Если всё хорошо, можно перезапускать старый сервер.

# перезапускаем
systemctl restart postgresql-9.6 # проверяем
pgrepup check

Теперь в выводе команды все до единого пункты должны быть отмечены как ОК.

Казалось бы, можно запускать миграцию, но…

Правим баги pgrepup

В актуальной версии pgrepup есть несколько багов, делающих миграцию невозможной. Pull request’ы отправлены, но увы, остаются без внимания, поэтому придётся сделать исправления вручную.

7/site-packages/pgrepup/commands/). Идём в папку установки pgrepup (наш случай — /usr/lib/python2.

В каждом файле *.py добавляем пропущенные **kwargs в описании функции. Делай раз. Картинка лучше тысячи слов:

Коммит тут.

В setup.py делаем поиск по «sh -c», два вхождения, все многострочные команды shell нужно сделать однострочными. Делай два.

Коммит тут.

Запускаем миграцию

# подготовка
pgrepup setup

Этой командой pgrepup подготавливает оба сервера к запуску репликации, создаёт пользователя, настраивает pglogical, переносит схему БД.

# запускаем репликацию
pgrepup start

Он сказал «Поехали!» и махнул рукой:

Текущую ситуацию можно увидеть с помощью команды pgrepup status: Репликация запущена.

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

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

SELECT * FROM pg_replication_origin_status ORDER BY remote_lsn DESC;
SELECT *,pg_xlog_location_diff(s.sent_location,s.replay_location) byte_lag FROM pg_stat_replication s;
SELECT query FROM pg_stat_activity WHERE application_name='subscription_copy'

Вдоволь напившись кофе (на тестовом сервере при написании этой статьи миграция ~700Gb данных длилась в районе суток), мы наконец видим такую картину:

И это означает, что пришло время готовить новый Slave.

Устанавливаем PostgreSQL 11 на Slave

Здесь всё просто и по учебнику, никаких нюансов.

# добавляем репозиторий
yum install https://download.postgresql.org/pub/repos/yum/11/redhat/rhel-7-x86_64/pgdg-oraclelinux11-11-2.noarch.rpm # устанавливаем postgresql 11
yum install postgresql11 postgresql11-devel postgresql11-server postgresql11-contrib # переливаем данные с нового мастера
su - postgres
pg_basebackup -h db-master.hostname -p 15432 -D /var/lib/pgsql/11/data/ -R -P -U replication -X stream -c fast

Копируем настройки доступа (pg_hba.conf) и настройки сервера (postgresql.conf) из 9.6 в 11. В конфиге postgresql.conf 11 версии меняем порт на 15432 (port = 15432)

# запускаем
systemctl enable postgresql-11
systemctl start postgresql-11

# проверяем статус репликации на Master
SELECT *,pg_wal_lsn_diff(s.sent_lsn,s.replay_lsn) AS byte_lag FROM pg_stat_replication s; # проверяем статус репликации на Slave
SELECT now()-pg_last_xact_replay_timestamp();

Промежуточные итоги

После всех этих процедур у нас получится вот такая хитрая схема репликаций:

6 Master и пронаблюдать, как он среплицируется на остальные три сервера. Здесь в качестве последней проверки (ну и, в конце концов, это просто красиво) можно сделать какой-нибудь UPDATE в базу на 9.

image

Переключение приложения на новую версию PostgreSQL

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

Для интереса ответим на оба вопроса «да» и приступим.

Останавливаем приложение.

# проверяем, что нет коннектов, например:
SELECT * FROM pg_stat_activity;

# останавливаем логическую репликацию
# при этом также произойдёт финальная синхронизация sequences.
pgrepup stop

# чистим за собой все хвосты
pgrepup uninstall

# на master:
# выключаем старый сервис
systemctl disable postgresql-9.6
# останавливаем оба сервиса, сначала старый, затем новый.
systemctl stop postgresql-9.6
systemctl stop postgresql-11 # на slave:
# выключаем старый сервис
systemctl disable postgresql-9.6
# останавливаем оба сервиса, сначала старый, затем новый.
systemctl stop postgresql-9.6
systemctl stop postgresql-11

Возвращаем стандартный порт в конфиге postgresql.conf новой версии на Master и Slave.

На новом Slave также меняем порт на стандартный в recovery.conf.

Попутно есть предложение от греха подальше поменять порт на становящейся неактивной старой версии:
Выставляем нестандартный порт в postgresql.conf старой версии на Master и Slave.
На старом Slave также меняем порт на нестандартный в recovery.conf.

# запускаем на master
systemctl enable postgresql-11
systemctl start postgresql-11 # запускаем на slave:
systemctl enable postgresql-11
systemctl start postgresql-11

Проверяем логи.

Проверяем статус репликации на Master.

SELECT *,pg_wal_lsn_diff(s.sent_lsn,s.replay_lsn) AS byte_lag FROM pg_stat_replication s;

Запускаем приложение. Радуемся полчаса.

А напоследок полезная литература по теме:

Успехов!


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

[Перевод] Python Testing с pytest. Начало работы с pytest, Глава 1

Вернуться Дальше Это уже приносит мне дивиденды в моей компании.Chris ShaverVP of Product, Uprising Technology Я обнаружил, что Python Testing с pytest является чрезвычайно полезным вводным руководством к среде тестирования pytest. 6 и pytest 3. Примеры в этой книге написаны ...

[Перевод] Python Testing с pytest. ГЛАВА 3 pytest Fixtures

Вернуться Дальше Эта книга — недостающая глава, отсутствующая в каждой всеобъемлющей книге Python. Frank RuizPrincipal Site Reliability Engineer, Box, Inc. 6 и pytest 3. Примеры в этой книге написаны с использованием Python 3. pytest 3. 2. 6, 2. 2 поддерживает ...