lenec ru

← все посты

Postgres 16 → 17: чек-лист апгрейда без сюрпризов

10K

Апгрейд мажорной версии Postgres — отдельный жанр работы. Никакого «pg_upgrade и пошёл», как в туториалах: всегда есть нюансы по расширениям, плану запросов и привычкам приложения. Я в этом году протащила два кластера с 16 на 17, и хочу собрать чек-лист, по которому это делать спокойно.

Контекст: managed Postgres у меня нет, всё руками на VPS — самый интересный режим, потому что нет никого, кто бы прибрался за тебя. Если у вас RDS, Yandex Managed или Selectel Cloud Database, часть пунктов отпадает: кнопкой апгрейд там делается за 10 минут с короткой паузой. Но даже там стоит пробежаться по совместимости.

Что новое в 17 важно для типичного приложения

Длинного списка не будет, рассказываю, что задело лично нас.

  • Логическая репликация умеет резервировать seq при failover. Если ты используешь логику для подписки между шардами или на staging — стало ощутимо удобнее.
  • VACUUM поведение и memory limit отдельным maintenance_io_concurrency. На больших таблицах с частыми обновлениями автоваккум стал ровнее.
  • Новые JSON-функции. Появились JSON_TABLE и компания. Если у тебя много jsonb и раньше парсил руками — есть смысл переписать.
  • SLRU теперь в shared buffers. Знакомая многим стена «multixact members SLRU» больше не упирается в захардкоженные мегабайты, а параметризуется. Снимает старый источник латентности на high-concurrency базах.

Из неприятного: некоторые extension-ы выпускают совместимые версии не сразу. Перед апгрейдом я ходила и проверяла, что нужные мне расширения уже собраны под 17.

Шаг 0: бэкап и план отката

Никакой апгрейд не начинается без свежего полного бэкапа базы и проверенного плана отката. У меня план простой: pg_dumpall --globals-only + pg_dump -Fc по каждой базе, складываем в S3 (Selectel Object Storage). Если апгрейд через pg_upgrade упал и оставил битые datadirs — мы за час поднимаем всё с нуля на чистом 17 из dump.

pg_dumpall --globals-only -U postgres > /var/backups/pg/globals-$(date +%F).sql
for db in $(psql -U postgres -tAc "select datname from pg_database where datistemplate = false"); do
  pg_dump -U postgres -Fc "$db" -f "/var/backups/pg/${db}-$(date +%F).dump"
done

Обязательно проверь, что dump читается: pg_restore -l /var/backups/pg/-...dump | head. Молча испорченный backup — самая обидная штука.

Шаг 1: проверка расширений

Самая частая причина зависнуть на апгрейде. На 16 у нас стояло: pg_stat_statements, pgcrypto, pgvector, pg_trgm, postgis, citus (на одном кластере). С первыми четырьмя проблем нет — они идут с дистрибутивом. postgis и citus — отдельная история: их версия должна быть собрана под 17.

select * from pg_available_extensions where installed_version is not null;

Сравни с pgxn.org и репозиторием PGDG, есть ли пакет {extension}-postgresql-17. Если пакета нет — апгрейд откладывается, иначе после него половина приложения отвалится.

Шаг 2: pg_upgrade --check

Это сухой прогон: он не трогает данные, но честно сообщит, что не нравится.

sudo -u postgres /usr/lib/postgresql/17/bin/pg_upgrade \
  --old-datadir /var/lib/postgresql/16/main \
  --new-datadir /var/lib/postgresql/17/main \
  --old-bindir  /usr/lib/postgresql/16/bin \
  --new-bindir  /usr/lib/postgresql/17/bin \
  --check

Если упало — читай ~postgres/loadable_libraries.txt: там список extension-ов, которые pg_upgrade считает неподходящими. Часто достаточно проставить нужные пакеты PGDG.

Шаг 3: реальный апгрейд

Перед командой остановись и проверь:

  • Приложение либо отключено, либо переключено в read-only.
  • Все коннекты от воркеров и cron закрыты. Ловятся pg_stat_activity.
  • На диске есть свободное место для второго datadir. С опцией --link места нужно мало (hard link), без неё — практически второй полный размер базы.

Команда без --check, но с --link — самое быстрое:

sudo -u postgres /usr/lib/postgresql/17/bin/pg_upgrade \
  --old-datadir /var/lib/postgresql/16/main \
  --new-datadir /var/lib/postgresql/17/main \
  --old-bindir  /usr/lib/postgresql/16/bin \
  --new-bindir  /usr/lib/postgresql/17/bin \
  --link \
  --jobs=4

--link переиспользует datafiles через жёсткие ссылки. Это значит: после апгрейда ты не сможешь откатиться обратно на 16 на тех же данных. Поэтому бэкап выше — обязательный шаг.

Шаг 4: пост-апгрейд

Сразу после успешного pg_upgrade запускаем analyze_new_cluster.sh, который скрипт оставит в текущей директории. Это нужно, чтобы план запросов знал актуальные статистики.

sudo -u postgres ./analyze_new_cluster.sh

На большом кластере имеет смысл запустить vacuumdb --all --analyze-in-stages -j 4 — он считает статистику в три прохода, от грубой к точной, и приложение быстрее получает адекватные планы.

Шаг 5: postgresql.conf

Конфиг в новой версии часто отличается параметрами. Я делаю так: оставляю старый postgresql.conf и беру шаблон от 17, потом руками переношу свои значения. Параметры, на которые стоит обратить внимание в 17:

  • maintenance_io_concurrency — теперь имеет смысл задрать на SSD-сторадж.
  • multixact_offsets_buffers, multixact_members_buffers и компания — это те самые SLRU из shared buffers. Если на 16 ты упирался в их размер — ставь явно, не оставляй default.
  • autovacuum_naptime и autovacuum_vacuum_scale_factor — мейнтейнс на 17 чуть быстрее, можно начать с тех же значений и потом подкручивать.

Шаг 6: совместимость драйверов

Это про клиентскую сторону. Драйверы pg для Node, psycopg2/psycopg3 для Python, JDBC — все работают с 17 без отдельных обновлений, протокол тот же. А вот пулеры на старых версиях иногда удивляют:

  • PgBouncer 1.18 и старше может не поддерживать новые типы протокола. Проверь, что у тебя версия от 1.21 или выше.
  • Patroni — поднимай до текущей stable, ранние версии не понимают новый pg_basebackup.
  • Connection-pooler от Yandex Managed и других managed-сервисов обновляется автоматически, тут можно не волноваться.

Шаг 7: smoke-тест

После старта 17 я запускаю проверочный набор:

  1. SELECT version(); — что вообще работает.
  2. Несколько типичных запросов из приложения, чтобы убедиться, что планы остались адекватные.
  3. EXPLAIN ANALYZE по двум-трём тяжёлым запросам. Если план сильно изменился к худшему — добавляем недостающую статистику или индексы.
  4. Прогон pg_stat_statements через сутки и сравнение топ-20 запросов с тем, что было на 16. Иногда после апгрейда неожиданно выстреливает другой запрос как самый частый — повод оптимизировать.

Чего я в этот раз не сделала и пожалела

На одном из кластеров я не обновила PgBouncer заодно с Postgres. Постгрес встал, всё ходило, но через два дня всплыл странный таймаут на длинных prepared statements. Версия PgBouncer была от 2022, и на новый протокол она реагировала непредсказуемо. Полчаса потерянного времени плюс рестарт пулера в неудобное время — пустяк, но лучше делать обновление парой.

Что запомнить

  • Снимаем dump перед всем.
  • Проверяем расширения на наличие сборок под 17.
  • Гоняем pg_upgrade --check, потом сам апгрейд с --link ради скорости и места.
  • Сразу запускаем analyze.
  • Перетряхиваем конфиг с оглядкой на новые параметры SLRU и autovacuum.
  • Проверяем версию пулера и драйверов.

На спокойном кластере с базой ~100 ГБ всё это занимает у меня окно в 30–40 минут реального простоя плюс пара часов на проверку планов. Главное — не торопиться и не пропускать пункт с бэкапом, остальные шаги легко доразоблачить, если что-то пойдёт не так.

Комментарии 0

  • Будьте первым, кто оставит комментарий.

Войдите, чтобы оставить комментарий.