PostgreSQL 17: уже можно просто делать бекапы и перестать страдать?
Так исторически сложилось, что задача организации простого и понятного резервного копирования в мире PostgreSQL до сих пор не решена. Есть набор комьюнити утилит, у каждой из которых есть некие плюсы, но всегда в нагрузку будет прорва минусов (тут нет инкрементных копий, там нет внятного расписания, это может только весь сервер вместо конкретной базы увозить и так далее). Да, есть тяжёловесный энтерпрайзный софт за много денег, зачастую требующий странного и работающий по какой-то своей логике, но это тоже не панацея. А вот чтобы просто и понятно, без головных болей организовать прозрачный процесс банального бекапа с инкрементами, работающим расписанием и восстановления только того что надо — вот такого нет.
Но буквально на днях вышел PostgreSQL 17 и может там что-то изменилось? И да, и нет. Та самая мана небесная в виде pg_awesome_backup_tool так и не появилась, однако в релиз попал механизм walsummarizer, который обещает нативно отслеживать изменения в файлах баз данных, что позволит делать инкрементальные бекапы нативно и без лишних приседаний.
А чтобы не рассматривать новичка в вакууме, будем сравнивать его с ptrack — нашей (Postgres Professional) разработкой, которую наши любимые конкуренты уже расхватали в свои продукты и продают их как уникальнейшие решения.
Вводные данные
Не будем тратить время на пересказывание в тысячный раз всем известных истин о том что такое инкрементальный бекап, чем он лучше дифференциального и кто важнее в гонке RPO/RTO. Нам важен лишь факт необходимости иметь механизм для отслеживания изменений произошедших с нашей базой данных от момента создания последней точки бекапа, до момента в котором нам захотелось создать инкрементальную к ней точку. Вариантов как это делать громадьё: от вычитывания WAL, до отслеживания изменившихся секторов на дисках с вычитыванием из снапшота, поэтому переходим к нашим проектам на сегодня.
Дано
Из общих черт обоих инструментов: имеют открытый исходный код и отслеживание изменений в файлах данных основного слоя данных (main fork) и слое карты видимости (vm). А вот дальше начинаются отличия.
walsummarizer — проект Роберта Хааса, который был закомичен в мастер ветку ванильного постгреса в 2023 году. Входит в состав PostgreSQL17 и не поддерживается младшими версиями.
Как сказано выше, умеет отслеживать только основной слой данных и vm. Запускает отдельный процесс в ОС, работая параллельно с postmaster, как отдельный компонент. Основной принцип работы: перечитывает WAL файлы и пишет данные в отдельную папку pg_wal/summaries/. Подробнее разберём чуть ниже.
Соответственно, отмечает только изменённые страницы и ничего лишнего. В данный момент интегрирован исключительно с pg_basebackup.
ptrack — разработан Postgres Professional в 2017 году. Позже, в 2020 году, на гитхаб выложена версия 2.0, где с тех пор регулярно обновляется. В отличие от walsummarizer поддерживает версии постгреса начиная с PostgreSQL 11, но поскольку не включен в ваниллу, требует патча в ядро. После чего надо будет собрать постгрес уже с ptrack.
Отслеживает не только основной слой данных и vm, но и изменения в карте свободного пространства (FSM). И, чисто технически, архитектура ptrack позволяет допилить его для отслеживания изменений в любых файлах постгреса, если будет такая необходимость. Подробности, тоже, чуть ниже.
Из других отличий стоит отметить что ptrack не запускает отдельный процесс, а работает прямо в postmaster. И, что важно, ничего дополнительно не читает с дисков и не создаёт какой бы то ни было заметной нагрузки на сервер. Платить за это приходится определённым уровнем неточности, но в положительную сторону. То есть ptrack может отметить изменёнными больше данных, чем было на самом деле. Но, главное, что не меньше т.е. консистентность не пострадает.
Ну и само собой ptrack поддержан во всей широте наших СУБД, от Standart до Enterprise редакций, включая все их фичи (CFS, Shardman и т.д.), начиная с v11.
Установка и настройка
В принципе, никаких сложностей тут нет (если вопрос пересборки постгреса не вызывает затруднений) и обе утилиты требуют указать несколько простейших параметров в обычном postgresql.conf
Для walsummarizer достаточно указать:
wal_level = «replica # или «logical»
summarize_wal = on;
У ptrack немного по другому:
wal_level = «replica # Можно использовать любой. Но replica или logical предпочтительнее
ptrack.map_size = 1000 # размер в МБ. Не будет меняться в течении работы
shared_preload_libraries = «ptrack»
После внесения изменений, в случае walsumarizer достаточно перечитать файл конфигурации, в то время как для активации ptrack необходимо перезапустить сервер и сделать CREATE EXTENSION ptrack; для добавления его SQL-функций в постгрес.
Схема работы или как оно работает под капотом
Начнём с Walsummarizer. Работает он довольно бесхитростно.
Есть postmaster, который как-то пишет WAL. Совершенно отдельно от него, walsummarizer читает эти файлы, запускает функцию SummarizeWAL и пишет самари файлы в выбранную директорию. Как видим, очевидные минусы здесь это непредсказуемость запусков и работа в бесконечном цикле без пауз. То есть создаётся небольшая, но нагрузка на CPU и диск.
Теперь копнём чуть ниже, а именно посмотрим как хранятся данные об изменённых страницах. Walsummarizer использует структуру roaring bitmap. Для того чтобы в неё что-то положить, он читает некий интервал LSN из WAL, присваивает ему tli (time line id) и создаёт файл с названием вида start_lsn/stop_lsn, чтобы сразу был понятен интервал. А чтобы итоговые самари файлы не хранились бесконечно, уничтожая ценное дисковое пространство, есть отдельный параметр wal_summary_keep_time. По умолчанию это десять дней.
Также есть набор SQL функций, позволяющий получать информацию из самари файлов. Например, чтобы вынуть информацию в удобочитаемом виде, надо произвести две операции. Во-первых, получить список всех файлов в папке /summaries:
SELECT pg_available_wal_summaries();
pg_available_wal_summaries
----------------------------
(tli, start_lsn, end_lsn) #Это легенда. На всякий случай ;)
(1,0/18000028,0/18000130)
(1,0/19006F20,0/1902B228)
(1,0/19006DE8,0/19006F20)
(1,0/1B010900,0/1C000028)
(1,0/19000028,0/19006DE8)
(1,0/1A000028,0/1B010900)
(1,0/18000130,0/1800FCE8)
(1,0/1800FCE8,0/19000028)
(1,0/1902B228,0/1A000028)
(9 rows)
После чего определяемся с какого LSN нам надо получить информацию об изменённых страницах и запрашиваем их скопировав нужную строку.
SELECT
pg_wal_summary_contents('1','0/19000028','0/19006DE8');
pg_wal_summary_cntents
---------------------------
(relfilenode, reltablespace, reldatabase, relforknumber,
relblocknumber, is_limit_block) #ага, это тоже легенда
(1247,1663,16384,0,14,f)
(1249,1663,16384,0,16,f)
(1259,1663,16384,0,5,f)
(2608,1663,16384,0,3,f)
(1213,1664,0,0,0,f)
(16400,16399,16384,0,0,t)
(16400,16399,16384,2,0,t)
(16400,16399,16384,3,0,t)
(8 rows
В результате имеем увесистых список параметров. Например, довольно интересный is_limit_block указывающий, что данные файлы были удалены если он выставлен в true. Или, что изменилась длинна записи, но это лучше в документации ознакомиться со всеми деталями. В любом случае, эта штука здорово ускоряет бекап.
Схема работы Ptrack выглядит намного проще. При помощи патча ядра, о котором упоминалось в начале, в postmaster добавлено несколько хуков для отлова моментов записи на диск. На схеме отображены только три главных хука, но их больше. Суть в том, что когда postmaster решает записать что-либо на диск, например происходит чекпоинт, активируется определённый хук, который передаёт информация в карту изменённых файлов ptrack_map. Таким образом, в дальнейшем, ptrack может отслеживать вообще любые изменения файлов на дисках, производимые postmaster.
Данные в карте хранятся с помощью фильтра Блума, в котором отмечаются LSN изменённых страниц т.е. это не классический битовых хэш, а хэш-карта изменённых блоков с настраиваемым размером. Кстати, наша рекомендации по размеру это N/1024, где N — объём кластера в мегабайтах.
Кроме страниц, ptrack может отмечать в карте измененные файлы и так называемые батчи (страницы сгруппированные по восемь). Если файл в карте не отмечен, значит все его страницы не менялись и их не надо проверять. То же самое верно для батчей, что здорово ускоряет процесс учета изменений. Однако фильтр Блума, как любая хэш-карта, допускает наличие коллизий, из-за чего у нас есть вероятность ложноположительного срабатывания. То есть случай, когда элемента в множестве нет, но структура данных утверждает что есть. Таким образом мы можем случайно забрать в бекап лишний файл, но точно никогда ничего не пропустим.
И немного про SQL запросы для работы с данными. Например, мы хотим узнать изменения произошедшие с определённого LSN. Пишем запрос:
postgres=# SELECT * FROM ptrack_get_pagemapset('0/185C8C0');
path | pagecount | pagemap
----------------|-----------|-------------------------
base/16384/1255 | 3 | \x00100000050000000000
base/16384/2674 | 3 | \x00000000100000000000
base/16384/2691 | 1 | \x00000000000000000000
base/16384/2608 | 1 | \x00040000000000000000
base/16384/2690 | 1 | \x00040000000000000000
В ответе для каждого файла мы получаем количество изменённых страниц и битовую карту этих страниц внутри pagemap. Из-за необходимости делать итерацию по номерам страниц, в начале процесса бекапа ptrack pg_probackup немного задумывается чтобы итерироваться по всем файлам в PGDATA и по их номерам страниц, но ничего криминального в плане задержек.
Немного практики с pg_basebackup и walsummarizer
Давайте же перейдём от теории к ежедневной рутине и попробуем сделать бекапы.
Для начала полный бекап:
$pg_basebackup -c fast -D backups/full --tablespacemapping==
Ничего необычного, просто бекап в директорию backups/full в несжатом формате. Единственное, что стоит отменить, это использование параметра --tablespacemapping, чтобы бекап табличных пространств не остался в тех же папках, где они были созданы. Но если вы их не используете, то смело пропускайте.
Теперь переходим к интересному. С приходом walsummarizer инкрементные бекапы появились и в pg_basebackup, за что спасибо всё тому же Роберту Хаасу. Инкременты можно делать, как заведено, или от полного файла или от другого инкремента. То есть появилась возможность выстраивать полноценные цепочки бекапов.
Команда сделать инкремент от фула выглядит таким образом:
$ pg_basebackup -c fast -D backups/increment1 -i backups/full/backup_manifest --tablespacemapping==
Цепочка инкрементов делается такой командой:
$ pg_basebackup -c fast -D backups/increment2 -i backups/increment1/backup_manifest --tablespacemapping==
В обоих случаях главная часть это команда -i в аргументе, которой указывается путь на backup_manifest того бекапа, от которого мы хотим сделать инкремент. И ещё один нюанс про табличные пространства: для них надо указать новую папку, иначе basebackup будет ругаться. Скажет что директория уже существует, она не пустая и всё удалит. Такая вот увлекательная архитектура была заложена, но как есть. Поэтому придётся выписываться заклинания а-ля new_tblspc_dir1, new_tblspc_dir2, new_tblspc_dir3. Грустно это, но пока вот так.
И самое интересное, это восстановление. С одной стороны всё просто, с другой требует подготовительных работ, чего во время аварии хотелось бы избежать. Так что удобство данного метода оставим за скобками, лишь только грустно вздохнём и набираем:
$ pg_combinebackup backups/full backups/increment1 backups/increment2 -o backups/full_combined
$ cp -r backups/full_combined/* pgdata_new/
$ cp -r /*
Рассмотрим что тут произошло. Чисто технически, поскольку pg_basebackup сохраняет файлы из PGDATA в неизмененном виде, можно просто зайти в папку с бекапами и сделать там pg_ctl start и всё запустится. Но у нас же теперь есть инкременты (продолжаем делать вид, что в реальном мире никто не использует сжатие и предварительно надо всё распаковать), поэтому надо всё собрать в одну кучу с помощью новой утилиты pg_combinebackup от всё того же Хааса. На вход подаём ей директории бекапов которые хотим объединить и папку куда сложить результат. Фактически, место для новой PGDATA, что мы и делаем парой команд cp после объединения. И да, если у нас есть табличные пространства, не забываем скопировать их тоже. А всё потому, что в постгресе так и не появилось встроенной утилиты для рестора, которой можно отдать все эти телодвижения на откуп.
Немного практики с ptrack и pg_probackup
Теперь посмотрим как мы реализовали те же действия в своих решениях. Начинаем, как обычно, с полного бекапа.
$ pg_probackup init -B /backup_dir
$ pg_probackup add-instance -B /backup_dir -D /path/to/pgdata --instance 'main'
$ pg_probackup backup -B /backup_dir --instance 'main' -b FULL
Как видим, нам понадобилось выполнить две дополнительные команды. Первой мы создаём директорию для бекапов и рассказываем об этом pg_probackup. Второй создаём экземпляры резервных копий. Типичная вспомогательная история, которая проверит что везде есть нужные права, создаст конфиги и так далее.
Третьей командой мы уже создаём полный бекап указав директорию куда писать, экземпляр и явным образом указываем режим работы. В нашем случае FULL. Создание инкрементов выглядит так же, только FULL заменяется на ptrack
$ pg_probackup backup -B /backup_dir --instance 'main' -b PTRACK
pg_probackup версий 2.x умеет делать инкремент только от последнего бэкапа в цепочке, поэтому ему не надо явно указывать родительский бекап. Но это именно во второй. Как выйдет третья, не надо так делать.
Если обратиться к документации по pg_probackup, там будет написано, что для создания инкрементов можно ещё использовать режимы PAGE и DELTA. Но по нашим замерам, в среднем, PTRACK получается несколько быстрее.
По сути, инкрементальный режим PAGE в pg_probackup повторяет функциональность walsummarizer. Но с той разницей, что PAGE читает WAL при запуске процесса бекапа т.е. тратит намного больше времени. Вернее, скорее всего потратить намного больше времени т.к. очевидно, то это напрямую зависит от количества произошедших между бекапами изменений. То есть, если наша база больше про чтение (мало записей WAL), то page будет быстрее чем ptrack. Но таких баз намного меньше чем тех, где транзакции происходят активно, так что в неком усреднённом случае page будет медленнее.
Теперь рестор:
$ pg_probackup restore -B /backup_dir --instance 'main' -D /new_pgdata
Ничего неожиданного: указываем каталог с бекапами, экземпляр и папку для восстановления. Табличные пространства он сам поместит куда надо, за них можно не переживать. Главное чтобы мажорные версии пробекапа и постгреса совпадали.
Выводы
Задачи сравнивать оба решения в лабораторных условиях не было, но даже по функциональному описанию можно сделать некоторые выводы.
Walsummarizer отличный проект с отличным набором функций. Как баги подправят, так будет вообще супер.
Ptrack явно эффективнее на маленьких высокопроизводительных базах (т.е. где WAL пишутся как сумасшедшие). На больших базах возрастает вероятность false-positive срабатывания в блум-фильтре.
Если мы делаем бекапы редко, то ptrack будет эффективнее.
Если мы делаем бекапы часто, то выигрывает walsummarizer.
Эффективность работы ptrack не зависит от частоты создания инкрементов или объёма накопленных изменений с последнего бекапа.
Размер ptrack.map_size прямо влияет на количество коллизий (больше карта — меньше коллизий). В данный момент её максимальный размер это 32 Гб.
Ptrack тормозит на старте бекапа из-за необходимости вычитать свою карту и проитерироваться по номерам блоков файлов в PGDATA.
Walsummarizer даёт некий оверхед на процессор во время рутинной работы, но не во время бекапа.
Если читателя всё же интересно получить какие-то лабораторные данные с графиками и цифрами, то пишите, а мы попробуем реализовать.
Скрытый текст
Ну очевидно же что очень хочется объединить плюсы обоих решений откинув их минусы? Ждите pg_probackup v3;)