Перенос базы 1С с PostgreSQL 18 на 17: восстановление по сети клиентом старшей версии
Иван Недомолков · Жарияланды:
Задача звучит так: база 1С живёт на сервере с PostgreSQL 18, а поднять её надо на сервере с PostgreSQL 17. Приёмник обновлять нельзя или некогда. Делаешь выгрузку pg_dump, несёшь на приёмник, запускаешь там pg_restore и получаешь отказ: утилита семнадцатой версии архив восемнадцатой читать не станет.
Решение, которое у нас сработало на двух базах 1С: восстановление запускается на источнике, утилитой версии 18, а ключ -h смотрит на сервер 17 по сети. Ошибок ноль, у большой базы 11 306 таблиц и 26 236 индексов совпали с источником.
Ниже методика по порядку: какие пути не годятся, как подготовить приёмник, какие команды запускать, чем принимать результат и как не ошибиться с окном работ. В конце случай, с которого всё началось, потому что сервер-приёмник до переноса успел лечь сам.
Четыре пути и какой из них рабочий
| Путь | Что будет при 18 → 17 |
|---|---|
| Физическая копия каталога данных (базовая копия кластера) | не поднимется: каталог данных версии 18 сервер 17 не запускает, это верно для любых мажорных версий |
pg_dump 18 → файл → pg_restore 17 на приёмнике | отказ на чтении архива: формат записан версией 18 |
| Обновить приёмник до 18 | работает, но это отдельный проект, а у нас не было на него ни права, ни окна |
pg_dump 18 → каталог → pg_restore 18 на источнике, -h на сервер 17 | работает, проверено на двух базах |
Документация pg_dump прямо говорит, что загрузка выгрузки в сервер более старой мажорной версии не гарантируется. Мы это не оспариваем. Последний путь работает по конкретной причине, и её стоит понимать до того, как нести его на продуктив.
Почему клиент старшей версии справляется
Тут несовместимы две разные вещи, и их полезно развести.
Формат архива. Собственный формат pg_dump (одним файлом или каталогом) содержит номер версии формата. pg_restore младшей версии, встретив архив новее себя, отказывается его разбирать. Это ограничение утилиты, до сервера дело не доходит.
Протокол и команды. Когда pg_restore работает с живым сервером, архив он серверу не передаёт. Он читает его сам и шлёт серверу обычные SQL-команды: создать таблицу, залить строки через COPY, построить индекс. Протокол клиент-сервер у современных версий общий, а команды создания таблиц и индексов у соседних версий совпадают.
Выходит, что архив должен читать клиент той же версии, что его писал, а сервер может быть младше. Поэтому pg_restore 18 и запускается там, где он уже установлен, то есть на источнике.
Границу этого приёма стоит знать заранее. Команды в архив записал pg_dump 18, и если в схеме встретится то, чего семнадцатая версия не знает, восстановление на этом объекте упадёт. Схема базы 1С простая: таблицы, индексы и типы из расширений сборки PostgreSQL для 1С. У нас сборка для 1С стояла на обеих сторонах, поэтому всё прошло. База с хранимыми процедурами, сторонними расширениями и свежими типами данных может повести себя иначе. И проверен приём на одной паре, 18 в 17: на разрыве в две-три версии растёт шанс, что разойдутся уже сами команды.
Подготовка приёмника
Сервер 17 должен пустить к себе источник по сети. Две правки, после них перечитать конфигурацию или перезапустить службу (если сервер здоров, про нездоровый ниже):
# postgresql.conf на приёмнике
listen_addresses = 'localhost,<адрес приёмника>'
# pg_hba.conf на приёмнике: одна строка ровно под сервер-источник
host all all <адрес источника>/32 scram-sha-256
Наружу, в интернет, приёмник при этом не выставляется. Связь проверяется консольным клиентом с источника: подключился psql -h <адрес приёмника> -U postgres, значит можно переносить.
Базу на приёмнике создаём заново, с явной кодировкой и локалью, от шаблона template0, иначе локаль возьмётся из шаблона по умолчанию:
DROP DATABASE IF EXISTS "base" WITH (FORCE);
CREATE DATABASE "base"
ENCODING 'UTF8'
LC_COLLATE 'ru_RU.UTF-8'
LC_CTYPE 'ru_RU.UTF-8'
TEMPLATE template0;
У нас источник работал с однобайтовой локалью Windows, приёмник получил UTF-8. Перенос это не остановило, но сортировка и сравнение строк в этих локалях различаются, и проверять такое надо отчётами и отборами в самой 1С. Мы этого не делали, так что при смене локали закладывайте прикладную проверку в план.
Выгрузка и загрузка
Обе команды выполняются на источнике (у нас Windows). Каталожный формат -Fd выбран потому, что только он параллелится на обеих стадиях:
# выгрузка локально, четыре потока
pg_dump -h localhost -U postgres -Fd -j 4 -f C:\pgmig\base_dir base
# загрузка: утилита версии 18 здесь, сервер версии 17 там
pg_restore -h <адрес приёмника> -U postgres -d base -j 4 --no-acl C:\pgmig\base_dir
Что здесь важно:
pg_dumpиpg_restoreберутся из каталогаbinустановки 18. Если вPATHокажется другая версия, результат будет не тот, который вы ждёте.--no-aclотбрасывает выдачи прав ролей источника. На приёмнике свои роли, чужие гранты там не нужны.- Пароль передаётся через файл паролей PostgreSQL или переменную
PGPASSWORDна время сеанса. В тексте скрипта его быть не должно, почему, видно из истории в конце. - Место под выгрузку на источнике прикиньте заранее. У нас каталог большой базы занял 14,4 ГБ при базе на 19 ГБ: сжатие есть, но не в разы.
Запускать отсоединённым
Долгую загрузку мы сначала запустили внутри удалённого сеанса PowerShell. Сеть моргнула, сеанс оборвался, задание упало. Сама pg_restore при этом выжила и доработала до конца, пропали только код возврата и вывод.
В соседнем переезде было наоборот: скачивание, запущенное через Start-Process в Invoke-Command, умерло ровно в момент закрытия сессии WinRM (разбор в статье Перенос 1С на Linux и PostgreSQL: восемь ловушек). От чего зависит исход, мы до конца не знаем, поэтому на удачу не полагаемся: долгий перенос запускается через планировщик заданий, где процессом владеет служба, а вывод пишется в файл.
Приёмка: сверка вместо кода возврата
Код возврата говорит, что процесс не упал. Если сеанс оборвался, у вас нет даже его. Надёжнее сверить количества на обеих сторонах одним и тем же запросом в одном и том же psql:
-- выполнить в базе на источнике и в базе на приёмнике, сравнить три числа
SELECT
(SELECT count(*) FROM pg_tables WHERE schemaname = 'public') AS tablitsy,
(SELECT count(*) FROM pg_indexes WHERE schemaname = 'public') AS indeksy,
pg_size_pretty(pg_database_size(current_database())) AS obem;
Считать разными инструментами на двух сторонах хуже, чем не считать: расхождение может дать сам способ подсчёта.
Наш результат:
| Объём до | Объём после | Таблиц | Индексов | |
|---|---|---|---|---|
| База 1 | 19 ГБ | 17 ГБ | 11 306 | 26 236 |
| База 2 | 956 МБ | 899 МБ | 7 989 | не сверяли |
Почему число индексов весомее числа таблиц: pg_restore строит индексы после загрузки данных. Если индексов столько же, сколько в источнике, последний этап дошёл до конца. Строки по таблицам мы не пересчитывали, и для большой базы стоит сделать это хотя бы по десятку крупнейших таблиц.
Два косвенных признака, что структура полная. У первой базы 2,32 индекса на таблицу, для конфигурации 1С это обычное соотношение. У второй около 112 КБ на таблицу: маленькая база с полной структурой, тоже типично для 1С.
Почему база стала меньше
Первая база похудела на 2 ГБ, это минус 10,5 %. Вторая на 6 %. Данные на месте: восстановленная база собрана заново и не несёт раздувания, которое годами копится от мёртвых версий строк (механизм MVCC и autovacuum мы разбирали в статье PostgreSQL для 1С: настройка и обслуживание).
Усадка в несколько процентов ожидаема, пугаться не надо. Но и доказательством полноты она не служит: потеря данных тоже уменьшает объём. Полноту доказывает сверка выше. Повод разбираться появится, если результат вышел больше источника.
Окно работ считают по одной таблице
В первой базе хранилище двоичных данных заняло 12 ГБ из 17, то есть 70,6 %. Остальные 11 305 таблиц вместе весят около 5 ГБ.
Параллельная загрузка раздаёт потокам объекты целиком: одну таблицу грузит один поток. Четыре потока делят между собой оставшиеся 29,4 % объёма, а главную таблицу по-прежнему тянет один. Ускорения в четыре раза на такой структуре не получится по построению. Реальное время переноса мы не замеряли, поэтому цифры окна не даём, но считать его надо от самой большой таблицы, а не от общего объёма базы.
Какая таблица у вас главная, видно до всякого переноса:
-- десять крупнейших таблиц базы вместе с индексами и TOAST
SELECT
c.relname AS tablitsa,
pg_size_pretty(pg_total_relation_size(c.oid)) AS obem
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname = 'public'
AND c.relkind = 'r'
ORDER BY pg_total_relation_size(c.oid) DESC
LIMIT 10;
Если смотреть это глазами 1С, а не по именам таблиц СУБД, то же самое показывает Карта объёмов базы 1С: она раскладывает объём по объектам метаданных. Почему наверху обычно оказывается не то, чего ждёшь, разобрано в статье Что растёт в базе 1С помимо документов.
С чего всё началось: сервер лёг от собственной резервной копии
Перенос понадобился не от хорошей жизни. На Linux-сервере с PostgreSQL 17 диск на 97 ГБ заполнился до конца: кто-то снял полную базовую копию кластера и положил её на тот же системный диск, где лежали данные (27 ГБ). СУБД не смогла дописать контрольную точку, упала и ушла в цикл перезапусков.
Штатный скрипт резервного копирования этого сервера оказался той же ловушкой, только заложенной заранее. Он делает полную базовую копию и тут же пакует её в архив, всё на тот же диск:
данные 27 ГБ + базовая копия 27 ГБ + архив копии 27 ГБ = 81 ГБ на диске 97 ГБ
83 % диска занято на время копирования, без журналов и самой системы. Переполнение при такой схеме вопрос номера запуска, и считается это умножением до внедрения. В том же скрипте лежал пароль суперпользователя СУБД открытым текстом, то есть один файл был и причиной аварии, и местом утечки. Проверить свои пароли СУБД можно Аудитом паролей СУБД.
Лечение выглядело так:
# место: удалить осиротевшую копию (20 ГБ) и ужать системный журнал
rm -rf /var/lib/postgresql/backups/<каталог копии>
journalctl --vacuum-size=200M
# снять зависшую службу и поднять заново
systemctl kill -s SIGKILL postgresql@17-main
systemctl reset-failed postgresql@17-main
systemctl start postgresql@17-main
После старта PostgreSQL доиграл журнал сам, свободного стало 26 ГБ. Два урока отсюда.
Сначала место, потом перезапуск. Службу перезапустили ради правки pg_hba.conf раньше, чем освободили диск, и процесс завис в штатной остановке. Время остановки у службы ограничено не было, так что висела она до принудительного снятия. Проверьте свою: systemctl cat postgresql@17-main (версию и имя кластера подставьте свои), строка TimeoutStopSec. Аварийное завершение PostgreSQL как раз безопасно, журнал доиграется при старте. Опасна зависшая остановка.
Сервер СУБД держите чистым. На той же машине стоял сервер 1С с соединениями к обеим базам. Его остановили и сняли с автозапуска. Пока нагрузка на диск и память была смешанной, разделить её в диагностике было нечем.
Там же лежали две неудачно залитые базы от прежних заходов: одна восстановилась частично, на 5,3 ГБ из 19, вторая весила 10 МБ. Это ровно то, что получается, если перезапустить загрузку поверх той, что ещё идёт. После любого обрыва сначала сверка на приёмнике, потом решение.
Что осталось непроверенным
- Время выгрузки и загрузки не замерено.
- Приём проверен на одной паре версий, 18 в 17.
- Смена локали с однобайтовой Windows на UTF-8 не проверена отчётами 1С.
- Длительность простоя во время аварии не зафиксирована.
- Схема резервного копирования на момент разбора не исправлена. План: выгрузка потоком во внешнее хранилище либо отдельный диск и оповещение о свободном месте.
Если переезд впереди
Перенос между версиями PostgreSQL обычно идёт рядом с большим переездом, и у него свои ловушки. Что проверить в самой базе 1С до смены СУБД, разобрано в статье Миграция 1С на PostgreSQL: проверка базы до переезда и собрано в обработку Проверка базы перед миграцией на PostgreSQL. Настройки сервера на новом месте проверяет Чек-ап СУБД под 1С.
Если нужно, чтобы перенос, резервное копирование и мониторинг места сделали за вас, это наша работа по обслуживанию баз 1С.