Меня зовут Николай Литковец, я инженер-разработчик в СберТехе, развиваю СУБД Pangolin — это реляционная СУБД, целевая в Сбере и не только. До недавнего времени у нас было два типа обновлений СУБД: минорное и мажорное. Минорное обновление — быстро и сравнительно просто, мажорное — долго, муторно, со значительными затратами ресурсов сервера. Мы стали думать, можем ли мы где-то обойтись без перехода на мажорные версии? Нашли сценарий, где это было возможным, и через некоторое время у нас появилось минорно-мажорное обновление, которое теперь экономит нам силы и время. На Хабре я рассказал про создание инструмента, который позволил нам устроить эту реформу, как обходили риски, и что нам это дало. Здесь приводится сокращённая версия текста, больше технических деталей — в полной версии статьи на Хабре.
В Platform V Pangolin DB изначально было предусмотрено два сценария обновления:
Раньше мы проводили мажорные обновления в следующих случаях:
В первых трёх случаях мажорные обновления обоснованы и без них не обойтись. А вот изменение системного каталога мы проанализировали подробнее. Добавление новых возможностей в продукт зачастую связано с изменением словаря данных или каталога, что подразумевает мажорное обновление. Здесь мы оказались перед выбором: перестать приносить новшества своевременно, либо заставлять клиентов выполнять сложные и рискованные манипуляции, которые подразумевают мажорные обновления. Обсудили и решили, что нам нужно попробовать что-то ещё.
Системный каталог PostgreSQL — это центральный элемент базы данных. Он содержит метаданные обо всех её объектах: таблицах, индексах, пользователях и так далее. Его изменение может быть необходимо для повышения производительности, безопасности или добавления новых возможностей.
Что именно понимается под изменением системного каталога:
Именно это зачастую требовало обновления с переносом данных. Наши изменения в составе и структуре вышеописанных системных каталогов включали в себя добавление новых объектов или модификацию (удаление) существующих.
Каждая функция, тип и представление имеют уникальный идентификатор OID (Object Identifier). Для перечисленных системных объектов он находится в диапазоне значений от 0 до 16 383 включительно. Внутри этого диапазона есть особенности:
Системный каталог с описанными выше таблицами создаётся в каждой новой базе из шаблонов template0 и template1. Поэтому изменения каталогов требуется делать во всех БД.
При изменении системного каталога обновляется номер его версии, который хранится в файле global/pg_control.
Имена папок в табличных пространствах, создаваемых пользователями, совпадают с версиями системного каталога (PG_<POSTGRES_VERSION>_<CATALOG_VERSION_NO>
).
При мажорном обновлении структура OID сохраняется благодаря созданию и инициализации новых баз данных с помощью команды initdb. Эта команда создаёт все объекты в соответствии с описанными правилами, включая назначение OID. При этом создаётся новый файл global/pg_control
с актуальной версией системного каталога. Для переноса данных в новую базу переименовываются папки в пользовательских табличных пространствах. И создаются жёсткие символьные ссылки на эти пространства для новой БД. Это позволяет сохранить доступ к данным и обеспечить их целостность при обновлении системы.
Наша задача — сделать то же самое в уже существующей базе данных.
Создаём сценарий минорно-мажорного обновления, называем его inplace upgrade. Он состоит из трёх основных частей:
Расскажу о них подробно.
Скрипт inplace_upgrade.sh содержит всю последовательность действий для обновления. Его задача — запустить Pangolin в режиме binary и применить SQL-скрипты обновления для всех баз данных.
После чего СУБД запускается в режиме single: без pg_cron
, autovacuum
, пользовательских бэкендов и других процессов, которые могут помешать работе inplace upgrade. Режим binary используется, чтобы задавать OID новых объектов с системными значениями.
Но как запустить Pangolin с новыми исполняемыми файлами, если версия системного каталога, которая им соответствует, отличается от текущей версии? Для решения этой проблемы мы разработали утилиту update_catalog_version
. С её помощью мы меняем версию системного каталога в начале запуска скрипта.
Cистемные каталоги есть во всех обновляемых базах данных. Нам нужно предварительно получить их список. В скрипте обновления подразумевается запрос всех присутствующих БД. Запускаем БД в обычном режиме, применяем запрос:
SELECT datname FROM pg_database;
Получаем список баз данных. Базы данных или их объекты могут находиться в пользовательских табличных пространствах, завязанных на версию системного каталога. Перед запуском Pangolin нужно переименовать папки пользовательских табличных пространств.
Чтобы не возникло проблем при доступе к БД, нужно сделать это до первого запуска Platform V Pangolin DB при обновлении. Нам требуется получить список всех табличных пространств, переименовать их. И список нужен до обновления исполняемых файлов СУБД.
Мы решили выполнять всё это на работающей базе до обновления. Список пользовательских табличных пространств получаем SQL-запросом:
SELECT pg_tablespace_location(oid) FROM pg_tablespace WHERE spcname not in ('pg_default', 'pg_global')
Так у нас появился ещё один режим работы скрипта. Мы назвали его info. Добавили ещё ряд дополнительных проверок, опишу их ниже.
После обновления версии системного каталога у нас есть доступ ко всем табличным пространствам, и нам известны все обновляемые базы данных. Теперь можно обновлять системные каталоги.
Главная опасность — испортить системный каталог. Если он сломается, то база данных вообще не запустится. Или может перестать работать ранее заявленная функциональность. Мы решили собирать все SQL-скрипты в одну транзакцию. Если где-то произойдёт ошибка, то изменения системного каталога не применятся. Однако ввиду того, что обновлять нужно больше одной базы данных, проблема может возникнуть в любой из них. Тогда получится, что часть баз данных обновится, а часть — нет. Чтобы этого не произошло, мы применяем скрипты обновления ко всем базам данных с последующим откатом. Если в результате не возникло ошибок, то обновляем все БД нашими скриптами.
Вроде бы задача решена. Только как нам убедиться в том, что мы не испортили целостность системных каталогов во всех БД? Используем утилиту pg_dump
. Формируем дамп системных каталогов во всех базах данных после обновления:
pg_dump -h $HOST -p $PORT -U $USER -d $dbname -n pg_catalog
Если ошибок при снятии дампа не возникло, считаем, что системный каталог не повреждён. А чтобы определить, что базы данных изначально были в консистентном состоянии, мы формируем дампы системных каталогов во всех БД до обновления и не начинаем процесс, если где-то возникли ошибки.
Что делать, если мы всё-таки испортили системный каталог в какой-нибудь базе данных? Делаем резервную копию файлов системного каталога во всех БД. С помощью системной функции pg_relation_filepath
узнаём имена файлов, соответствующих таблицам системного каталога, и сохраняем их копию. Если портится системный каталог, то восстанавливаем все его файлы из сохранённых копий.
Процедура восстановления системного каталога довольно сложная, поэтому она вынесена в отдельный режим работы скрипта reset.
Собственно, основной режим его работы мы назвали update. Он существует в двух вариантах:
Отличаются они только тем, что на реплике системный каталог дообновляется не с помощью скриптов обновления, а с помощью репликации.
Итак, у нас получилось три режима работы скрипта inplace_upgrade
.sh:
Далее расскажу, как они работают.
Эта утилита разработана для обновлении версии системного каталога в файле global/pg_control
. Как ранее упоминалось, Pangolin не запустится, если версия системного каталога в его исполняемых файлах не соответствует версии системного каталога в файле global/pg_control
. Для этого утилита при остановленной базе данных считывает файл global/pg_control
, формирует его копию pg_control.bak
, проверяет, что новая версия системного каталога превышает текущую, и устанавливает новую версию системного каталога. Также можно поменять версию системного каталога в обход проверок на случай ручного восстановления версии системного каталога, при исправлении неполадок обновления.
SQL-скрипты создаются для каждой версии СУБД Pangolin, в которой происходят изменения в составе системного каталога.
Обновление системного каталога проходит в два этапа.
Рассмотрим пример обновления с версии 6.3.1 на версию 6.3.4. В этом процессе изменения системного каталога происходят в версиях 6.3.3 и 6.3.4. Это условный сценарий, но он демонстрирует последовательность действий.
Здесь оценивается, можно и нужно ли выполнять обновление. Важно учесть, что в этот период база данных продолжает функционировать, но пользователям ограничен доступ к ней до завершения обновления. Сценарий выполняется как на основном сервере (мастере), так и на репликах.
Сценарий состоит из следующих этапов.
Проверка необходимости обновления: анализ структуры SQL-скриптов, предназначенных для обновления.
Проверка возможности обновления версии системного каталога:
Проверка доступа к SQL-скриптам обновления на чтение:
Формирование списка табличных пространств:
При успешном прохождении всех этапов получаем соответствующее сообщение.
INFO mode finished with Success. Update required.
===================================================
Если все этапы проверок прошли без ошибок, переходим к обновлению.
Есть два сценария обновления:
Выполняется на остановленной СУБД и состоит из следующих этапов.
Проверка необходимости обновления: анализ структуры SQL-скриптов, предназначенных для процесса. Этот шаг повторяет аналогичный этап разведки, где заново считываются версии системного каталога, нуждающиеся в обновлении.
Обновление версии системного каталога в файле global/pg_control
: проводится с использованием утилиты update_catalog_version
. Этот шаг крайне важен, поскольку без него СУБД не сможет запуститься.
Формирование общего SQL-скрипта обновления в соответствии с обновляемыми версиями.
Обновление названий папок в пользовательских табличных пространствах. Без этого шага после обновления нельзя получить доступ к пользовательским табличным пространствам.
Создание дампов системного каталога во всех обновляемых БД с помощью утилиты pg_dump
. Эти дампы нужны для анализа внесенных изменений в системный каталог (если возникнет такая потребность) и проверки его целостности.
Создание резервной копии системных каталогов для всех обновляемых баз данных. Запускаем СУБД, формируем список файлов, соответствующих таблицам системного каталога для всех баз данных. И эти файлы сохраняются в резервную копию. Также создаётся копия файла global/pg_control
с версией системного каталога до обновления, чтобы сохранить указатель на текущий чекпоинт.
Кроме того, мы сохраняем резервные копии папок pg_wal
, pg_replslot
и файла global/pg_control
. Это нужно для полного отката изменений в случае возникновения проблем на дальнейших этапах.
Проверка обновления системных каталогов. СУБД запускается в режиме binary upgrade
(аналогичном тому, что используется утилитой pg_upgrade
для переноса объектов системного каталога с сохранением OID). Перед обновлением все необходимые скрипты применяются в тестовом режиме в одной транзакции с последующим откатом для всех баз данных.
Обновление системных каталогов. СУБД запускается в режиме binary upgrade. После чего применяется скрипт обновления к одной базе данных.
Затем СУБД останавливается и запускается в обычном режиме для создания дампа системного каталога после обновления одной базы данных. Этот дамп проверяет целостность системного каталога и если где-то что-то повреждено, выдаёт ошибку.
Эта процедура повторяется для всех обновляемых баз данных. После обновления системных каталогах во всех базах данных СУБД останавливается.
Окончание сценария обновления: после успешного обновления всех баз данных сценарий завершает работу.
Важно: при возникновении проблем до этапа «Обновление системных каталогов», автоматически восстанавливается прежняя версия системного каталога и имена пользовательских табличных пространств (и обновления не происходит). Если же проблемы возникают после, то требуется запустить сценарий inplace_upgrade.sh
в режиме восстановления. Опишу его ниже.
Это значительно проще, чем обновление мастера. Само обновление системного каталога происходит в момент синхронизации с мастером после завершения сценария обновления.
Обновление выполняется на остановленной реплике и включает следующие этапы.
Проверка необходимости обновления: анализ структуры SQL-скриптов, предназначенных для обновления.
Обновление версии системного каталога в файле global/pg_control.
Формирование общего SQL-скрипта обновления в соответствии с обновляемыми версиями. Сам скрипт для обновления не требуется, но он проверяет целостность всего сценария обновления.
Обновление названий папок в пользовательских табличных пространствах.
Обновление мастера и реплики может происходить параллельно. В завершение обновления требуется восстановить синхронизацию между ними и убедиться в том, что она полностью завершилась. Это нужно, чтобы изменения системного каталога, сделанные на мастере, применились на реплике. Лишь после этого можно предоставить пользователям доступ к СУБД.
Если при обновлении возникли конфликты, препятствующие запуску СУБД или нарушившие целостность системного каталога, то нужно откатить системный каталог к состоянию до обновления. Откат выполняется до предоставления пользователям доступа к СУБД.
Откат изменений подразумевает запуск сценария восстановления на остановленной СУБД и включает следующие шаги.
Восстановление из резервной копии:
global/pg_control
;Восстановление структуры системного каталога: удаляются те директории, которые появились после обновления СУБД и отсутствовали ранее.
Запуск СУБД и создание дампов системного каталога для всех баз данных. Эти дампы проверяют корректность отката изменений системного каталога и его целостность.
После выполнения этих шагов восстановление считается успешным.
RESET mode finished with Success.
===================================================
Внедрение inplace upgrade значительно сократило время обновления Platform V Pangolin DB в сценарии с изменением данных системного каталога. Если мажорное обновление 100 ГБ базы занимает 65 минут, то обновление по новому сценарию выполняется за 23 минуты. К тому же он позволяет обновлять продукт проще и с меньшими рисками.
Собираемся развивать инструмент и адаптировать его для новых задач. Сейчас мы видим, что пользователи приходят с вопросами по содержанию логов инструмента — хочется сделать их более удобочитаемыми. И есть планы по небольшим доработкам скриптов обновления — о том, как здесь всё работает, писала моя коллега-DevOps в своей статье на Хабре. О развитии этого направления постараемся писать ещё статьи, как минимум будем рассказывать об этом в сообществе нашей команды (присоединяйтесь, будем рады!).