- PVSM.RU - https://www.pvsm.ru -
У нас в True Engineering на одном проекте назрела необходимость в смене версии PostgreSQL с 9.6 на 11.1.
Зачем? База данных на проекте уже объемом 1,5 Tb и растет. Перформанс – одно из основных требований к системе. А сама структура данных эволюционирует: добавляются новые колонки, меняются существующие. Новая версия Postgres научилась эффективно работать с добавлением новых колонок с дефолтным значением, так что не нужно городить кастомных костылей на уровне приложения. Ещё в новой версии добавили несколько новых способов партиционирования таблиц, что тоже крайне полезно в условиях большого объема данных.
Итак, решено, мигрируем. Конечно, можно поднять параллельно со старой новую версию сервера PostgreSQL, остановить приложение, через dump/restore (или pg_upgrade) переместить базу и снова запустить приложение. Нам это решение не подошло из-за большого размера базы, к тому же, приложение работает в боевом режиме, и на даунтайм есть считанные минуты.
Поэтому мы решили попробовать миграцию с помощью логической репликации в PostgreSQL с помощью стороннего плагина под названием pglogical [1].
В процессе «проб» мы столкнулись с весьма обрывочной документацией по этому процессу (а на русском языке её вообще нет), а также некоторыми подводными камнями и неочевидными нюансами. В этой статье мы хотим изложить свой опыт в виде Tutorial.

TL;DR
Как мы уже сказали, самый простой выход: поднять параллельно со старой новую версию сервера PostgreSQL, остановить приложение, через dump/restore (или pg_upgrade) переместить базу и снова запустить приложение. Для баз небольшого объёма, в принципе, это вполне подходящий вариант (или, в общем случае — объём неважен, когда у вас есть возможность даунтайма приложения на время «переливания» БД со старого сервера на новый, каким бы долгим это время ни было). Но в нашем случае база занимает порядка 1,5 Tb на диске, и её перемещение — это вопрос не минут, а нескольких часов. Приложение же, в свою очередь, работает в боевом режиме, и даунтайма дольше пары минут очень хотелось избежать.
Также против этого варианта играл ещё и тот факт, что мы используем Master-Slave репликацию и не можем безболезненно выключить Slave-сервер из рабочего процесса. А значит, для переключения приложения со старой версии PostgreSQL на новую после миграции Master-сервера требовалось бы готовить и новый Slave-сервер до запуска приложения. А это ещё несколько часов простоя, пока создастся Slave (хотя и значительно меньше, чем миграция Master).
Поэтому решили попробовать миграцию с помощью логической репликации в PostgreSQL с помощью стороннего плагина под названием pglogical.
pglogical [1] — это система логической репликации, использующая нативный Logical Decoding в PostgreSQL и реализованная в виде PostgreSQL extension. Позволяет настраивать выборочную репликацию с помощью модели подписок/публикаций. Не требует создания триггеров в базе или использования каких-либо внешних утилит для репликации.
Расширение работает на любой версии PostgreSQL, начиная с 9.4 (поскольку Logical Decoding впервые появился в 9.4), и позволяет осуществлять миграцию между любыми поддерживаемыми версиями PostgreSQL в любом направлении.
Настройка репликации с помощью pglogical вручную не очень тривиальна, хотя в принципе и вполне возможна. К счастью, существует сторонняя утилита pgrepup [2] для автоматизации процесса настройки, которой мы и воспользуемся.
Поскольку мы планируем поднимать новую версию PostgreSQL на тех же серверах параллельно со старой, требования к диску под БД на серверах Master и Slave удваиваются. Казалось бы, это очевидно, но… Просто позаботьтесь о достаточном количестве свободного места перед запуском репликации, чтобы не жалеть о бесцельно прожитых годах.
В нашем случае потребовались модификации базы, плюс формат хранения при миграции между 9.6 и 11 «пухнет» не в пользу свежей версии, поэтому место на диске пришлось в итоге увеличивать не в 2, а примерно в 2.2 раза. Хвала LVM, это можно сделать в процессе миграции на лету.
В общем, take care of it.
Note: Мы используем Oracle Linux, и всё нижеследующее будет заточено под этот дистрибутив. Не исключено, что другие дистрибутивы 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
Копируем настройки доступа (pg_hba.conf) и настройки сервера (postgresql.conf) из 9.6 в 11.
Чтобы запустить два сервера PostgreSQL на одной машине, в конфиге postgresql.conf 11 версии меняем порт на 15432 (port = 15432).
Здесь нужно плотно подумать, что ещё нужно сделать в новой версии PostgreSQL конкретно в вашем случае, чтобы он запустился с вашим postgresql.conf (и ваше приложение могло в итоге с ним работать). В нашем случае требовалось установить в новую версию используемые нами расширения PostgreSQL. Это выходит за рамки статьи, просто сделайте так, чтобы новый PostgreSQL запустился, работал и полностью вас устраивал :)
# ставим расширения, тюним конфиги, добавляем shared libraries, whatever...
# ....
# запускаемся
systemctl enable postgresql-11
systemctl start postgresql-11
Смотрим в /var/lib/pgsql/11/data/pg_log/. Всё хорошо? Продолжаем!
# ставим python
yum install python
yum install python2-pip
# ставим pgrepup
pip install pgrepup
# создаём конфигурацию
pgrepup config

Нюансы:
# запускаем проверку
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 Key.
В нашем случае PK был не везде, поэтому на время репликации необходимо его добавить, а по окончании репликации при желании удалить.
Список таблиц без PK, среди прочего, выдаёт pgrepup check. Для всех таблиц из этого списка нужно добавить primary key любым приемлемым для вас способом. В нашем случае это было нечто вида:
ALTER TABLE %s ADD COLUMN temporary_pk BIGSERIAL NOT NULL PRIMARY KEY
У утилиты pgrepup есть встроенная команда для проведения этой операции (pgrepup fix), и при её использовании даже подразумевается, что при успешной репликации эти временные колонки будут автоматически удалены. Но, к сожалению, этот функционал так неиллюзорно и феерически глючил на больших базах, что мы решили не использовать его, а сделать эту операцию вручную так, как нам удобно.
Инструкции по установке расширения можно почитать тут [3]. Расширение необходимо установить в оба сервера.
# добавляем репозитории с нужными нам версиями
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'
Это вспомогательное расширение, которое 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 есть несколько багов, делающих миграцию невозможной. Pull request’ы отправлены, но увы, остаются без внимания, поэтому придётся сделать исправления вручную.
Идём в папку установки pgrepup (наш случай — /usr/lib/python2.7/site-packages/pgrepup/commands/).
Делай раз. В каждом файле *.py добавляем пропущенные **kwargs в описании функции. Картинка лучше тысячи слов:

Коммит тут [4].
Делай два. В setup.py делаем поиск по «sh -c», два вхождения, все многострочные команды shell нужно сделать однострочными.
Коммит тут [5].
# подготовка
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.
Здесь всё просто и по учебнику, никаких нюансов.
# добавляем репозиторий
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();
После всех этих процедур у нас получится вот такая хитрая схема репликаций:

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

До сих пор наше приложение ничего не подозревало о новой версии 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;
Запускаем приложение. Радуемся полчаса.
А напоследок полезная литература по теме:
Успехов!
Автор: maxout
Источник [11]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/linux/306334
Ссылки в тексте:
[1] pglogical: https://www.2ndquadrant.com/en/resources/pglogical/
[2] pgrepup: https://github.com/rtshome/pgrepup/
[3] тут: https://www.2ndquadrant.com/en/resources/pglogical/pglogical-installation-instructions/
[4] тут: https://github.com/Slach/pgrepup/commit/fe2146af5d8965a7fd6193669923eb9fbe7eaccc
[5] тут: https://github.com/rtshome/pgrepup/pull/16
[6] pglogical Docs: https://www.2ndquadrant.com/en/resources/pglogical/pglogical-docs/
[7] Upgrading PostgreSQL from 9.4 to 10.3 with pglogical: https://hunleyd.github.io/posts/Upgrading-PostgreSQL-from-9.4-to-10.3-with-pglogical/
[8] pgrepup – upgrade PostgreSQL using logical replication: http://gasparin.net/2016/11/pgrepup-upgrade-postgresql-using-logical-replication/
[9] pgrepup – PostgreSQL REPlicate and UPgrade: https://github.com/rtshome/pgrepup
[10] Upgrading PostgreSQL from 9.6 to 10 with minimal downtime using pglogical: https://rosenfeld.herokuapp.com/en/articles/infrastructure/2017-11-10-upgrading-postgresql-from-9-6-to-10-with-minimal-downtime-using-pglogical
[11] Источник: https://habr.com/ru/post/437318/?utm_campaign=437318
Нажмите здесь для печати.