Как обновить PostgreSQL с минимальным простоем: pg_upgrade, логическая репликация и план отката

12 ноября PostgreSQL 14 получит последний патч, а 19 ноября по плану выходит PostgreSQL 19. Хороший момент, чтобы перестать переносить обновление PostgreSQL на следующий квартал. Centicore Group о пути от проверки расширений до переключения приложения: где хватит pg_upgrade, где нужна логическая репликация и почему план отката пишут до старта.

Обложка: Как обновить PostgreSQL с минимальным простоем: pg_upgrade, логическая репликация и план отката

Почему обновление PostgreSQL больше нельзя откладывать

Сообщество поддерживает каждую мажорную версию пять лет, потом выходит финальный минорный релиз, и ветка перестаёт получать исправления. Для PostgreSQL 14 это случится 12 ноября 2026 года.

Чем это грозит, давайте покажем на недавнем примере: в августе 2026 года закрыли уязвимость CVE-2026-16239 с оценкой 8,8 по CVSS. Она позволяла пользователю базы выполнить произвольный код от имени системного пользователя сервера и затрагивала ветки с 14-й по 18-ю. Для 14-й исправление вошло в релиз 14.24. Следующую такую дыру после 12 ноября для 14-й уже не закроют.

Но PostgreSQL 14 может работать у вас, даже если вы его специально не выбирали: в Ubuntu 22.04 LTS пакет postgresql по умолчанию ставит именно 14-ю версию, об этом пишут в разборе WZ-IT.

Если база в облаке, провайдер может обновить её сам. В Yandex Managed Service for PostgreSQL кластеры начинают автоматически обновлять за три месяца до конца поддержки. Когда поддержка заканчивается, кластер переводят на последнюю поддерживаемую версию. Если обновить его нельзя, кластер останавливают.

Минорное и мажорное обновление: на какую версию переходить

Минорное обновление, например с 18.5 на 18.6, простое. Вы останавливаете сервер, ставите новые бинарники и запускаете его снова. Каталог данных не меняется, так написано в разделе документации об обновлении кластера.

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

Целевую версию выбирайте по сроку поддержки из политики версий:

5 сервисов · 3 столбца
ВерсияПоддержка доЧто учесть
1511 ноября 2027через год придётся обновляться снова
169 ноября 2028запас всего два года
178 ноября 2029если приложение ещё не готово к 18-й
1814 ноября 2030вышла в сентябре 2025 года, актуальный релиз 18.6
195 лет с даты выходарелиз запланирован на 19 ноября 2026
Листайте столбцы →Смахните влевоВсе 3 столбца

Для большинства систем, которые уходят с 14-й, лучше выбрать 18-ю. PostgreSQL 19 к моменту публикации ещё не вышел: первый релиз-кандидат появился 15 октября, второй намечен на 12 ноября, а общий релиз запланирован на 19 ноября.

Промежуточные версии проходить не нужно, с 14-й можно сразу перейти на 18-ю. Но release notes всех пропущенных версий прочитать придётся, в первую очередь разделы Migration.

Три способа обновления и сколько простоя даёт каждый

У мажорного обновления три пути. Они по-разному тратят время и оставляют разные варианты отката.

3 сервиса · 3 столбца
СпособПростойПуть назад
pg_dumpall и восстановлениерастёт вместе с объёмом данныхстарый кластер не меняется
pg_upgradeминуты в режиме linkзависит от режима переноса файлов
Логическая репликациясекунды на переключениестарый кластер работает до переключения
Листайте столбцы →Смахните влевоВсе 3 столбца

Дамп самый надёжный и самый медленный. В GitLab оценивали, что дамп и восстановление базы около 20 ТиБ занимают примерно сутки, об этом рассказано в докладе команды на PostgresConf 2024. Для дампа берите pg_dump и pg_dumpall из новой версии.

pg_upgrade не переливает данные. Он создаёт новые системные каталоги и переиспользует старые файлы, поэтому в режиме --link укладывается в минуты, пишет документация pg_upgrade.

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

Что проверить до окна обслуживания

Большинство сорванных обновлений ломается не на самом pg_upgrade, а на окружении вокруг него.

Расширения. pg_upgrade переносит определения расширений, но не ставит их библиотеки. Файлы .so под новую версию должны быть установлены до запуска, а CREATE EXTENSION в новом кластере выполнять не нужно. В гайде Netdata по сбоям pg_upgrade описана ловушка PostGIS: SQL-функция PostGIS_Full_Version() отработает без C-библиотеки, а ST_Contains упадёт. Поэтому проверяйте расширение вызовом C-функции.

Блокирующие объекты. Неподтверждённые prepared transactions остановят pg_upgrade. Ещё утилита не переносит столбцы с типами reg*, кроме regclass, regrole и regtype.

Контрольные суммы данных. В PostgreSQL 18 initdb включает их по умолчанию, а pg_upgrade требует одинаковую настройку у старого и нового кластера. Многие кластеры на 14-й работают без них. Новый кластер создают с initdb --no-data-checksums или включают суммы на остановленном старом кластере через pg_checksums --enable.

Правила сортировки. В glibc 2.28 правила сортировки привели к стандарту ISO 14651. Индексы, построенные на старых правилах, на новой glibc могут возвращать неверные результаты, подробно об этом пишет Crunchy Data. Риск появляется, когда вместе с PostgreSQL вы меняете ОС. Дамп и логическая репликация от этой проблемы защищены: индексы строятся заново.

			-- ищем зависшие подготовленные транзакции
SELECT * FROM pg_prepared_xacts;
-- сравниваем версию collation в каталоге с версией в ОС
SELECT collname, collversion, pg_collation_actual_version(oid) FROM pg_collation WHERE collversion IS NOT NULL;
-- подключаем расширение для проверки индексов
CREATE EXTENSION IF NOT EXISTS amcheck;
-- проверяем B-tree индекс вместе с данными таблицы
SELECT bt_index_check('orders_email_idx'::regclass, true);

		

Если цель PostgreSQL 19, сверьтесь с release notes 19-й версии. Дампы, снятые pg_dump старше 19-й версии при standard_conforming_strings = off, не загрузятся корректно. pg_upgrade откажется переносить кластер с индексами btree_gist по типам inet и cidr.

Резервная копия и точка невозврата

Перед обновлением нужен бэкап, который вы уже хоть раз восстановили. Проверьте и сам инструмент резервного копирования: он тоже должен поддерживать целевую версию. Например, pgBackRest поддерживает PostgreSQL 19rc1 начиная с версии 2.59.2.

У pg_upgrade есть точка невозврата, и она зависит от режима. В режиме --link старый кластер становится непригодным, как только вы запустили новый: файлы данных у них общие. В режиме --swap старый кластер разрушается уже на этапе переноса файлов. Скорость --link с нетронутым старым кластером даёт --clone. Он работает на Linux с ядром 4.5 и выше на Btrfs и XFS с поддержкой reflink, а также на macOS с APFS. Ещё документация предлагает обновлять в режиме link копию кластера или снапшот файловой системы.

Пробный перенос на копии прода

Репетиция показывает реальное время работы, недостающие пакеты и ошибки до начала окна обслуживания. Начните с pg_upgrade --check. Он только проверяет кластеры и не меняет данные, а запускать его можно при работающем старом сервере. Флаг режима передавайте тот же, что и в боевом запуске, иначе проверки для этого режима не выполнятся.

			# каталог с бинарниками старой версии
export PGBINOLD=/usr/lib/postgresql/14/bin
# каталог с бинарниками новой версии
export PGBINNEW=/usr/lib/postgresql/18/bin
# каталог данных старого кластера
export PGDATAOLD=/var/lib/postgresql/14/main
# каталог данных нового кластера
export PGDATANEW=/var/lib/postgresql/18/main
# запускаем только проверки с тем же режимом, что будет в бою
$PGBINNEW/pg_upgrade --check --link

		

В Debian и Ubuntu конфигурация лежит отдельно от данных, в /etc/postgresql. Поэтому путь к postgresql.conf передают через --old-options и --new-options.

Прогон делайте на копии продовых данных и с тестами приложения. В GitLab весь процесс автоматизировали в Ansible и прогоняли в проде все шаги до момента переключения, замеряя время.

Если на обследование и прогон не хватает своих людей, эту часть можно передать подрядчику. Centicore Group модернизирует легаси-код, базы данных и инфраструктуру и помогает перейти на актуальные инструменты и платформы. Перед передачей соберите версии, объём данных, список расширений и допустимое окно простоя: с этими вводными будет проще оценить работу.

pg_upgrade: как сократить простой до минут

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

  1. --copy копирует файлы, это режим по умолчанию и самый медленный;
  2. --clone клонирует файлы через reflink и не трогает старый кластер;
  3. --copy-file-range использует системный вызов copy_file_range на Linux и FreeBSD;
  4. --link создаёт жёсткие ссылки, каталоги должны лежать на одной файловой системе;
  5. --swap из PostgreSQL 18 перемещает каталоги данных целиком.

--swap выигрывает на кластерах с большим числом таблиц. По разбору Fujitsu он работает при обновлении с PostgreSQL 10 и выше. Документация советует запускать его вместе с --sync-method=fsync.

Флаг --jobs обрабатывает несколько баз и табличных пространств параллельно. Начинать советуют со значения, равного числу ядер CPU.

Главное изменение 18-й версии касается статистики. Раньше после pg_upgrade её собирали заново для всей базы, и первое время планировщик работал вслепую. Теперь, как объясняет Павел Лузанов из Postgres Professional, основная статистика оптимизатора переносится. Не переносятся расширенная статистика из CREATE STATISTICS, статистика расширений и накопительные счётчики. Поэтому после запуска выполните две команды:

			# быстро собираем статистику только там, где её нет
vacuumdb --all --analyze-in-stages --missing-stats-only
# обновляем накопительную статистику для autovacuum
vacuumdb --all --analyze-only

		

pg_upgrade не копирует postgresql.conf и pg_hba.conf. Перенесите shared_preload_libraries, параметры памяти и правила доступа вручную.

Логическая репликация: перенос почти без простоя

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

схема и DDL не реплицируются, их переносят через pg_dump --schema-only и замораживают на время миграции;значения последовательностей не реплицируются;large objects не передаются;реплицируются только таблицы;для UPDATE и DELETE нужен первичный ключ или другой replica identity.

DDL в GitLab заморозили просто: отключили фоновые миграции на время переезда.

Слабое место способа в начальной синхронизации, поэтому записывайте: сначала поднимаем физическую реплику, а потом превращаем её в логическую. С PostgreSQL 17 это делает pg_createsubscriber без повторного копирования данных. Исходный сервер, реплика и сама утилита должны быть одной мажорной версии, поэтому реплику сначала делают логической и только потом обновляют pg_upgrade. На исходном сервере нужен wal_level = logical, а max_slot_wal_keep_size советуют выставить в -1, чтобы нужные WAL не удалились.

			# проверяем преобразование без изменений в каталоге данных
pg_createsubscriber --dry-run -D /var/lib/postgresql/17/main -P "host=old-primary dbname=app" -d app
# превращаем остановленную физическую реплику в логическую
pg_createsubscriber -D /var/lib/postgresql/17/main -P "host=old-primary dbname=app" -d app

		

Для версий старше 17-й физическую реплику переводят в логическую вручную: через логический слот, recovery_target_lsn и подписку с copy_data = false. Схему описывает Николай Самохвалов из Postgres.ai.

Последовательности: главная ловушка переключения

Данные в столбцах serial и identity реплицируются вместе с таблицей. Сами последовательности на подписчике до PostgreSQL 18 включительно остаются на стартовых значениях. После переключения первый же INSERT получит занятый идентификатор и упадёт на уникальном ключе.

Последовательности синхронизируют вручную прямо перед переключением.

			-- на старом мастере смотрим текущие значения последовательностей
SELECT schemaname, sequencename, last_value FROM pg_sequences;
-- на новом кластере выставляем значение со старого мастера плюс запас
SELECT setval('public.orders_id_seq', 2698070);

		

В PostgreSQL 19 появилась синхронизация последовательностей: публикация ALL SEQUENCES и команда ALTER SUBSCRIPTION ... REFRESH SEQUENCES. Значения копируются только по явной команде, поэтому её ставят в сценарий переключения. Публиковать последовательности должен сервер на 19-й версии, так что по сценарию Fujitsu это пригодится при переходе с 19-й на 20-ю. При обновлении с 14-й или 18-й синхронизация остаётся ручной.

Переключение приложения

Сценарий переключения пишут по шагам и прогоняют на репетиции:

  1. Перевести старый мастер в режим «по умолчанию только чтение» и запомнить позицию WAL.
  2. Дождаться, пока подписка на новом кластере дойдёт до этой позиции.
  3. Синхронизировать последовательности.
  4. Поставить PgBouncer на паузу, переключить его на новый кластер и снять паузу.
  5. Отключить подписку на новом кластере, но не удалять её.
			-- на старом мастере: новые транзакции открываются только на чтение
ALTER SYSTEM SET default_transaction_read_only = on;
-- применяем настройку без перезапуска
SELECT pg_reload_conf();
-- запоминаем текущую позицию WAL
SELECT pg_current_wal_lsn();
-- на новом кластере: проверяем, до какой позиции дошла подписка
SELECT subname, received_lsn, latest_end_lsn FROM pg_stat_subscription;

		

Команда PAUSE дожидается освобождения серверных соединений, в режиме transaction pooling это конец текущих транзакций. Новые клиенты в это время ждут, а не получают ошибку. Если после RELOAD в конфиге изменился адрес базы, новые соединения пойдут на новый сервер.

			-- в консоли PgBouncer: ждём текущие транзакции и держим новых клиентов
PAUSE;
-- перечитываем pgbouncer.ini, где host указывает на новый кластер
RELOAD;
-- отпускаем клиентов на новый кластер
RESUME;

		

Этой связкой пользовались в GitLab: дождаться нулевого лага, поставить паузу, переключить, снять паузу.

План отката: продумать до старта

Откат придумывают до окна обслуживания, потому что от него зависит выбор режима. Правила возврата после pg_upgrade по документации:

после --check, --copy и --clone старый кластер не изменился, его можно просто запустить;после --link без запуска нового кластера достаточно убрать суффикс .old у файла global/pg_control;после --link с запуском нового кластера или после --swap, когда старый кластер объявлен небезопасным, поможет только бэкап.

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

Если в кластере Patroni и реплики

Физическая репликация между разными мажорными версиями не работает, поэтому «катящееся» мажорное обновление по репликам невозможно. Эту ошибку разобрали в июле 2026 года в рассылке pgsql-admin.

В документации Patroni порядок такой:

  1. Остановить Patroni.
  2. Обновить бинарники и выполнить pg_upgrade на лидере.
  3. Обновить patroni.yml.
  4. Удалить ключ initialize из DCS или выполнить patronictl remove, потому что у нового кластера другой системный идентификатор.
  5. Запустить Patroni на лидере.
  6. На репликах обновить бинарники и patroni.yml, очистить каталог данных.
  7. Запустить Patroni на репликах и дождаться копирования данных.

Ускорить пересборку реплик можно через rsync, но только если лидер обновлялся в режиме --link. Самым безопасным Patroni называет полное копирование данных своими средствами.

Итого

Обновление PostgreSQL с минимальным простоем решается не флагом pg_upgrade, а подготовкой. Если допустимо окно в несколько минут и кластер один, хватит pg_upgrade в режиме --clone или --link после репетиции на копии прода. Если простой должен уложиться в секунды, нужна логическая репликация с ручной синхронизацией последовательностей и паузой в PgBouncer. В обоих случаях первым пишут план отката: он определяет, какой режим вы можете себе позволить.