Hvordan vi brugte forsinket replikering til katastrofegendannelse med PostgreSQL

Hvordan vi brugte forsinket replikering til katastrofegendannelse med PostgreSQL
Replikering er ikke backup. Eller ikke? Her er, hvordan vi brugte udskudt replikering til at gendanne fra utilsigtet sletning af genveje.

Infrastruktur specialister GitLab er ansvarlig for arbejdet GitLab.com - den største GitLab-instans i naturen. Med 3 millioner brugere og næsten 7 millioner projekter er det et af de største open source SaaS-websteder med en dedikeret arkitektur. Uden PostgreSQL-databasesystemet vil GitLab.com-infrastrukturen ikke nå langt, og hvad gør vi for at sikre fejltolerance i tilfælde af fejl, når data kan gå tabt. Det er usandsynligt, at en sådan katastrofe vil ske, men vi er godt forberedt og fyldt op med forskellige backup- og replikeringsmekanismer.

Replikering er ikke et middel til at sikkerhedskopiere databaser (se nedenunder). Men nu vil vi se, hvordan du hurtigt kan gendanne utilsigtet slettede data ved hjælp af doven replikering: on GitLab.com bruger slettede genvejen for projektet gitlab-ce og mistede forbindelser med fusionsanmodninger og opgaver.

Med en udskudt replika gendannede vi data på kun 1,5 time. Se hvordan det skete.

Tidspunktgendannelse med PostgreSQL

PostgreSQL har en indbygget funktion, der gendanner en databases tilstand til et bestemt tidspunkt. Det kaldes Point-in-Time Recovery (PITR) og bruger de samme mekanismer, der holder replikaen opdateret: startende med et pålideligt øjebliksbillede af hele databaseklyngen (base backup), anvender vi en række tilstandsændringer op til et bestemt tidspunkt.

For at bruge denne funktion til kold backup laver vi regelmæssigt en grundlæggende database backup og gemmer den i et arkiv (GitLab arkiver live i Google cloud storage). Vi overvåger også ændringer i databasens tilstand ved at arkivere fremskrivningsloggen (fremskrivningslog, WAL). Og med alt dette på plads, kan vi lave en PITR til katastrofegendannelse: begyndende med det øjebliksbillede taget før fejlen, og anvende ændringerne fra WAL-arkivet op til fejlen.

Hvad er udskudt replikation?

Doven replikering er anvendelsen af ​​ændringer fra WAL med en forsinkelse. Det vil sige, at transaktionen skete på en time X, men det vises i replikaen med en forsinkelse d om en time X + d.

PostgreSQL har 2 måder at konfigurere en fysisk databasereplik på: backupgendannelse og streamingreplikering. Gendanner fra et arkiv, fungerer i det væsentlige som PITR, men kontinuerligt: ​​vi henter konstant ændringer fra WAL-arkivet og anvender dem på replikaen. EN streaming replikering henter WAL-strømmen direkte fra upstream-databaseværten. Vi foretrækker arkivgendannelse - det er nemmere at administrere og har normal ydeevne, der følger med produktionsklyngen.

Sådan konfigurerer du forsinket gendannelse fra et arkiv

Gendannelsesmuligheder beskrevet i filen recovery.conf. Et 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 parametre konfigurerede vi en udskudt replika med backupgendannelse. Her bruges det wal-e at udtrække WAL-segmenter (restore_command) fra arkivet, og ændringerne vil blive anvendt efter otte timer (recovery_min_apply_delay). Replikaen vil holde øje med tidslinjeændringer i arkivet, for eksempel på grund af en klynge-failover (recovery_target_timeline).

С recovery_min_apply_delay Du kan konfigurere streaming-replikering med en forsinkelse, men der er et par faldgruber her, der er relateret til replikeringsslots, varm standby-feedback og så videre. WAL-arkivet giver dig mulighed for at undgå dem.

Parameter recovery_min_apply_delay dukkede kun op i PostgreSQL 9.3. I tidligere versioner skal du for udskudt replikering konfigurere kombinationen recovery management funktioner (pg_xlog_replay_pause(), pg_xlog_replay_resume()) eller hold WAL-segmenter i arkivet så længe forsinkelsen varer.

Hvordan gør PostgreSQL dette?

Det er interessant at se, hvordan PostgreSQL implementerer doven opsving. Lad os se på recoveryApplyDelay(XlogReaderState). Det kaldes fra hovedgentagelsesløkke for hver post 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;
}

Den nederste linje er, at forsinkelsen er baseret på den fysiske tid, der er registreret i tidsstemplet for transaktionsforpligtelse (xtime). Som du kan se, gælder forsinkelsen kun for commits og påvirker ikke andre poster - alle ændringer anvendes direkte, og commit er forsinket, så vi vil først se ændringerne efter den konfigurerede forsinkelse.

Sådan bruger du en forsinket replika til at gendanne data

Lad os sige, at vi har en databaseklynge og en replika med otte timers forsinkelse i produktionen. Lad os se, hvordan du gendanner data ved hjælp af et eksempel ved et uheld slette genveje.

Da vi lærte om problemet, vi arkivgendannelse er blevet sat på pause for en udskudt replika:

SELECT pg_xlog_replay_pause();

Med pause havde vi ingen risiko for, at replikaen ville gentage anmodningen DELETE. En nyttig ting, hvis du har brug for tid til at finde ud af alt.

Pointen er, at den udskudte replika skal nå frem til øjeblikket før anmodningen DELETE. Vi kendte omtrent det fysiske tidspunkt for fjernelse. Vi har slettet recovery_min_apply_delay og tilføjet recovery_target_time в recovery.conf. Sådan når replikaen det rigtige øjeblik uden forsinkelse:

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

Med tidsstempler er det bedre at reducere overskuddet for ikke at gå glip af. Sandt nok, jo større faldet er, jo flere data mister vi. Igen, hvis vi savner anmodningen DELETE, vil alt blive slettet igen, og du bliver nødt til at starte forfra (eller endda tage en kold backup til PITR).

Vi genstartede den udskudte Postgres-instans, og WAL-segmenterne blev gentaget indtil det angivne tidspunkt. Du kan spore fremskridt på dette stadium ved at spørge:

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 længere ændres, er gendannelsen fuldført. Handling kan tilpasses recovery_target_actionfor at lukke, fremme eller sætte forekomsten på pause efter genforsøg (den er suspenderet som standard).

Databasen vendte tilbage til sin tilstand før den uheldige anmodning. Nu kan du f.eks. eksportere data. Vi eksporterede de slettede etiketdata og alle links til problemer og fletteanmodninger og flyttede dem ind i produktionsdatabasen. Hvis tabene er store, kan du blot promovere kopien og bruge den som den vigtigste. Men så vil alle ændringer efter det punkt, hvortil vi er kommet os, gå tabt.

I stedet for tidsstempler er det bedre at bruge transaktions-id'er. Det er nyttigt at registrere disse ID'er, for eksempel for DDL-sætninger (som f.eks DROP TABLE), ved hjælp af log_statements = 'ddl'. Hvis vi havde et transaktions-id, ville vi tage recovery_target_xid og kørte alt ned til transaktionen før anmodningen DELETE.

At komme tilbage til arbejdet er meget enkelt: Fjern alle ændringer fra recovery.conf og genstart Postgres. Replikaen vil snart have en forsinkelse på otte timer igen, og vi er forberedt på fremtidige problemer.

Genopretningsfordele

Med en udskudt replika i stedet for en kold backup behøver du ikke bruge timer på at gendanne hele billedet fra arkivet. For eksempel tager det os fem timer at få hele den grundlæggende 2 TB backup. Og så skal du stadig anvende hele den daglige WAL for at komme dig til den ønskede tilstand (i værste fald).

En udskudt replika er bedre end en kold backup på to måder:

  1. Der er ingen grund til at fjerne hele den grundlæggende sikkerhedskopi fra arkivet.
  2. Der er et fast otte-timers vindue med WAL-segmenter, som skal gentages.

Vi tjekker også konstant, om det er muligt at lave en PITR fra WAL, og vi vil hurtigt bemærke korruption eller andre problemer med WAL-arkivet ved at overvåge forsinkelsen af ​​den udskudte replika.

I dette eksempel tog det os 50 minutter at gendanne, hvilket betyder, at hastigheden var 110 GB WAL-data i timen (arkivet var stadig tændt AWS S3). I alt løste vi problemet og gendannede dataene på 1,5 time.

Resultater: hvor en udskudt replika er nyttig (og hvor den ikke er)

Brug forsinket replikering som førstehjælp, hvis du ved et uheld mistede data og bemærkede dette problem inden for den konfigurerede forsinkelse.

Men husk: replikering er ikke en sikkerhedskopi.

Sikkerhedskopiering og replikering har forskellige formål. En kold backup vil være praktisk, hvis du ved et uheld har lavet DELETE eller DROP TABLE. Vi laver en sikkerhedskopi fra kølerum og gendanner den tidligere tilstand af tabellen eller hele databasen. Men samtidig anmodningen DROP TABLE gengives næsten øjeblikkeligt i alle replikaer på arbejdsklyngen, så almindelig replikering hjælper ikke her. Replikering i sig selv holder databasen tilgængelig, når individuelle servere udlejes, og fordeler belastningen.

Selv med en udskudt replika har vi nogle gange virkelig brug for en kold backup på et sikkert sted, hvis der opstår et datacenterfejl, skjulte skader eller andre hændelser, der ikke umiddelbart er mærkbare. Replikation alene er ikke til nogen nytte her.

Bemærk. på GitLab.com Vi beskytter i øjeblikket kun mod tab af data på systemniveau og gendanner ikke data på brugerniveau.

Kilde: www.habr.com

Tilføj en kommentar