Как използвахме отложена репликация за аварийно възстановяване с PostgreSQL

Как използвахме отложена репликация за аварийно възстановяване с PostgreSQL
Репликация — не резервно копиране. Или може би да? Ето как използвахме отложена репликация за възстановяване, след като случайно изтрихме ярлиците.

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

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

С отложената реплика възстановихме данните само за 1,5 часа. Вижте как беше.

Възстановяване на момент от време с PostgreSQL

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

За да използваме тази функция за студено резервно копие, ние редовно правим основно резервно копие на базата данни и го съхраняваме в архив (архивите на GitLab живеят в облачното хранилище на Google). Освен това следим измененията на състоянието на базата данни, архивирайки журнала на предшестващото записване (write-ahead log, 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-e за извличане на сегменти 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 реализира отложеното възстановяване. Нека разгледаме recoveryApplyDelay(XlogReaderState). Той се извиква от главния цикъл на повторение за всяка запись от 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, &microsecs);
    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;

Ако времевата отметка вече не се променя, възстановяването е завършено. Можете да настроите действието recovery_target_action, за да затворите, преместите или задържите инстанцията след възстановяване (по подразбиране тя се задържа).

Базата данни е достигнала състояние преди злощастната заявка. Сега можете например да експортирате данните. Ние експортирахме изтритите данни за маркера и всички връзки с задачите и мерж-реквестите и ги пренесохме в работната база данни. Ако загубите са значителни, можете просто да преместите репликата и да я използвате като основна. Но тогава загубите всички промени след момента, до който сме се възстановили.

Вместо времеви отметки е по-добре да използвате ID на транзакциите. Полезно е да записвате тези ID, например, за оператори DDL (като DROP TABLE) с помощта на log_statements = 'ddl'. Ако имахме ID на транзакцията, щяхме да вземем recovery_target_xid и да преминем през всичко до транзакцията преди заявката. DELETE.

Връщането към работа е много лесно: просто премахнете всички промени от recovery.conf и рестартирайте Postgres. Скоро във репликата отново ще се появи осемчасова забавяне и сме готови за бъдещи неприятности.

Предимства при възстановяването

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

Отложената реплика е по-добра от студения бекъп по две точки:

  1. Не е необходимо да извличате целия основен бекъп от архива.
  2. Има фиксирано осемчасово прозорец от сегменти на WAL, които трябва да бъдат възстановени.

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

В този пример ни отне 50 минути да се възстановим, което означава, че скоростта е била 110 GB данни на WAL на час (архивът все още беше на AWS S3). Ние успяхме да разрешим проблема и възстановим данните за 1,5 часа.

Резюме: къде е полезна отложената репликация (и къде не е)

Използвайте отложената репликация като средство за първа помощ, ако случайно загубите данни и забележите проблема в рамките на настроената закъснение.

Но имайте предвид: репликацията не е бекъп.

Бекъпът и репликацията имат различни цели. Хладният бекъп ще бъде полезен, ако случайно сте направили DELETE или DROP TABLE. Ние правим бекъп от хладно хранилище и възстановяваме предишното състояние на таблицата или на цялата база данни. Но при това заявката DROP TABLE почти моментално се изпълнява във всички реплики на работния кластер, така че обикновената репликация тук няма да помогне. Самата репликация поддържа базата данни достъпна, когато определени сървъри са недостъпни, и разпределя натоварването.

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

Забележка. На GitLab.com в момента защитаваме данните от загуба само на системно ниво и не възстановяваме данни на ниво потребител.

Източник: habr.com

Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри 🔥 Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри | ProHoster