Hvordan vi brukte Lazy Replication for Disaster Recovery med PostgreSQL

Hvordan vi brukte Lazy Replication for Disaster Recovery med PostgreSQL
Replikering er ikke en sikkerhetskopi. Eller ikke? Her er hvordan vi brukte lat replikering for gjenoppretting ved å slette snarveier ved et uhell.

infrastrukturspesialister GitLab er ansvarlig for arbeidet GitLab.com - den største forekomsten av GitLab i naturen. Med 3 millioner brukere og nesten 7 millioner prosjekter er det et av de største SaaS-nettstedene med åpen kildekode med en dedikert arkitektur. Uten PostgreSQL-databasesystemet vil ikke GitLab.com-infrastrukturen gå langt, og det vi bare ikke gjør for feiltoleranse i tilfelle feil når du kan miste data. Det er usannsynlig at en slik katastrofe vil skje, men vi forberedte oss godt og fylte opp med ulike sikkerhetskopierings- og replikeringsmekanismer.

Replikering er ikke ditt sikkerhetskopieringsverktøy for databasen (se nedenfor). Men nå skal vi se hvordan du raskt gjenoppretter slettede data ved hjelp av lat replikering: på GitLab.com bruker fjernet snarvei for prosjektet gitlab-ce og mistet forbindelser med sammenslåingsforespørsler og oppgaver.

Med forsinket replika gjenopprettet vi data på bare 1,5 time. Se hvordan det var.

Gjenoppretting på tidspunktet med PostgreSQL

PostgreSQL har en innebygd funksjon som gjenoppretter tilstanden til en database til et bestemt tidspunkt. Det kalles Gjenoppretting på tidspunktet (PITR) og bruker de samme mekanismene som holder en replika oppdatert: starter med et pålitelig øyeblikksbilde av hele databaseklyngen (base backup), bruker vi en rekke tilstandsendringer opp til et visst tidspunkt.

For å bruke denne funksjonen for en kald sikkerhetskopiering, lager vi regelmessig en basedatabasesikkerhetskopiering og lagrer den i et arkiv (GitLab-arkivene lever i Google skylagring). Vi overvåker også endringer i databasetilstanden ved å arkivere en fremskrivningslogg (fremskrivningslogg, WAL). Og med alt dette kan vi gjøre PITR for katastrofegjenoppretting: vi starter med et øyeblikksbilde tatt før feilen og bruker endringene fra WAL-arkivet frem til krasj.

Hva er forsinket replikering?

Forsinket replikering er bruk av endringer fra WAL med en forsinkelse. Det vil si at transaksjonen skjedde på timen X, men det vil vises i kopien med en forsinkelse d om en time X + d.

Det er to måter å sette opp en fysisk databasereplika i PostgreSQL: arkivgjenoppretting og streamingreplikering. Gjenoppretter fra et arkiv, fungerer i hovedsak som PITR, men kontinuerlig: vi trekker stadig ut endringer fra WAL-arkivet og bruker dem på replikaen. EN streaming replikering henter WAL-strømmen direkte fra oppstrømsdatabaseverten. Vi foretrekker gjenoppretting fra et arkiv - det er enklere å administrere og har normal ytelse, som ikke henger etter en produksjonsklynge.

Hvordan sette opp forsinket sikkerhetskopigjenoppretting

Gjenopprettingsalternativer beskrevet i filen recovery.conf... Eksempel:

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'

Med disse innstillingene har vi konfigurert en forsinket replika med arkivgjenoppretting. Brukes her wal-e å trekke ut WAL-segmenter (restore_command) fra arkivet, og endringene vil bli tatt i bruk etter åtte timer (recovery_min_apply_delay). Replica vil se etter endringer i tidslinjen i arkivet, for eksempel på grunn av en klyngefailover (recovery_target_timeline).

С recovery_min_apply_delay du kan konfigurere latency streaming-replikering, men det er et par fallgruver knyttet til replikeringsspor, hot spare-tilbakemeldinger og så videre. WAL-arkivet unngår dem.

Parameter recovery_min_apply_delay dukket bare opp i PostgreSQL 9.3. I tidligere versjoner krever forsinket replikering en kombinasjon av administrasjonsfunksjoner for gjenoppretting (pg_xlog_replay_pause(), pg_xlog_replay_resume()) eller hold WAL-segmentene i arkivet så lenge forsinkelsen varer.

Hvordan gjør PostgreSQL det?

Det er interessant å se hvordan PostgreSQL implementerer lat gjenoppretting. La oss se på recoveryApplyDelay(XlogReaderState). Det kalles fra hovedsløyfe gjenta for hver oppføring fra 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;
}

Poenget er at forsinkelsen er basert på den fysiske tiden som er registrert i tidsstemplet for transaksjonsbekreftelsen (xtime). Som du kan se, gjelder forsinkelsen bare for forpliktelser og påvirker ikke andre poster - alle endringer blir brukt direkte, og forpliktelsen er forsinket, så vi vil se endringene først etter den konfigurerte forsinkelsen.

Hvordan bruke lat replika for datagjenoppretting

La oss si at vi har en databaseklynge i produksjon og en replika med åtte timers forsinkelse. La oss se hvordan du gjenoppretter data ved å bruke et eksempel utilsiktet sletting av snarveier.

Da vi ble klar over problemet, gjorde vi midlertidig stoppet gjenoppretting av sikkerhetskopier for forsinket kopi:

SELECT pg_xlog_replay_pause();

Med en pause hadde vi ingen risiko for at kopien ville gjenta forespørselen DELETE. Nyttig hvis du trenger tid til å finne ut av alt.

Poenget er at den forsinkede kopien må nå øyeblikket før forespørselen DELETE. Vi visste omtrent det fysiske tidspunktet for fjerning. Vi fjernet recovery_min_apply_delay og lagt til recovery_target_time в recovery.conf. Så replikaen når rett øyeblikk uten forsinkelse:

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

Med tidsstempler er det bedre å redusere overskuddet for ikke å gå glipp av. Det er sant at jo større nedgangen er, jo mer data mister vi. Igjen, hvis vi hopper over forespørselen DELETE, vil alt bli slettet igjen og du må starte på nytt (eller til og med ta en kald sikkerhetskopi for PITR).

Vi startet den forsinkede Postgres-forekomsten på nytt, og WAL-segmentene ble gjentatt til det angitte tidspunktet. Du kan spore fremdriften på dette stadiet ved å spørre:

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;

Hvis tidsstemplet ikke lenger endres, er gjenopprettingen fullført. Du kan tilpasse handlingen recovery_target_actionfor å lukke, fremme eller sette forekomsten på pause etter et nytt forsøk (den pauses som standard).

Databasen kom til staten før den skjebnesvangre forespørselen. Nå kan du for eksempel eksportere data. Vi har eksportert fjernetikettdataene og alle lenkene til problemer og sammenslåingsforespørsler og overført dem til produksjonsdatabasen. Hvis tapene er store, kan du ganske enkelt promotere kopien og bruke den som den viktigste. Men da vil alle endringer gå tapt etter øyeblikket vi kom oss til.

Det er bedre å bruke transaksjons-ID-er i stedet for tidsstempler. Det er nyttig å registrere disse ID-ene, for eksempel for DDL-setninger (som DROP TABLE), ved bruk av log_statements = 'ddl'. Hvis vi hadde en transaksjons-ID, ville vi tatt recovery_target_xid og kjørte alt ned til transaksjonen før forespørselen DELETE.

Å komme tilbake til jobb er veldig enkelt: fjern alle endringer fra recovery.conf og start postgres på nytt. Snart vil køen igjen ha en åtte timers forsinkelse, og vi er klare for fremtidige problemer.

Gjenopprettingsfordeler

Med en forsinket kopi, i stedet for en kald sikkerhetskopi, trenger du ikke bruke timer på å gjenopprette hele øyeblikksbildet fra arkivet. For eksempel trenger vi fem timer for å få hele 2 TB base backup. Og da må du fortsatt bruke hele den daglige WAL for å komme deg til ønsket tilstand (i verste fall).

En forsinket kopi er bedre enn en kald backup på to måter:

  1. Du trenger ikke hente hele grunnsikkerhetskopien fra arkivet.
  2. Det er et fast vindu på åtte timer med WAL-segmenter som må gjentas.

Vi sjekker også kontinuerlig for å se om WAL kan PITR-behandles, og vi vil raskt legge merke til korrupsjon eller andre problemer med WAL-arkivet ved å overvåke etterslepet til den forsinkede kopien.

I dette eksemplet tok det oss 50 minutter å gjenopprette, det vil si at hastigheten var 110 GB WAL-data per time (arkivet var fortsatt på AWS S3). Totalt løste vi problemet og gjenopprettet dataene på 1,5 time.

Sammendrag: hvor en forsinket kopi er nyttig (og hvor ikke)

Bruk forsinket replikering som førstehjelp hvis du ved et uhell mister data og oppdager denne katastrofen innen den konfigurerte forsinkelsen.

Men husk: replikering er ikke en sikkerhetskopi.

Sikkerhetskopiering og replikering har forskjellige formål. En kald sikkerhetskopi vil være nyttig hvis du ved et uhell har gjort det DELETE eller DROP TABLE. Vi tar en sikkerhetskopi fra kjølelager og gjenoppretter den forrige tilstanden til en tabell eller en hel database. Men samtidig forespørselen DROP TABLE nesten øyeblikkelig reprodusert i alle replikaer på arbeidsklyngen, så vanlig replikering vil ikke lagres her. Replikering i seg selv holder databasen tilgjengelig når individuelle servere leies ut og fordeler belastningen.

Selv med en forsinket kopi, trenger vi noen ganger virkelig en kald sikkerhetskopi på et trygt sted, hvis det plutselig oppstår en datasenterfeil, skjulte skader eller andre hendelser som du ikke umiddelbart legger merke til. Her fra en replikering er det ingen mening.

Note. På GitLab.com vi beskytter foreløpig kun mot tap av data på systemnivå og gjenoppretter ikke data på brukernivå.

Kilde: www.habr.com

Legg til en kommentar