Як мы выкарыстоўвалі адкладзеную рэплікацыю для аварыйнага аднаўлення з 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;

    /* 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, &microsecs);
    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, каб аднавіцца да патрэбнага стану (у горшым выпадку).

Адкладзеная рэпліка лепш халоднага бэкапу па двух пунктах:

  1. Ня трэба даставаць увесь базавы бэкап з архіву.
  2. Ёсць фіксаванае васьмігадзіннае акно сегментаў WAL, якія трэба паўтарыць.

А яшчэ мы ўвесь час правяраем, ці можна зрабіць PITR з WAL, і мы бы хутка заўважылі пашкоджанні ці іншыя праблемы з архівам WAL, сочачы за адставаннем адкладзенай рэплікі.

У гэтым прыкладзе ў нас сышло 50 хвілін на аднаўленне, гэта значыць хуткасць была 110 ГБ дадзеных WAL у гадзіну (архіў тады ўсё яшчэ быў на AWS S3). Агулам мы вырашылі праблему і аднавілі дадзеныя за 1,5 гадзіны.

Вынікі: дзе спатрэбіцца адкладзеная рэпліка (а дзе не)

Выкарыстоўвайце адкладзеную рэплікацыю як сродак першай дапамогі, калі выпадкова страцілі дадзеныя і заўважылі гэтую бяду ў межах настроенай затрымкі.

Але ўлічыце: рэплікацыя - не бэкап.

У бэкапу і рэплікацыі розныя мэты. Халодны бэкап спатрэбіцца, калі вы выпадкова зрабілі DELETE або DROP TABLE. Мы робім бэкап з халоднага сховішча і аднаўляем папярэдні стан табліцы ці ўсёй базы дадзеных. Але пры гэтым запыт DROP TABLE амаль маментальна прайграваецца ва ўсіх рэпліках на працоўным кластары, таму звычайная рэплікацыя тут не выратуе. Сама па сабе рэплікацыя падтрымлівае базу дадзеных даступнай, калі здаюць асобныя серверы, і размяркоўвае нагрузку.

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

Заўвага. На GitLab.com мы зараз абараняем ад страты дадзеных толькі на ўзроўні сістэмы і не аднаўляем дадзеныя на ўзроўні карыстальніка.

Крыніца: habr.com

Дадаць каментар