25.1. Непрерывное архивирование и восстановление на момент времени (Point-in-Time Recovery, PITR) #
Всё время в процессе работы Postgres Pro ведёт журнал предзаписи (WAL), который расположен в подкаталоге pg_wal/
каталога с данными кластера баз данных. В этот журнал записываются все изменения, вносимые в файлы данных. Прежде всего, журнал существует для безопасного восстановления после краха сервера: если происходит крах, целостность СУБД может быть восстановлена в результате «воспроизведения» записей, зафиксированных после последней контрольной точки. Однако наличие журнала делает возможным использование третьей стратегии копирования баз данных: можно сочетать резервное копирование на уровне файловой системы с копированием файлов WAL. Если потребуется восстановить данные, мы можем восстановить копию файлов, а затем воспроизвести журнал из скопированных файлов WAL, и таким образом привести систему в нужное состояние. Такой подход более сложен для администрирования, чем любой из описанных выше, но он имеет значительные преимущества:
В качестве начальной точки для восстановления необязательно иметь полностью согласованную копию на уровне файлов. Внутренняя несогласованность копии будет исправлена при воспроизведении журнала (практически то же самое происходит при восстановлении после краха). Таким образом, согласованный снимок файловой системы не требуется, вполне можно использовать tar или похожие средства архивации.
Поскольку при воспроизведении можно обрабатывать неограниченную последовательность файлов WAL, непрерывную резервную копию можно получить, просто продолжая архивировать файлы WAL. Это особенно ценно для больших баз данных, полные резервные копии которых делать как минимум неудобно.
Воспроизводить все записи WAL до самого конца нет необходимости. Воспроизведение можно остановить в любой точке и получить целостный снимок базы данных на этот момент времени. Таким образом, данная технология поддерживает восстановление на момент времени: можно восстановить состояние базы данных на любое время с момента создания резервной копии.
Если непрерывно передавать последовательность файлов WAL другому серверу, получившему данные из базовой копии того же кластера, получается система тёплого резерва: в любой момент мы можем запустить второй сервер и он будет иметь практически текущую копию баз данных.
Примечание
Программы pg_dump и pg_dumpall не создают копии на уровне файловой системы и не могут применяться как часть решения по непрерывной архивации. Создаваемые ими копии являются логическими и не содержат информации, необходимой для воспроизведения WAL.
Как и обычное резервное копирование файловой системы, этот метод позволяет восстанавливать только весь кластер баз данных целиком, но не его части. Кроме того, для архивов требуется большое хранилище: базовая резервная копия может быть объёмной, а нагруженные системы будут генерировать многие мегабайты трафика WAL, который необходимо архивировать. Тем не менее этот метод резервного копирования предпочитается во многих ситуациях, где необходима высокая надёжность.
Для успешного восстановления с применением непрерывного архивирования (также называемого «оперативным резервным копированием» многими разработчиками СУБД), вам необходима непрерывная последовательность заархивированных файлов WAL, начинающаяся не позже, чем с момента начала копирования. Так что для начала вы должны настроить и протестировать процедуру архивирования файлов WAL до того, как получите первую базовую копию. Соответственно, здесь мы обсудим некоторые аспекты архивирования файлов WAL. Для дальнейшего чтения см. pg_probackup.
25.1.1. Настройка архивирования WAL #
В абстрактном смысле, запущенная СУБД Postgres Pro производит неограниченно длинную последовательность записей WAL. СУБД физически делит эту последовательность на файлы сегментов WAL, которые обычно имеют размер 16 МиБ (хотя размер сегмента может быть изменён при initdb). Файлы сегментов получают цифровые имена, которые отражают их позицию в абстрактной последовательности WAL. Когда архивирование WAL не применяется, система обычно создаёт только несколько файлов сегментов и затем «перерабатывает» их, меняя номер в имени ставшего ненужным файла на больший. Предполагается, что файлы сегментов, содержимое которых предшествует последней контрольной точке, уже не представляют интереса и могут быть переработаны.
При архивировании данных WAL необходимо считывать содержимое каждого файла-сегмента, как только он заполняется, и сохранять эти данные куда-то, прежде чем файл-сегмент будет переработан и использован повторно. В зависимости от применения и доступного аппаратного обеспечения, возможны разные способы «сохранить данные куда-то»: можно скопировать файлы-сегменты в смонтированный по NFS каталог на другую машину, записать их на ленту (убедившись, что у вас есть способ идентифицировать исходное имя каждого файла) или собрать их в пакет и записать на CD, либо какие-то совсем другие варианты. Чтобы у администратора баз данных была гибкость в этом плане, Postgres Pro пытается не делать каких-либо предположений о том, как будет выполняться архивация. Вместо этого, Postgres Pro позволяет администратору указать команду оболочки или библиотеку архивирования, которая будет запускаться для копирования файла завершённого сегмента в нужное место. Её действие может заключаться в выполнении простых команд оболочки, включая cp
, или в вызове нетривиальной функции на языке C — это решать вам.
Чтобы включить архивирование WAL, установите в параметре конфигурации wal_level уровень replica
или выше, в archive_mode — значение on
и задайте желаемую команду оболочки в параметре archive_command или библиотеку архивирования в параметре archive_library. На практике эти параметры всегда задаются в файле postgresql.conf
.
В archive_command
символы %p
заменяются полным путём к файлу, подлежащему архивации, а %f
заменяются только именем файла. (Путь задаётся относительно текущего рабочего каталога, т. е. каталога данных кластера). Если в команду нужно включить сам символ %
, запишите %%
. Простейшая команда, которая может быть полезна:
archive_command = 'test ! -f /mnt/server/archivedir/%f && cp %p /mnt/server/archivedir/%f' # Unix archive_command = 'copy "%p" "C:\\server\\archivedir\\%f"' # Windows
Она будет копировать архивируемые сегменты WAL в каталог /mnt/server/archivedir. (Команда дана как пример, а не как рекомендация, и может работать не на всех платформах.) После замены параметров %p
и %f
фактически запускаемая команда может выглядеть так:
test ! -f /mnt/server/archivedir/00000001000000A900000065 && cp pg_wal/00000001000000A900000065 /mnt/server/archivedir/00000001000000A900000065
Подобная команда будет генерироваться для каждого следующего архивируемого файла.
Команда архивирования будет запущена от имени того же пользователя, от имени которого работает сервер Postgres Pro. Поскольку архивируемые последовательности файлов WAL фактически содержат всё, что есть в вашей базе данных, вам нужно будет защитить архивируемые данные от посторонних глаз; например, сохраните архив в каталог, чтение которого запрещено для группы и остальных пользователей.
Важно, чтобы команда архивирования возвращала нулевой код завершения, если и только если она завершилась успешно. Получив нулевой результат, Postgres Pro будет полагать, что файл успешно заархивирован и удалит его или переработает. Однако ненулевой код состояния скажет Postgres Pro, что файл не заархивирован; попытки заархивировать его будут периодически повторяться, пока это не удастся.
Другой способ архивирования — использовать пользовательский модуль архивирования в качестве archive_library
. Поскольку такие модули пишутся на языке C
, создать собственный модуль гораздо сложнее, чем написать команду оболочки. Однако модули архивирования могут быть эффективнее, чем вызываемые команды оболочки, им доступны многие полезные ресурсы сервера. Дополнительная информация о модулях архивирования находится в Главе 52.
Когда команда архивирования завершается сигналом (отличным от SIGTERM, получаемого при штатном отключении сервера) или при возникновении ошибки оболочки (например, если команда не найдена) либо если функция архивирования выдаёт ошибку уровня ERROR
или FATAL
, процесс архиватора прерывается и перезапускается управляющим процессом postmaster. В таких случаях в pg_stat_archiver не сообщается об ошибке.
Команды и библиотеки архивирования обычно разрабатываются так, чтобы не допускать перезаписи любых существующих архивных файлов. Это важная мера безопасности, позволяющая сохранить целостность архива в случае ошибки администратора (например, если архивируемые данные двух разных серверов будут сохраняться в одном каталоге). Рекомендуется протестировать предлагаемую библиотеку архивирования, чтобы убедиться, что она не перезаписывает существующий файл.
В редких случаях Postgres Pro может попытаться повторно архивировать ранее заархивированный файл WAL. Например, если система выходит из строя до того, как сервер делает надёжную запись об успешном архивировании, он пытается снова заархивировать файл после перезапуска (при условии, что архивирование всё ещё включено). Когда команда или библиотека архивирования обнаруживает существовавший файл, первая должна возвращать нулевой статус, а вторая — true
, если содержимое файла WAL полностью совпадает с содержимым существующего архива, который находится в хранилище. Если содержимое существующего файла отличается от содержимого архивируемого файла WAL, команда или библиотека архивирования должны возвращать ненулевой статус или false
соответственно.
Рекомендуется протестировать команду архивирования, чтобы убедиться, что она действительно не перезаписывает существующие файлы. В показанной выше команде для Unix для этого добавлен отдельный шаг test
. На некоторых платформах Unix у cp
есть ключ -i
, который позволяет сделать то же самое менее явно. Но не проверив, какой код состояния при этом возвращается, полагаться на этот ключ не следует. (В частности, GNU cp
возвратит нулевой код состояния, если используется ключ -i
и целевой файл существует, а это не то, что нужно.)
Разрабатывая схему архивирования, подумайте, что произойдёт, если команда или библиотека архивирования начнёт постоянно выдавать ошибку, потому что требуется вмешательство оператора или для архивирования не хватает места. Например, это может произойти, если вы записываете архивы на ленточное устройство без механизма автозамены; когда лента заполняется полностью, больше ничего архивироваться не будет, пока вы не замените кассету. Вы должны убедиться, что любые возникающие ошибки или обращения к человеку (оператору) обрабатываются так, чтобы проблема решалась достаточно быстро. Пока она не разрешится, каталог pg_wal/
продолжит наполняться файлами-сегментами WAL. (Если файловая система, в которой находится каталог pg_wal/
заполнится до конца, Postgres Pro завершит свою работу аварийно. Зафиксированные транзакции не потеряются, но база данных не будет работать, пока вы не освободите место.)
Не важно, с какой скоростью работает команда или библиотека архивирования, если только она не ниже средней скорости, с которой сервер генерирует записи WAL. Обычно работа продолжается, даже если процесс архивирования немного отстаёт. Если же архивирование отстаёт значительно, это приводит к увеличению объёма данных, которые могут быть потеряны в случае аварии. При этом каталог pg_wal/
будет содержать большое количество ещё не заархивированных файлов-сегментов, которые в конце концов могут занять всё доступное дисковое пространство. Поэтому рекомендуется контролировать процесс архивации и следить за тем, чтобы он выполнялся как задумано.
При написании команды или библиотеки архивирования вы должны иметь в виду, что имена файлов для архивирования могут иметь длину до 64 символов и содержать любые комбинации из цифр, точек и букв ASCII. Сохранять исходный относительный путь (%p
) необязательно, но необходимо сохранять имя файла (%f
).
Обратите внимание, что хотя архивирование WAL позволяет сохранить любые изменения данных, произведённые в базе данных Postgres Pro, оно не затрагивает изменения, внесённые в конфигурационные файлы (такие как postgresql.conf
, pg_hba.conf
и pg_ident.conf
), поскольку эти изменения выполняются вручную, а не через SQL. Поэтому имеет смысл разместить конфигурационные файлы там, где они будут заархивированы обычными процедурами копирования файлов. Как перемещать конфигурационные файлы, рассказывается в Разделе 19.2.
Команда или функция архивирования вызывается, только когда сегмент WAL заполнен до конца. Таким образом, если сервер постоянно генерирует небольшой трафик WAL (или есть продолжительные периоды, когда это происходит), между завершением транзакций и их безопасным сохранением в архиве может образоваться большая задержка. Чтобы ограничить время жизни неархивированных данных, можно установить archive_timeout, чтобы сервер переключался на новый файл сегмента WAL как минимум с заданной частотой. Заметьте, что неполные файлы, архивируемые досрочно из-за принудительного переключения по тайм-ауту, будут иметь тот же размер, что и заполненные файлы. Таким образом, устанавливать очень маленький archive_timeout
неразумно — это приведёт к замусориванию архивного хранилища. Обычно подходящее значение archive_timeout
— минута или около того.
Также вы можете принудительно переключить сегмент WAL вручную с помощью pg_switch_wal
, если хотите, чтобы только что завершённая транзакция заархивировалась как можно скорее. Другие полезные функции, относящиеся к управлению WAL, перечисляются в Таблице 9.95.
Когда wal_level
имеет значение minimal
, некоторые команды SQL выполняются в обход журнала WAL. Если архивирование или потоковая репликация были включены во время выполнения таких операторов, WAL не будет содержать информацию, необходимую для восстановления. (На восстановление после краха это не распространяется). Поэтому wal_level
можно изменить только при запуске сервера. Однако для изменения параметра archive_command
или archive_library
достаточно перезагрузить файл конфигурации. Если вы используете для архивирования команду оболочки и хотите на время остановить архивирование, это можно сделать, например, задав в качестве значения archive_command
пустую строку (''
). В результате файлы WAL будут накапливаться в каталоге pg_wal/
, пока не будет восстановлена действующая команда archive_command
.
25.1.2. Линии времени #
Возможность восстановить базу данных на некий предыдущий момент времени создаёт некоторые сложности, сродни научно-фантастическим историям о путешествиях во времени и параллельных мирах. Например, предположим, что в начальной истории базы данных вы удалили важную таблицу в 17:15 во вторник, но осознали эту ошибку только в среду в полдень. Вы можете спокойно взять резервную копию, восстановить данные на 17:14 во вторник и запустить сервер. В этой истории мира базы данных вы никогда не удаляли вышеупомянутую таблицу. Но предположим, что позже вы заметили, что это была не такая уж хорошая идея и захотели вернуться к утру среды в первоначальной истории базы данных. Вы не сможете сделать это, если в процессе работы базы данных она успеет перезаписать какие-либо файлы-сегменты WAL, приводящие к моменту времени, к которому вы хотите вернуться теперь. Таким образом, для получения желаемого результата необходимо как-то отличать последовательности записей WAL, добавленные после восстановления на какой-то момент времени от тех, что существовали в начальной истории базы данных.
Для решения этой проблемы в Postgres Pro есть такое понятие, как линия времени. Всякий раз, когда завершается восстановление из архива, создаётся новая линия времени, позволяющая идентифицировать последовательность записей WAL, добавленных после этого восстановления. Номер линии времени включается в имя файлов-сегментов WAL, так что файлы новой линии времени не перезаписывают данные WAL, сгенерированные предыдущими линиями времени. Например, 00000001
в имени файла WAL 0000000100001234000055CD
— это идентификатор линии времени в шестнадцатеричном формате. (Обратите внимание, что в других контекстах, например в сообщениях журнала сервера, идентификаторы линии времени обычно выводятся в десятичном формате.)
Фактически можно архивировать много различных линий времени. Хотя это может показаться бесполезной возможностью, на самом деле она часто бывает спасительной. Представьте, что вы не определились, какую точку времени выбрать для восстановления, и таким образом должны проводить восстановление методом проб и ошибок, пока не найдёте лучший момент для ответвления от старой истории. Без линий времени этот процесс быстро стал бы очень запутанным. А благодаря линиям времени можно вернуться к любому предыдущему состоянию, включая состояния в ветках линий времени, покинутых ранее.
Каждый раз, когда образуется новая линия времени, Postgres Pro создаёт файл «истории линии времени», показывающий, от какой линии времени ответвилась данная и когда. Эти файлы истории нужны, чтобы система могла выбрать правильные файлы-сегменты WAL при восстановлении из архива, содержащего несколько линий времени. Таким образом, они помещаются в область архивов WAL так же, как и файлы сегментов WAL. Файлы истории представляют собой небольшие текстовые файлы, так что они не занимают много места и их вполне можно сохранять неограниченно долго (в отличие от файлов сегментов, имеющих большой размер). Если хотите, вы можете добавлять в файл истории комментарии, свои собственные заметки о том, как и почему была создана эта конкретная линия времени. Такие комментарии будут особенно ценны, если в результате экспериментов у вас образуется хитросплетение разных линий времени.
По умолчанию восстановление осуществляется до самой последней линии времени, найденной в архиве. Если вы хотите восстановить состояние на линии времени, которая была текущей, когда создавалась копия, либо на какой-либо дочерней линии времени (то есть хотите вернуться к некоторому состоянию, которое тоже было получено в результате попытки восстановления), вам необходимо указать current
или идентификатор целевой линии времени в recovery_target_timeline. Восстановить состояние на линии времени, ответвившейся раньше, чем была сделана базовая резервная копия, нельзя.
25.1.3. Советы и примеры #
Ниже мы дадим несколько советов по настройке непрерывного архивирования.
25.1.3.1. Обособленные горячие резервные копии #
Средства резервного копирования Postgres Pro можно применять для создания обособленных горячих копий. Эти копии нельзя использовать для восстановления на момент времени, но создаются и восстанавливаются они обычно гораздо быстрее, чем дампы pg_dump. (Они также намного больше, чем дампы pg_dump, так что в некоторых случаях выигрыш в скорости может быть потерян.)
25.1.3.2. Сжатие журналов в архиве #
Если размер архива имеет большое значение, можно воспользоваться gzip и сжимать архивные файлы:
archive_command = 'gzip < %p > /mnt/server/archivedir/%f.gz'
При этом для восстановления придётся использовать gunzip:
restore_command = 'gunzip < /mnt/server/archivedir/%f.gz > %p'
25.1.3.3. Скрипты archive_command
#
Многие в качестве команды archive_command
используют скрипты, так что запись в postgresql.conf
оказывается очень простой:
archive_command = 'local_backup_script.sh "%p" "%f"'
Применять отдельный файла скрипта целесообразно всегда, когда вы хотите использовать в процедуре архивирования несколько команд. Это позволяет управлять сложностью этой процедуры в рамках одного скрипта, который можно написать на любом популярном языке скриптов, например на bash или perl.
В частности, с помощью скриптов можно решить такие задачи:
Копирование данных в безопасное внешнее хранилище
Пакетная обработка файлов WAL, чтобы они передавались каждые три часа, а не по одному
Взаимодействие с другими приложениями резервного копирования и восстановления
Взаимодействие со средствами мониторинга, регистрация ошибок
Подсказка
Когда в archive_command
используется скрипт, желательно включить logging_collector. Тогда все сообщения, которые скрипт выведет в stderr, будут записываться в журнал сервера баз данных, что позволит легко диагностировать ошибки в сложных конфигурациях.
25.1.4. Ограничения #
На момент написания документации методика непрерывного архивирования имеет несколько ограничений. Они могут быть ликвидированы в будущих версиях:
Если во время создания базовой резервной копии выполняется команда
CREATE DATABASE
, а затем база-шаблон, задействованная вCREATE DATABASE
, изменяется, пока продолжается копирование, возможно, что при восстановлении эти изменения распространятся также и на созданную базу данных. Конечно, это нежелательно. Во избежание подобных рисков, лучше всего не изменять никакие базы-шаблоны во время получения базовой резервной копии.Команды
CREATE TABLESPACE
записываются в WAL с абсолютным путём и, таким образом, при воспроизведении WAL будут выполнены с тем же абсолютным путём. Это может быть нежелательно, если WAL воспроизводится на другой машине. Но опасность есть, даже если WAL воспроизводится на той же машине, но в другом каталоге данных: при воспроизведении будет так же перезаписано содержимое исходных табличных пространств. Во избежание потенциальных проблем такого рода лучше всего делать новую базовую резервную копию после создания или удаления табличных пространств.
Также следует заметить, что стандартный формат WAL не очень компактный, так как включает много снимков дисковых страниц. Эти снимки страниц предназначены для поддержки восстановления после сбоя, на случай, если понадобится исправить страницы, записанные на диск частично. В зависимости от аппаратного и программного обеспечения вашей системы, риск частичной записи может быть достаточно мал, так что его можно игнорировать, и в этом случае можно существенно уменьшить общий объём архивируемых файлов WAL, выключив снимки страниц с помощью параметра full_page_writes. (Прежде чем делать это, прочтите замечания и предупреждения в Главе 28.) Выключение снимков страниц не препятствует использованию WAL для восстановления PITR. Одним из направлений разработки в будущем является сжатие архивируемых данных WAL путём удаления ненужных копий страниц даже при включённом режиме full_page_writes
. Тем временем администраторы могут сократить количество снимков страниц, включаемых в WAL, увеличив параметры интервала контрольных точек в разумных пределах.