Главная » Хабрахабр » [Перевод] Как мы использовали отложенную репликацию для аварийного восстановления с PostgreSQL

[Перевод] Как мы использовали отложенную репликацию для аварийного восстановления с PostgreSQL

Или нет? Репликация — не бэкап. Вот как мы использовали отложенную репликацию для восстановления, случайно удалив ярлыки.

Здесь 3 миллиона пользователей и почти 7 миллионов проектов, и это один из самых крупных опенсорс-сайтов SaaS с выделенной архитектурой. Специалисты по инфраструктуре на GitLab отвечают за работу GitLab.com — самого большого экземпляра GitLab в природе. Вряд ли такая катастрофа случится, но мы хорошо подготовились и запаслись разными механизмами бэкапа и репликации. Без системы баз данных PostgreSQL инфраструктура GitLab.com далеко не уедет, и что мы только не делаем для отказоустойчивости на случаи любых сбоев, когда можно потерять данные.

ниже). Репликация — это вам не средство бэкапа баз данных (см. Но сейчас мы увидим, как быстро восстановить случайно удаленные данные с помощью отложенной репликации: на GitLab.com пользователь удалил ярлык для проекта gitlab-ce и потерял связи с мерж-реквестами и задачами.

Смотрите, как это было. С отложенной репликой мы восстановили данные всего за 1,5 часа.

Восстановление на момент времени с PostgreSQL

Она называется Point-in-Time Recovery (PITR) и использует те же механизмы, которые поддерживают актуальность реплики: начиная с достоверного снимка всего кластера базы данных (базовый бэкап), мы применяем ряд изменений состояния до определенного момента времени. У PostgreSQL есть встроенная функция, которая восстанавливает состояние базы данных на определенный момент времени.

А еще отслеживаем изменения состояния базы данных, архивируя журнал упреждающей записи (write-ahead log, WAL). Чтобы использовать эту функцию для холодного бэкапа, мы регулярно делаем базовый бэкап базы данных и храним его в архиве (архивы GitLab живут в облачном хранилище Google). И со всем этим мы можем выполнить PITR для аварийного восстановления: начинаем со снимка, сделанного до ошибки, и применяем изменения из архива WAL вплоть до сбоя.

Что такое отложенная репликация?

То есть транзакция произошла в час X, но в реплике она появится с задержкой d в час X + d. Отложенная репликация — это применение изменений из WAL с задержкой.

Восстановление из архива, по сути, работает, как PITR, но непрерывно: мы постоянно извлекаем изменения из архива WAL и применяем их к реплике. В PostgreSQL есть 2 способа настроить физическую реплику базы данных: восстановление из архива и стриминговая репликация. Мы предпочитаем восстановление из архива — им проще управлять и у него нормальная производительность, которая не отстает от рабочего кластера. А стриминговая репликация напрямую извлекает поток WAL из вышестоящего хоста базы данных.

Как настроить отложенное восстановление из архива

Пример: Параметры восстановления описаны в файле recovery.conf.

standby_mode = 'on'
restore_command = '/usr/bin/envdir /etc/wal-e.d/env /opt/wal-e/bin/wal-e wal-fetch -p 4 "%f" "%p"'
recovery_min_apply_delay = '8h'
recovery_target_timeline = 'latest'

Тут используется wal-e для извлечения сегментов WAL (restore_command) из архива, а изменения будут применяться через восемь часов (recovery_min_apply_delay). С этими параметрами мы настроили отложенную реплику с восстановлением из архива. Реплика будет следить за изменениями временной шкалы в архиве, например, из-за отработки отказа в кластере (recovery_target_timeline).

Архив WAL позволяет их избежать. С recovery_min_apply_delay можно настроить стриминговую репликацию с задержкой, но здесь есть пара подвохов, которые связаны со слотами репликации, обратной связью горячего резерва и проч.

3. Параметр recovery_min_apply_delay появился только в PostgreSQL 9. В предыдущих версиях для отложенной репликации нужно настроить комбинацию функций управления восстановлением (pg_xlog_replay_pause(), pg_xlog_replay_resume()) или удерживать сегменты WAL в архиве на время задержки.

Как PostgreSQL это делает?

Посмотрим на recoveryApplyDelay(XlogReaderState). Любопытно посмотреть, как PostgreSQL реализует отложенное восстановление. Он вызывается из главного цикла повтора для каждой записи из WAL.

static bool
recoveryApplyDelay(XLogReaderState *record)
return true;
}

Как видно, задержка применяется только к коммитам и не трогает другие записи — все изменения применяются напрямую, а коммит откладывается, так что мы увидим изменения только после настроенной задержки. Суть в том, что задержка основана на физическом времени, записанном в метке времени коммита транзакции (xtime).

Как использовать отложенную реплику для восстановления данных

Посмотрим, как восстановить данные на примере случайного удаления ярлыков. Допустим, у нас в продакшене есть кластер базы данных и реплика с восьмичасовой задержкой.

Когда мы узнали о проблеме, мы приостановили восстановление из архива для отложенной реплики:

SELECT pg_xlog_replay_pause();

Полезная штука, если нужно время во всем разобраться. С паузой у нас не было риска, что реплика повторит запрос DELETE.

Мы примерно знали физическое время удаления. Суть в том, что отложенная реплика должна дойти до момента перед запросом DELETE. Так реплика доходит до нужного момента без задержек: Мы удалили recovery_min_apply_delay и добавили recovery_target_time в recovery.conf.

recovery_target_time = '2018-10-12 09:25:00+00'

Правда, чем больше убавка, тем больше данных теряем. С метками времени лучше убавить лишнего, чтобы не промахнуться. Опять же, если проскочим запрос DELETE, все опять удалится и придется начинать заново (или вообще брать холодный бэкап для PITR).

Отследить прогресс на этом этапе можно запросом: Мы перезапустили отложенный экземпляр Postgres, и сегменты WAL повторялись до указанного времени.

SELECT -- current location in WAL pg_last_xlog_replay_location(), -- current transaction timestamp (state of the replica) pg_last_xact_replay_timestamp(), -- current physical time now(), -- the amount of time still to be applied until recovery_target_time has been reached '2018-10-12 09:25:00+00'::timestamptz - pg_last_xact_replay_timestamp() as delay;

Можно настроить действие recovery_target_action, чтобы закрыть, продвинуть или приостановить экземпляр после повтора (по умолчанию он приостанавливается). Если метка времени больше не меняется, восстановление завершено.

Теперь можно, например, экспортировать данные. База данных пришла в состояние до того злосчастного запроса. Если потери масштабные, можно просто продвинуть реплику и использовать ее как основную. Мы экспортировали удаленные данные о ярлыке и все связи с задачами и мерж-реквестами и перенесли их в рабочую базу данных. Но тогда потеряются все изменения после момента, до которого мы восстановились.

Полезно записывать эти ID, например, для операторов DDL (типа DROP TABLE), с помощью log_statements = 'ddl'. Вместо меток времени лучше использовать ID транзакций. Будь у нас ID транзакции, мы бы взяли recovery_target_xid и прогнали все вплоть до транзакции перед запросом DELETE.

Скоро в реплике снова появится восьмичасовая задержка, и мы готовы к будущим неприятностям. Вернуться к работе очень просто: уберите все изменения из recovery.conf и перезапустите Postgres.

Преимущества для восстановления

Нам, например, нужно пять часов, чтобы достать весь базовый бэкап на 2 ТБ. С отложенной репликой вместо холодного бэкапа не приходится часами восстанавливать весь снимок из архива. А потом еще придется применить весь суточный WAL, чтобы восстановиться до нужного состояния (в худшем случае).

Отложенная реплика лучше холодного бэкапа по двум пунктам:

  1. Не нужно доставать весь базовый бэкап из архива.
  2. Есть фиксированное восьмичасовое окно сегментов WAL, которые нужно повторить.

А еще мы постоянно проверяем, можно ли сделать PITR из WAL, и мы бы быстро заметили повреждения или другие проблемы с архивом WAL, следя за отставанием отложенной реплики.

Всего мы решили проблему и восстановили данные за 1,5 часа. В этом примере у нас ушло 50 минут на восстановление, то есть скорость была 110 ГБ данных WAL в час (архив тогда все еще был на AWS S3).

Итоги: где пригодится отложенная реплика (а где нет)

Используйте отложенную репликацию как средство первой помощи, если случайно потеряли данные и заметили эту беду в пределах настроенной задержки.

Но учтите: репликация — не бэкап.

Холодный бэкап пригодится, если вы случайно сделали DELETE или DROP TABLE. У бэкапа и репликации разные цели. Но при этом запрос DROP TABLE почти моментально воспроизводится во всех репликах на рабочем кластере, поэтому обычная репликация тут не спасет. Мы делаем бэкап из холодного хранилища и восстанавливаем предыдущее состояние таблицы или всей базы данных. Сама по себе репликация поддерживает базу данных доступной, когда сдают отдельные серверы, и распределяет нагрузку.

Тут от одной репликации толку нет. Даже с отложенной репликой нам иногда очень нужен холодных бэкап в безопасном месте, если вдруг произойдет сбой дата-центра, скрытое повреждение или другие события, которые сразу не заметишь.

На GitLab.com мы сейчас защищаем от потери данных только на уровне системы и не восстанавливаем данные на уровне пользователя. Примечание.


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

Слушаем SID-музыку через OPL3 на современных ПК

Кто-то может подумает, что это будет что-то ужасное, а оказывается если сделать простой маппер, то можно получить весьма хорошее звучание, как это сделали несколько разработчиков в программе LLSID ещё в далеком 2007 году. Наверное не все любители чиптюн музыки знают, ...

Пользователь в Docker

В новой статье он рассказывает, как создать пользователей в Docker. Андрей Копылов, наш технический директор, любит, активно использует и пропагандирует Docker. Правильная работа с ними, почему пользователей нельзя оставлять с root правами и, как решить задачу несовпадения идентификаторов в Dockerfile. Это кажется очень удобно, ведь ...