Реплікація – не бекап. Чи ні? Ось як ми використали відкладену реплікацію для відновлення, випадково вилучивши ярлики.
Реплікація - це вам не засіб бекапу баз даних (gitlab-ce
З відкладеною реплікою ми відновили дані лише за 1,5 години. Дивіться, як це було.
Відновлення на момент часу з PostgreSQL
PostgreSQL має вбудовану функцію, яка відновлює стан бази даних на певний момент часу. Вона називається
Щоб використовувати цю функцію для холодного бекапу, ми регулярно робимо базовий бекап бази даних та зберігаємо його в архіві (архіви GitLab живуть у
Що таке відкладена реплікація?
Відкладена реплікація – це застосування змін із затримкою WAL. Тобто транзакція сталася на годину X
, але у репліці вона з'явиться із затримкою d
в час X + d
.
У PostgreSQL є два способи налаштувати фізичну репліку бази даних: відновлення з архіву та стримінгова реплікація.
Як настроїти відкладене відновлення з архіву
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'
З цими параметрами ми налаштували відкладену репліку із відновленням із архіву. Тут використовується restore_command
) з архіву, а зміни будуть застосовуватися через вісім годин (recovery_min_apply_delay
). Репліка стежитиме за змінами тимчасової шкали в архіві, наприклад, через відпрацювання відмови у кластері (recovery_target_timeline
).
С recovery_min_apply_delay
можна налаштувати стримінгову реплікацію із затримкою, але тут є пара каверз, які пов'язані зі слотами реплікації, зворотним зв'язком гарячого резерву та ін. Архів WAL дозволяє їх уникнути.
Параметр recovery_min_apply_delay
з'явився лише у PostgreSQL 9.3. У попередніх версіях для відкладеної реплікації потрібно налаштувати комбінацію pg_xlog_replay_pause(), pg_xlog_replay_resume()
) або утримувати сегменти WAL в архіві під час затримки.
Як PostgreSQL це робить?
Цікаво подивитися, як PostgreSQL реалізує відкладене відновлення. Подивимося на recoveryApplyDelay(XlogReaderState)
static bool
recoveryApplyDelay(XLogReaderState *record)
{
uint8 xact_info;
TimestampTz xtime;
long secs;
int microsecs;
/* nothing to do if no delay configured */
if (recovery_min_apply_delay <= 0)
return false;
/* no delay is applied on a database not yet consistent */
if (!reachedConsistency)
return false;
/*
* Is it a COMMIT record?
*
* We deliberately choose not to delay aborts since they have no effect on
* MVCC. We already allow replay of records that don't have a timestamp,
* so there is already opportunity for issues caused by early conflicts on
* standbys.
*/
if (XLogRecGetRmid(record) != RM_XACT_ID)
return false;
xact_info = XLogRecGetInfo(record) & XLOG_XACT_OPMASK;
if (xact_info != XLOG_XACT_COMMIT &&
xact_info != XLOG_XACT_COMMIT_PREPARED)
return false;
if (!getRecordTimestamp(record, &xtime))
return false;
recoveryDelayUntilTime =
TimestampTzPlusMilliseconds(xtime, recovery_min_apply_delay);
/*
* Exit without arming the latch if it's already past time to apply this
* record
*/
TimestampDifference(GetCurrentTimestamp(), recoveryDelayUntilTime,
&secs, µsecs);
if (secs <= 0 && microsecs <= 0)
return false;
while (true)
{
// Shortened:
// Use WaitLatch until we reached recoveryDelayUntilTime
// and then
break;
}
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 транзакцій. Корисно записувати ці ID, наприклад, для операторів DDL (типу DROP TABLE
), за допомогою log_statements = 'ddl'
. Якби ми мали ID транзакції, ми б взяли recovery_target_xid
і прогнали все до транзакції перед запитом DELETE
.
Повернутися до роботи дуже просто: приберіть усі зміни з recovery.conf
та перезапустіть Postgres. Незабаром у репліці знову з'явиться восьмигодинна затримка, і ми готові до майбутніх неприємностей.
Переваги для відновлення
З відкладеною реплікою замість холодного бекапу не доводиться відновлювати годинами весь знімок з архіву. Нам, наприклад, потрібно п'ять годин, щоб дістати весь базовий бекап на 2 ТБ. А потім ще доведеться застосувати весь добовий WAL, щоб відновитись до потрібного стану (у гіршому випадку).
Відкладена репліка краща за холодний бекап за двома пунктами:
- Не потрібно діставати весь базовий бекап з архіву.
- Є фіксоване восьмигодинне вікно сегментів WAL, які потрібно повторити.
А ще ми постійно перевіряємо, чи можна зробити PITR з WAL, і ми швидко помітили б пошкодження або інші проблеми з архівом WAL, стежачи за відставанням відкладеної репліки.
У цьому прикладі ми пішли 50 хвилин на відновлення, тобто швидкість була 110 ГБ даних WAL на годину (архів тоді все ще був на
Підсумки: де стане в нагоді відкладена репліка (а де ні)
Використовуйте відкладену реплікацію як засіб першої допомоги, якщо випадково втратили дані та помітили це лихо в межах налаштованої затримки.
Але врахуйте: реплікація – не бекап.
У бекапу та реплікації різні цілі. Холодний бекап стане в нагоді, якщо ви випадково зробили DELETE
або DROP TABLE
. Ми робимо бекап із холодного сховища та відновлюємо попередній стан таблиці або всієї бази даних. Але при цьому запит DROP TABLE
майже миттєво відтворюється у всіх репліках на робочому кластері, тому нормальна реплікація тут не врятує. Сама по собі реплікація підтримує доступну базу даних, коли здають окремі сервери, і розподіляє навантаження.
Навіть із відкладеною реплікою нам іноді дуже потрібен холодних бекап у безпечному місці, якщо раптом станеться збій дата-центру, приховане пошкодження чи інші події, які одразу не помітиш. Тут від однієї реплікації толку немає.
Примітка. На
Джерело: habr.com