
Репликация — не резервно копиране. Или може би да? Ето как използвахме отложена репликация за възстановяване, след като случайно изтрихме ярлиците.
в GitLab отговарят за работата — най-голямото проявление на GitLab в света. Тук има 3 милиона потребители и почти 7 милиона проекта, и това е един от най-големите опенсорс сайтове SaaS с отделена архитектура. Без системата за бази данни PostgreSQL инфраструктурата на GitLab.com ще бъде много ограничена, затова ние правим всичко възможно за откажане на системата при всякакви сривове, за да не загубим данни. Вероятно такава катастрофа няма да се случи, но ние сме добре подготвени с различни механизми за резервно копиране и репликация.
Репликацията не е средство за резервно копиране на бази данни (). Но сега ще видим как бързо да възстановим случайно изтрити данни с помощта на отложена репликация: на потребител за проекта и загубих връзките с мерж-реквестите и задачите.
С отложената реплика възстановихме данните само за 1,5 часа. Вижте как беше.
Възстановяване на момент от време с PostgreSQL
PostgreSQL разполага с вградена функция, която възстановява състоянието на базата данни до определен момент. Тя се нарича (PITR) и използва същите механизми, които поддържат актуалността на репликата: започвайки от надежден моментен образ на целия кластер от бази данни (основно резервно копие), приложим редица изменения на състоянието до определен момент.
За да използваме тази функция за студено резервно копие, ние редовно правим основно резервно копие на базата данни и го съхраняваме в архив (архивите на GitLab живеят в ). Освен това следим измененията на състоянието на базата данни, архивирайки журнала на предшестващото записване (, WAL). И с всичко това можем да извършим PITR за аварийно възстановяване: започваме от изображението, направено преди грешката, и прилагаме измененията от архива WAL до повредата.
Какво е отложена репликация?
Отложената репликация е прилагане на промените от WAL с закъснение. Тоест транзакция е извършена в час X, но в репликата ще се появи с закъснение d в час X + d.
В PostgreSQL има 2 начина за конфигуриране на физическа репликация на базата данни: възстановяване от архив и стрийминг репликация. , по същество, работи като PITR, но непрекъснато: ние постоянно извличаме промените от архива WAL и ги прилагаме към репликата. А директно извлича потока 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 (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 реализира отложеното възстановяване. Нека разгледаме . Той се извиква от за всяка запись от WAL.
static bool
recoveryApplyDelay(XLogReaderState *record)
{
uint8 xact_info;
TimestampTz xtime;
long secs;
int microsecs;
/* Няма какво да се прави, ако няма зададена забавяне */
if (recovery_min_apply_delay <= 0)
return false;
/* Няма закъснение при база данни, която все още не е последователна */
if (!reachedConsistency)
return false;
/*
* Дали е запис за COMMIT?
*
* Умишлено избираме да не забавяме abort, тъй като те нямат значение за
* MVCC. Вече позволяваме възпроизвеждане на записи, които нямат времеви маркер,
* така че вече има възможност за проблеми причинени от ранни конфликти на
* 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);
/*
* Изход, без да задействаме latch, ако е вече след времето за прилагане на този
* запис
* /
TimestampDifference(GetCurrentTimestamp(), recoveryDelayUntilTime,
&secs, µsecs);
if (secs <= 0 && microsecs <= 0)
return false;
while (true)
{
// Съкращаване:
// Използвайте WaitLatch, докато не достигнем recoveryDelayUntilTime
// и след това
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
-- текущо местоположение в WAL
pg_last_xlog_replay_location(),
-- текуща времева отметка на транзакцията (състоянието на репликата)
pg_last_xact_replay_timestamp(),
-- текущо физическо време
now(),
-- количеството време, което все още трябва да бъде приложено, докато не бъде достигнато recovery_target_time
'2018-10-12 09:25:00+00'::timestamptz - pg_last_xact_replay_timestamp() as delay;Ако времевата отметка вече не се променя, възстановяването е завършено. Можете да настроите действието , за да затворите, преместите или задържите инстанцията след възстановяване (по подразбиране тя се задържа).
Базата данни е достигнала състояние преди злощастната заявка. Сега можете например да експортирате данните. Ние експортирахме изтритите данни за маркера и всички връзки с задачите и мерж-реквестите и ги пренесохме в работната база данни. Ако загубите са значителни, можете просто да преместите репликата и да я използвате като основна. Но тогава загубите всички промени след момента, до който сме се възстановили.
Вместо времеви отметки е по-добре да използвате ID на транзакциите. Полезно е да записвате тези ID, например, за оператори DDL (като DROP TABLE) с помощта на log_statements = 'ddl'. Ако имахме ID на транзакцията, щяхме да вземем recovery_target_xid и да преминем през всичко до транзакцията преди заявката. DELETE.
Връщането към работа е много лесно: просто премахнете всички промени от recovery.conf и рестартирайте Postgres. Скоро във репликата отново ще се появи осемчасова забавяне и сме готови за бъдещи неприятности.
Предимства при възстановяването
С отложената реплика вместо студен бекъп не е необходимо часове наред да възстановявате целия снимък от архива. Например, ни трябват пет часа, за да извлечем целия основен бекъп от 2 ТБ. След това ще трябва и да приложим целия дневен WAL, за да се възстановим до необходимо състояние (в най-лошия случай).
Отложената реплика е по-добра от студения бекъп по две точки:
- Не е необходимо да извличате целия основен бекъп от архива.
- Има фиксирано осемчасово прозорец от сегменти на WAL, които трябва да бъдат възстановени.
Също така постоянно проверяваме дали е възможно да направим PITR от WAL и бързо бихме забелязали повреди или други проблеми с архива на WAL, наблюдавайки забавянето на отложената реплика.
В този пример ни отне 50 минути да се възстановим, което означава, че скоростта е била 110 GB данни на WAL на час (архивът все още беше на ). Ние успяхме да разрешим проблема и възстановим данните за 1,5 часа.
Резюме: къде е полезна отложената репликация (и къде не е)
Използвайте отложената репликация като средство за първа помощ, ако случайно загубите данни и забележите проблема в рамките на настроената закъснение.
Но имайте предвид: репликацията не е бекъп.
Бекъпът и репликацията имат различни цели. Хладният бекъп ще бъде полезен, ако случайно сте направили DELETE или DROP TABLE. Ние правим бекъп от хладно хранилище и възстановяваме предишното състояние на таблицата или на цялата база данни. Но при това заявката DROP TABLE почти моментално се изпълнява във всички реплики на работния кластер, така че обикновената репликация тук няма да помогне. Самата репликация поддържа базата данни достъпна, когато определени сървъри са недостъпни, и разпределя натоварването.
Дори с отложената репликация понякога много ни е нужен хладен бекъп на безопасно място, ако случайно се случи повреда на дата центъра, скрито повреждане или други събития, които не забелязвате веднага. Тук само от репликацията няма да има полза.
Забележка. На в момента защитаваме данните от загуба само на системно ниво и не възстановяваме данни на ниво потребител.
Източник: habr.com
