Акция Панель управления Ispmanager для VDS — первый месяц бесплатно
до 31.07.2026 Подробнее
Выберите продукт

PostgreSQL: перенос PGDATA на NVMe/LVM и апгрейд через pg_upgrade с минимальным простоем (systemd, rsync)

Практичный сценарий для Linux с systemd: перенос PGDATA PostgreSQL на NVMe/LVM, подготовка systemd к новому пути и апгрейд кластера через pg_upgrade. Плюс rsync pre-sync, чтобы сократить финальное окно простоя и упростить откат.
PostgreSQL: перенос PGDATA на NVMe/LVM и апгрейд через pg_upgrade с минимальным простоем (systemd, rsync)

В реальных проектах апгрейд PostgreSQL часто «цепляет» сразу несколько задач: вы хотите ускорить диск, аккуратно переехать на NVMe, навести порядок с LVM-томами и при этом обновить версию Postgres, не раздувая окно простоя. Ниже — рабочий админский сценарий: перенос PGDATA (datadir) на NVMe/LVM, подготовка systemd к новому пути, затем апгрейд через pg_upgrade и использование rsync для сокращения финального downtime.

Статья рассчитана на Linux с systemd (Debian/Ubuntu/RHEL-подобные). Пути и имена юнитов могут отличаться, но логика одинаковая.

Что именно мы делаем и почему так

Есть два основных подхода к обновлению PostgreSQL:

  • Логическая миграция (pg_dump/restore, logical replication): обычно дольше, зато гибче по трансформациям и по «разводу» окружений.

  • Физический апгрейд через pg_upgrade: быстро по данным, но требует совместимых условий и дисциплины по расширениям/библиотекам.

Если задача — минимизировать простой, pg_upgrade почти всегда выигрывает. А перенос datadir на NVMe/LVM лучше организовать так, чтобы:

  • тома и монтирование были готовы заранее;

  • сервис PostgreSQL однозначно указывал на новый путь (через параметры запуска/юнит), а не «где-то в одном из конфигов»;

  • финальная остановка была короткой: большую часть данных перегоняем заранее через rsync, а в окно простоя — только дельту.

Предварительные проверки: версия, место, расширения

Перед любыми переносами соберите вводные:

  • текущая версия PostgreSQL и целевая версия (например, 14 → 16);

  • фактический размер PGDATA (и отдельно объём pg_wal, если он вынесен/разросся);

  • список расширений и их наличие для новой версии (особенно postgis, timescaledb, сторонние FDW).

psql -U postgres -c "SELECT version();"
psql -U postgres -c "SHOW data_directory;"
psql -U postgres -c "SELECT extname, extversion FROM pg_extension ORDER BY 1;"
du -sh /var/lib/postgresql

Важно: pg_upgrade апгрейдит конкретный кластер (конкретный PGDATA). Если у вас несколько кластеров/инстансов на хосте, планируйте и проверяйте каждый отдельно.

Если вы не уверены, что текущий сервер «впишется» в новое железо/конфигурацию, полезно заранее прочитать про эксплуатационные нюансы: тюнинг autovacuum и индексов в PostgreSQL (часто именно он влияет на скорость пост-апгрейдного «прогрева»).

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Для больших баз важно заранее оценить I/O и запас по месту. Если вы переезжаете на новый сервер под PostgreSQL, чаще всего практичнее брать отдельный VDS под базу и не смешивать её с вебом и очередями, чтобы предсказуемо управлять диском и нагрузкой.

Схема томов LVM и точка монтирования для PGDATA PostgreSQL

Готовим NVMe + LVM под новый datadir

Почему «NVMe + LVM» — хорошая связка: NVMe даёт IOPS/латентность, а LVM упрощает рост тома и обслуживание. При этом LVM не заменяет бэкапы и репликацию.

Разметка и создание тома

Предположим, NVMe виден как /dev/nvme0n1. Создадим PV/VG/LV и файловую систему. Названия подстройте под свои стандарты.

lsblk
sudo pvcreate /dev/nvme0n1
sudo vgcreate vg_pg /dev/nvme0n1
sudo lvcreate -n lv_pgdata -L 200G vg_pg
sudo mkfs.ext4 -m 0 -E lazy_itable_init=0,lazy_journal_init=0 /dev/vg_pg/lv_pgdata

Создаём точку монтирования и монтируем:

sudo mkdir -p /pgdata
sudo mount /dev/vg_pg/lv_pgdata /pgdata
sudo chown postgres:postgres /pgdata
sudo chmod 700 /pgdata

Добавьте запись в /etc/fstab (лучше по UUID):

sudo blkid /dev/vg_pg/lv_pgdata
sudoedit /etc/fstab
UUID=REPLACE_ME /pgdata ext4 noatime,nodiratime 0 2

Проверяем, что автомонтирование работает:

sudo umount /pgdata
sudo mount -a
mount | grep /pgdata

Права и SELinux/AppArmor

На SELinux enforcing новый каталог может «молча» ломать запуск PostgreSQL. Идея простая: новый путь должен быть разрешён политикой для postgresql. Конкретные команды зависят от дистрибутива и ваших политик безопасности, поэтому проверку делайте по логам после пробного старта (см. ниже про диагностику через journalctl).

Находим текущий PGDATA и как он задаётся в systemd

Частая причина проблем: каталог перенесли, а точку правды, где сервис берёт datadir, не обновили. В systemd это обычно одно из трёх мест:

  • переменная окружения PGDATA (в env-файле или прямо в юните);

  • параметр -D в ExecStart;

  • дистрибутивные «обёртки» (например, Debian/Ubuntu часто управляют кластерами через pg_ctlcluster).

Сначала выясняем, какой юнит запускает PostgreSQL и что в нём определено:

systemctl status postgresql
systemctl cat postgresql
systemctl show postgresql -p FragmentPath -p DropInPaths

Если юнит «версийный» (часто на RHEL-подобных), проверяйте его:

systemctl status postgresql-14
systemctl cat postgresql-14

Дальше я буду писать «сервис PostgreSQL». Подставляйте ваш юнит: postgresql, postgresql-14, postgresql@14-main и т.д.

Перенос datadir: быстрый pre-sync через rsync

Цель: заранее прогнать почти весь PGDATA на новый NVMe-том, пока PostgreSQL работает. Это не финальная копия (данные меняются), но она сильно уменьшит дельту в момент остановки.

Первый rsync на живой базе

Пример: текущий datadir /var/lib/postgresql/14/main, новый путь /pgdata/14/main.

sudo -u postgres mkdir -p /pgdata/14/main
sudo -u postgres chmod 700 /pgdata/14/main
sudo rsync -aHAX --numeric-ids --delete --info=progress2 /var/lib/postgresql/14/main/ /pgdata/14/main/

Пояснения по флагам:

  • -a сохраняет права/владельца/время (насколько возможно);

  • -HAX помогает сохранить hardlinks и расширенные атрибуты (если они реально используются в вашем окружении);

  • --delete делает приёмник зеркалом источника;

  • слэши в конце путей важны: копируется содержимое каталога, а не сам каталог как вложенный.

Финальный rsync в окно простоя

В момент cutover вы останавливаете PostgreSQL, делаете ещё один rsync (обычно быстро, потому что основная масса уже скопирована), переключаете PGDATA и стартуете сервис.

Cutover: переключаем PGDATA и правим systemd

Ниже — общий подход. Конкретика зависит от того, как ваш дистрибутив задаёт datadir.

Остановка сервиса и финальная синхронизация

sudo systemctl stop postgresql

Убедитесь, что процессов не осталось:

ps aux | grep [p]ostgres

Финальный rsync:

sudo rsync -aHAX --numeric-ids --delete --info=progress2 /var/lib/postgresql/14/main/ /pgdata/14/main/

Переключение путей: вариант через systemd drop-in

Самый безопасный метод — не править пакетные юниты, а сделать drop-in. Создаём его:

sudo systemctl edit postgresql

Дальше содержимое зависит от исходного юнита. Два типовых варианта:

1) Если в юните учитывается PGDATA из окружения:

[Service]
Environment=PGDATA=/pgdata/14/main

2) Если в юните задан ExecStart с -D, нужно переопределить ExecStart полностью (сначала очистить):

[Service]
ExecStart=
ExecStart=/usr/lib/postgresql/14/bin/postgres -D /pgdata/14/main

Применяем изменения и пробуем старт:

sudo systemctl daemon-reload
sudo systemctl start postgresql
sudo systemctl status postgresql

Проверяем, что PostgreSQL действительно использует новый datadir:

sudo -u postgres psql -c "SHOW data_directory;"

Если вы на Debian/Ubuntu с кластерным управлением, нередко правильнее менять datadir средствами кластера/конфигов. Но итоговая проверка всегда одна: SHOW data_directory.

Апгрейд версии через pg_upgrade: быстрый путь

Дальше — про сам pg_upgrade. Практичная схема с минимальным простоем выглядит так:

  1. Ставим новую версию PostgreSQL (бинарники) заранее.

  2. Инициализируем новый пустой кластер на NVMe (новый PGDATA) заранее.

  3. Гоняем pg_upgrade --check заранее, чтобы отловить несовместимости.

  4. В окно простоя: стоп старого сервиса, pg_upgrade, старт нового сервиса.

  5. Post-upgrade: analyze/статистика, контроль логов, контроль расширений.

Подготовка каталогов старого и нового кластера

Удобно держать структуру на NVMe явной:

  • /pgdata/14/main — старый кластер (уже перенесён).

  • /pgdata/16/main — новый кластер под апгрейд.

Создаём каталог для нового кластера:

sudo -u postgres mkdir -p /pgdata/16/main
sudo -u postgres chmod 700 /pgdata/16/main

Инициализируем новый кластер (пути к бинарникам зависят от дистрибутива/пакетов):

sudo -u postgres /usr/lib/postgresql/16/bin/initdb -D /pgdata/16/main

Проверка совместимости: pg_upgrade --check

Запустите проверку до окна работ. Если она падает на расширениях или библиотеках, у вас будет время поставить нужные пакеты под новую версию.

sudo -u postgres /usr/lib/postgresql/16/bin/pg_upgrade --check -d /pgdata/14/main -D /pgdata/16/main -b /usr/lib/postgresql/14/bin -B /usr/lib/postgresql/16/bin

Окно простоя и запуск pg_upgrade

Останавливаем старый кластер и запускаем апгрейд.

sudo systemctl stop postgresql

По умолчанию pg_upgrade старается использовать link-mode (жёсткие ссылки), это быстро, но требует, чтобы старый и новый datadir были на одной файловой системе. Если оба каталога на одном LVM-томе — отлично.

sudo -u postgres /usr/lib/postgresql/16/bin/pg_upgrade -d /pgdata/14/main -D /pgdata/16/main -b /usr/lib/postgresql/14/bin -B /usr/lib/postgresql/16/bin

Если вы хотите более прямолинейный откат ценой времени (копирование данных), используйте --copy:

sudo -u postgres /usr/lib/postgresql/16/bin/pg_upgrade --copy -d /pgdata/14/main -D /pgdata/16/main -b /usr/lib/postgresql/14/bin -B /usr/lib/postgresql/16/bin

Запуск pg_upgrade и проверка совместимости кластера PostgreSQL перед апгрейдом

Post-upgrade: статистика и «прогрев»

pg_upgrade обычно оставляет в рабочем каталоге скрипты наподобие analyze_new_cluster.sh и delete_old_cluster.sh. Прочитайте их перед выполнением и запускайте по месту.

Если отдельного скрипта нет или вы хотите контролируемый запуск — обновите статистику так:

sudo -u postgres /usr/lib/postgresql/16/bin/vacuumdb --all --analyze-in-stages

systemd: корректный запуск нового кластера и защита от ошибок

После апгрейда важно, чтобы systemd стартовал именно новую версию и новый PGDATA, а старый кластер не поднимался случайно.

Переключаем сервис на новый PGDATA

По сути, это повторение идеи с drop-in, но уже для новой версии/нового каталога. Минимальный набор проверок после переключения:

sudo systemctl daemon-reload
sudo systemctl start postgresql
sudo -u postgres psql -c "SHOW data_directory;"
sudo -u postgres psql -c "SELECT version();"

Журналы и быстрые проверки

Если что-то пошло не так, не гадайте — читайте journal:

journalctl -u postgresql -n 200 --no-pager

А ещё проверьте, что у каталога правильный владелец и режим:

sudo ls -ld /pgdata /pgdata/16 /pgdata/16/main

Как уменьшить downtime ещё сильнее

  • Pre-sync через rsync перед cutover (описан выше): фактически это главный рычаг уменьшения простоя при переносе.

  • pg_upgrade --check заранее: ловите несовместимые расширения до окна работ.

  • Новый кластер (initdb) заранее: не тратьте минуты на инициализацию в downtime.

  • Упрощайте структуру datadir на время апгрейда: меньше «магии» с симлинками и нестандартными путями — меньше сюрпризов.

Реальный простой — это не только время pg_upgrade, но и время прогрева кэшей, выполнения analyze и восстановления пула соединений приложения (например, PgBouncer/клиенты).

Если у вас большой парк приложений и соединения важнее всего, заранее продумайте пуллинг и поведение клиентов при рестарте: гайд по PgBouncer и пуллингу соединений помогает убрать часть «последствий» короткого downtime.

План отката: где «точка невозврата»

Rollback нужен всегда. Две практические заметки:

  • В link-mode (жёсткие ссылки) «новый» кластер разделяет файлы со «старым». Поэтому тестировать откат надо заранее в тестовой процедуре: простой «вернуть старый каталог» может быть уже небезопасен после записи в новый кластер.

  • Если вам важен максимально прямой откат, выбирайте --copy и/или заранее делайте подтверждённый бэкап. LVM-снапшот иногда выручает как страховка, но на больших нагрузках может просадить I/O и требует грамотного расчёта размера.

Минимальный здравый вариант: до окна простоя иметь проверенный бэкап и возможность поднять старый сервис с прежним PGDATA. Старый каталог не удаляйте, пока не пройдёте пост-проверки и короткий период наблюдения.

Чек-лист после миграции

  • SHOW data_directory; возвращает новый путь на NVMe.

  • SELECT version(); показывает целевую версию.

  • В journald нет ошибок старта и repeated crash-loop:

journalctl -u postgresql -n 200 --no-pager
  • Права на каталоги корректные, владелец postgres, режимы не «шире», чем нужно.

  • Все расширения грузятся и имеют ожидаемые версии (проверьте pg_extension).

  • Выполнен analyze (скрипт от pg_upgrade или vacuumdb).

Типовые ошибки и быстрые диагнозы

PostgreSQL не стартует после переноса PGDATA

Чаще всего причины:

  • systemd всё ещё смотрит в старый каталог (не обновили PGDATA или ExecStart);

  • не те права/владелец на новом каталоге;

  • том не примонтировался (ошибка в /etc/fstab);

  • SELinux/AppArmor запрещает доступ.

Быстрый путь: journalctl по юниту, затем проверка конфигурации юнита через systemctl cat, и только после успешного старта — контроль SHOW data_directory.

pg_upgrade ругается на несовместимые библиотеки

Почти всегда это расширения. Решение: установить пакеты расширений под новую версию до миграции и повторить pg_upgrade --check.

Итого

Комбинация «NVMe + LVM + pg_upgrade + предварительный rsync» — один из самых практичных способов обновить PostgreSQL и одновременно привести хранение данных в порядок. Ключ к минимальному downtime: максимум подготовительных действий до окна простоя, чёткое понимание, где задаётся datadir в systemd, и обязательный прогон pg_upgrade --check заранее.

Если вы строите хранилище «вдолгую», заранее продумайте, как будете масштабировать объём тома и бэкапы. Для наращивания диска на сервере пригодится инструкция: как увеличить диск на VDS и расширить раздел/ФС.

Поделиться статьей

Вам будет интересно

Debian/Ubuntu: mount: wrong fs type, bad option, bad superblock — как быстро найти и исправить причину OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: mount: wrong fs type, bad option, bad superblock — как быстро найти и исправить причину

Ошибка mount: wrong fs type, bad option, bad superblock в Debian/Ubuntu может означать и простую опечатку в имени раздела, и пробл ...
Debian/Ubuntu: XFS metadata corruption и emergency read-only — пошаговое восстановление OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: XFS metadata corruption и emergency read-only — пошаговое восстановление

Если XFS-раздел внезапно стал доступен только для чтения, а сервер ушёл в emergency mode, главное — не спешить. Разберём безопасны ...
Debian/Ubuntu: как исправить Failed to fetch при apt update OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: как исправить Failed to fetch при apt update

Ошибка Failed to fetch при apt update в Debian и Ubuntu обычно связана не с самим APT, а с DNS, сетью, зеркалом, прокси, временем ...