
Данные, идентичность и поведение
В первой статье мы рассмотрели подход к созданию физической резервной копии кластера Greengage (open‑source форк Greenplum) и восстановлению из нее, а также утилиту‑прототип (ggbm), реализующую этот подход. Во второй статье мы перешли от идеальных условий к практике: прерывание резервирования, удаление резервных копий без потери зависимых от них restore point, изменение топологии кластера между резервированием и восстановлением.
Но все это по‑прежнему опиралось на одно допущение: мы восстанавливаем кластер сам в себя, на те же хосты, в те же сегменты. В этой статье мы от такого допущения отказываемся. И как только оно исчезает, три сценария, которые выглядят независимыми, оказываются одной и той же задачей, рассмотренной с трех сторон:
как изменение конфигурации влияет на имеющиеся резервные копии;
параллельное восстановление primary‑ и mirror‑сегментов;
восстановление одного кластера из резервной копии другого кластера.
Общая идея такая: каталог PGDATA сегмента содержит три типа данных, и только данные первого типа ‑ это то, ради чего мы вообще снимали резервную копию.
Данные ‑ файлы, хранящие данные heap и append‑optimized таблиц, данные системного каталога, WAL. Это полезная нагрузка, и она одинакова для primary‑сегмента и его зеркала.
Идентичность ‑ то, каким именно сегментом какого именно кластера является данный экземпляр:
gp_dbid,gp_contentid, порт, путь к каталогу данных, размещение табличных пространств. Именно это отличает один экземпляр от другого.Поведение ‑ то, как экземпляр работает и кто может с ним взаимодействовать:
archive_command, настройки памяти и соединений, pg_hba.conf, pg_ident.conf. Все это наследуется от источника резервной копии независимо от того, имеет ли оно смысл в целевом окружении.
Восстановление корректно только тогда, когда данные возвращены без изменений, идентичность переназначена под целевое окружение, а поведение пересмотрено, а не унаследовано вслепую. Все дальнейшее следует из этого.

Как изменение конфигурации влияет на имеющиеся резервные копии
Начнем с самой конфигурации. В первой статье мы перечислили файлы, которые следует исключить из резервной копии, чтобы не запутать PostgreSQL в процессе восстановления. Теперь нам нужен дополняющий список: файлы, которые возвращаются вместе с восстановлением и с которыми надо что‑то делать после него.
Значимые файлы
Файл |
Что содержит |
Что делать после восстановления |
|---|---|---|
postgresql.conf |
|
Переназначить |
internal.auto.conf |
Одну строку, |
Переназначить под |
postgresql.auto.conf |
Все, что задано через |
Удалить параметры восстановления после его завершения |
recovery.conf (Greengage 6) |
|
Создается при восстановлении, должен стать recovery.done по его завершении |
standby.signal / recovery.signal (Greengage 7) |
Наличие файла означает режим восстановления кластера |
Удаляется после восстановления |
pg_hba.conf |
Кто может подключаться и кто может реплицировать ‑ привязано к адресам конкретной пары сегментов |
Сгенерировать заново под целевую пару |
pg_ident.conf |
Сопоставление пользователей ОС и пользователей БД |
Должен соответствовать пользователям ОС на целевых хостах |
Два пункта из этого списка заслуживают более пристального внимания.
gp_dbid и gp_contentid идентифицируют сегмент кластера
Эти две настройки ‑ то, что делает каталог файлов конкретным сегментом. gp_contentid говорит, какую именно часть распределенных данных содержит экземпляр. gp_dbid ‑ какой именно физический экземпляр их содержит. У primary‑сегмента и его зеркала одинаковый gp_contentid и разный gp_dbid ‑ собственно, в этом и состоит разница между ними.
gp_contentid располагается в postgresql.conf, а gp_dbid ‑ в отдельном файле:
$ cat /data1/primary/gpseg0/internal.auto.conf gp_dbid=2
В Greengage путь пользовательского табличного пространства на диске содержит gp_dbid:
<tablespace_location>/<gp_dbid>/GPDB_<major>_<catalog_version>/<dboid>/<relfilenode>
gp_dbid присутствует в пути для того, чтобы несколько сегментов на одном хосте могли использовать общее расположение табличного пространства без конфликтов. Но это означает, что переназначение gp_dbid ‑ не просто правка internal.auto.conf: каталоги табличных пространств нужно перенести в подкаталог с новым gp_dbid, а символьные ссылки в pg_tblspc/ перенаправить на них. Отсюда правило: если меняется gp_dbid, вместе с ним меняется и размещение табличных пространств. Любое восстановление, при котором сегмент попадает под другой gp_dbid, должно обрабатывать табличные пространства явно. Если же пользовательских табличных пространств в кластере нет, эта работа сводится к нулю.
pg_hba.conf привязан к хостам
Файл pg_hba.conf сегмента не является универсальным ‑ он генерируется с адресами пары этого сегмента внутри. Greengage формирует эти записи сам при добавлении или восстановлении зеркал, и выглядят они так:
host replication gpadmin samehost trust host all gpadmin 192.168.1.12/32 trust host replication gpadmin 192.168.1.12/32 trust host replication gpadmin 192.168.1.11/32 trust
Адреса primary‑ и mirror‑сегментов записаны в том виде, в каком они были на момент записи файла. Восстановите этот PGDATA на другом хосте или в паре с другим зеркалом, и файл будет описывать связь, которая не соответствует окружению. Сегмент запустится (с данными все в порядке), а затем репликация будет отклонена, потому что адреса нового зеркала нет в списке.
pg_ident.conf ломается точно так же ‑ если на целевых хостах используется другой пользователь ОС, сопоставления перестают работать и подключиться к СУБД не получится.
Решение состоит в том, чтобы либо сохранить эти файлы до момента восстановления, либо сгенерировать их заново под целевую топологию в рамках восстановления (ровно так, как это делают gprecoverseg и gpaddmirrors для тех сегментов, которых они касаются).
Конфигурация proxy
У того, что конфигурация живет внутри резервной копии, есть и менее очевидное следствие. Как пример, это настройка сетевого протокола для интерконнекта, задаваемая через gp_interconnect_type. В случае, если этот GUC был выставлен в значение proxy, то взаимодействие сегментов через интерконнект определяется значением в gp_interconnect_proxy_addresses. Поскольку данная настройка содержит IP‑адреса/имена хостов из оригинального кластера, то это отразится на попытке выполнить распределенный запрос уже после восстановления кластера ‑ будет выглядеть так, будто запрос завис, хотя на самом деле все из‑за того, что с мастера не будет подключения к сегментам другого кластера. Одно из решений в данном случае ‑ до старта кластера надо запустить только мастер и в режиме utility изменить значение в gp_interconnect_proxy_addresses, чтобы оно соответствовало топологии целевого кластера. Из вариантов попроще можно также в режиме utility переключить тип интерконнекта на udpifc и дополнительно заменить на пустую строку значение в gp_interconnect_proxy_addresses.
Параллельное восстановление primary‑ и mirror‑сегментов
Вспомним процедуру восстановления из первой статьи. Мы восстанавливаем master и каждый primary‑сегмент из репозитория, запускаем кластер, а затем выполняем следующее:
$ gprecoverseg -aF
Этот последний шаг производит полное восстановление всех зеркал, а это в свою очередь вызывает pg_basebackup с соответствующего primary‑сегмента. Как при этом выглядит восстановление кластера с точки зрения одного primary‑сегмента: он один раз прочитал весь объем данных из репозитория, чтобы восстановить primary, а теперь читает весь объем данных с primary повторно и второй раз передает его по сети, чтобы построить зеркало. Очевидно, что зеркала не могут стартовать, пока не отработали primary‑сегменты, то есть эти две фазы последовательны. На большом кластере это заметно увеличивает RTO (Recovery Time Objective), а также порождает дополнительную нагрузку на primary‑сегменты, которые были подняты совсем недавно.

Идея
Данные зеркала побайтово совпадают с данными его primary‑сегмента. Все эти данные у нас уже есть в репозитории. Поэтому вместо того, чтобы восстанавливать primary‑сегмент и затем клонировать его, восстановим primary‑сегмент и зеркало из одного и того же backup set одновременно. В этом случае каждый экземпляр сегмента, и primary, и mirror, представляет собой независимую задачу восстановления.
Различаться между этими двумя восстановлениями должна ровно та идентичность, о которой шла речь выше:
Primary |
Mirror |
|
|---|---|---|
gp_contentid |
0 |
0 (то же) |
gp_dbid |
2 |
5 |
port |
10 000 |
10 500 |
каталог данных |
/data1/primary/gpseg0 |
/data1/mirror/gpseg0 |
подкаталог табличного пространства |
<location>/2/… |
<location>/5/… |
pg_hba.conf |
записи для пары |
записи для пары |
Иначе говоря, восстановление зеркала выглядит вполне допустимым из того же backup set, из которого восстанавливается primary.
Синхронизация
Восстановить данные из бэкапа ‑ относительно простая половина задачи. Интереснее то, что происходит в момент промоушена primary‑сегмента.
Обе копии (primary и mirror) проигрывают WAL до одной и той же точки восстановления, поэтому в этот момент они идентичны. Зеркалу уже не потребуется базовая копия с primary, потому что оно уже находится в позиции primary‑сегмента. Дальше ему нужно только подключиться по репликации. Но тут остаются две вещи, которые стоит разобрать подробно.
Переключение линии времени. Когда primary‑сегмент завершает восстановление на точку во времени и переводится в рабочий режим, он увеличивает номер линии времени (timeline), после чего новый WAL пишется на линии 2, тогда как зеркало все еще находится на линии 1. Реплика не пересечет эту границу самостоятельно. Зеркало должно быть настроено на следование за последней линией времени, и оно должно суметь получить файл истории новой линии либо из архива через restore_command, либо потоком с переведенного в рабочий режим primary‑сегмента:
recovery_target_timeline = 'latest'
Слот репликации удаляется. Greengage держит на каждом primary‑сегменте физический слот репликации с именем internal_wal_replication_slot. Именно он не дает primary‑сегменту переиспользовать WAL, который его зеркало еще не забрало. И вот в чем подвох: когда сегмент стартует в режиме восстановления из архива, Greengage удаляет этот слот:
if (ArchiveRecoveryRequested) ReplicationSlotDropIfExists(INTERNAL_WAL_REPLICATION_SLOT_NAME);
Таким образом, после восстановления у primary‑сегмента слота нет, и ничто не защищает WAL, который еще нужен зеркалу. Слот необходимо пересоздать до того, как зеркало подключится:
$ PGOPTIONS='-c gp_session_role=utility' psql -p 10000 -d postgres \ -c "SELECT pg_create_physical_replication_slot('internal_wal_replication_slot');"
Собирая все вместе, получаем такую последовательность:
Восстановить все primary‑сегменты и все зеркала из одного backup set параллельно, переназначив каждому его собственную идентичность.
Проиграть WAL на всех сегментах до одной и той же точки восстановления. В этот момент primary‑сегмент и зеркало идентичны.
Записать полную топологию (primary‑сегменты и зеркала в
gp_segment_configurationна master, тем же способом, что и в предыдущей статье), выставив статус синхронизации для зеркал вs.Перевести primary‑сегменты в состояние
in production(завершение PITR).Пересоздать
internal_wal_replication_slotна каждом primary‑сегменте.Переконфигурировать каждое зеркало на его primary‑сегмент (
primary_conninfo, имя слота,recovery_target_timeline='latest').Перезапустить кластер.
Когда это оправданно, а когда нет
Стоит честно обозначить компромисс. gprecoverseg -aF является штатным путем: это одна команда, и она хорошо протестирована. То, что описано выше, ‑ нетривиальный способ оптимизации восстановления кластера, в котором нужно правильно выполнить немалую последовательность действий на кластере.
Поэтому подход оправдывает свою сложность тогда, когда объем данных достаточно велик, чтобы сокращение времени восстановления имело значение. И он должен сопровождаться проверками, не менее тщательными, чем само восстановление: сверить каждую пару dbid/content с планируемой топологией, учесть локальную конфигурацию, подтвердить состояние репликации и так далее
Восстановление одного кластера из резервной копии другого кластера
Третий сценарий почти не требует новой механики. Если вы умеете переназначать идентичность сегмента, вы можете разместить его где угодно.
Предварительные условия
Прежде всего три проверки:
Одинаковое число primary‑сегментов. Это то же ограничение: физическая резервная копия содержит по одному backup set на каждый
content, и каждому из них нужно место. Резервная копия кластера из 16 сегментов не может превратиться в кластер из 8 сегментов.Совпадение мажорной версии и версии каталога. Версия каталога здесь обязательна, поскольку она входит в имя каталога табличного пространства.
Доступность репозитория на чтение на целевых хостах, чтобы была возможность чтения данных и WAL.
Сопоставление
Далее строим соответствие между исходной топологией (сохраненной в метаданных резервной копии, как описано в первой статье) и целевой топологией, соединяя их по content:
content |
dbid источника → dbid цели |
хост источника → хост цели |
целевой datadir |
целевой порт |
|---|---|---|---|---|
-1 |
1 → 1 |
|
/data1/master/gpseg-1 |
5432 |
0 |
2 → 2 |
|
/data1/primary/gpseg0 |
10 000 |
1 |
3 → 3 |
|
/data1/primary/gpseg1 |
10 001 |
Затем для каждого сегмента надо восстановить из stanza seg<content> источника в целевой каталог данных, переназначить gp_dbid, port и пути табличных пространств, сгенерировать заново pg_hba.conf и pg_ident.conf под целевые хосты и нейтрализовать archive_command. Наконец, перезаписать gp_segment_configuration на целевом master целевой топологией и запустить кластер.

Ловушка archive_command
А теперь, пожалуй, самое важное. Рассмотренные ранее проблемы, связанные с восстановлением, носили явный характер ‑ если их не исправить, то кластер либо не восстанавливается, либо не функционирует как ожидается. В данном же случае, если все остальное учтено, с работоспособностью кластера все будет в порядке, а вот архивирование WAL в лучшем случае не будет работать, что может повлиять как на работоспособность кластера в будущем, так и на последующие восстановления.
В нашем случае, на примере утилиты ggbm, в postgresql.conf восстановленного сегмента по‑прежнему находится archive_command исходного кластера:
archive_command = 'PGOPTIONS="-c gp_session_role=utility" pgbackrest --stanza=seg%c archive-push %p'
Имя stanza выводится из %c, идентификатора контента сегмента, то есть на целевом кластере оно корректно. А вот в какой репозиторий пойдет запись, определяется целиком тем pgbackrest.conf, на который указывает PGBACKREST_CONFIG на целевом хосте. Если он все еще ссылается на репозиторий исходного кластера, то в момент запуска восстановленный кластер начнет отправлять свой WAL в stanza исходного кластера.
Стоит явно проговорить, почему привычная проверка pgbackrest здесь не срабатывает. Stanza проверяется по системному идентификатору и версии кластера, который в нее пишет, а физическая копия наследует от источника и то, и другое. Для этой проверки клон и оригинал ‑ это один и тот же кластер.
Безопасная последовательность действий:
До первого запуска отключить архивирование на восстановленном кластере ‑ выставить
archive_mode=offлибо направитьarchive_commandв заглушку.Запустить кластер, проверить результат восстановления.
Переконфигурировать pgbackrest.conf на тот репозиторий, который должен принадлежать восстановленному кластеру, создать его stanza и только после этого включить архивирование обратно.

Резервная копия как шаблон
Как только резервную копию можно восстановить в другой кластер, она перестает быть исключительно средством аварийного восстановления и становится средством подготовки новых кластеров.
Держите одну полную резервную копию и именованную точку восстановления в качестве эталонного образа, и каждый новый кластер становится восстановлением из него: стенд с реалистичным объемом данных, кластер под разработчика или под запуск CI, всегда стартующий из идентичного известного состояния, аналитическая копия, которую не жалко пересоздать, кластер для репетиции обновления на реальных данных или переезд на новое оборудование.
По сравнению с gpbackup/gprestore для той же задачи компромисс очевиден. Логическое восстановление переносимо: оно может изменить число сегментов и может применяться на разных версиях кластера. Физическое восстановление не умеет ни того, ни другого, зато оно хорошо параллелится, оно не перестраивает индексы и не пересчитывает статистику.
Что сделать после клонирования
Одна оговорка, которая не имеет отношения к механике резервирования и целиком относится к эксплуатации результата, а именно то, что физическая копия продуктива ‑ это продуктивные данные вместе с продуктивными учетными данными. Роли и их пароли переносятся в неизменном виде, как и все в каталоге, что ссылается наружу. Прежде чем передавать клон кому‑либо, как минимум:
смените или отключите пришедшие вместе с ним роли и ограничьте pg_hba.conf на клоне теми, кому он действительно должен быть доступен;
проверьте расположения внешних таблиц (URL
gpfdist://, endpoint’ы S3, определения PXF и сторонних серверов по‑прежнему указывают на endpoint’ы источника, а внешняя таблица, доступная на запись, с готовностью в них запишет);проверьте все остальное, где встречаются имена хостов или учетные данные, включая расширения.
Маскирование самих данных ‑ это отдельная задача, которую физическая резервная копия за вас не решит. Если клон предназначен для тех, кому не следует видеть продуктивные данные, это придется решать уже после восстановления.
Итоги
За три статьи физическое резервирование кластера Greengage прошло путь от «скопировать PGDATA со всех сегментов» до решения с обоснованной структурой. Последняя часть добавляет к этому способ думать о копии: PGDATA содержит данные, идентичность и поведение, и восстановление корректно только тогда, когда данные возвращены без изменений, идентичность переназначена под то место, куда копия попадает, а поведение пересмотрено, а не унаследовано.
Именно это различие и делает возможными два других сценария. Поскольку зеркало отличается от своего primary‑сегмента только идентичностью, его можно восстановить из резервной копии primary и синхронизировать до промоушена. Это убирает из времени восстановления целый последовательный проход по всему объему данных. И поскольку один кластер отличается от другого только идентичностью, резервная копия может стать основой нового кластера, превращая систему резервирования в инструмент подготовки окружений. Цена этого в том, что переназначение идентичности должно быть выполнено точно и осознанно.
itGuevara
Какие модели отказоустойчивости для данного случая есть? Что-то типа такого.