Hur vi använde fördröjd replikering för katastrofåterställning med PostgreSQL

Hur vi använde fördröjd replikering för katastrofåterställning med PostgreSQL
Replikering är inte backup. Eller inte? Så här använde vi uppskjuten replikering för att återställa från oavsiktlig radering av genvägar.

Infrastrukturspecialister GitLab ansvarar för arbetet GitLab.com - den största GitLab-instansen i naturen. Med 3 miljoner användare och nästan 7 miljoner projekt är det en av de största SaaS-sajterna med öppen källkod med en dedikerad arkitektur. Utan PostgreSQL-databassystemet kommer GitLab.com-infrastrukturen inte att gå långt, och vad gör vi för att säkerställa feltolerans vid eventuella fel när data kan gå förlorade. Det är osannolikt att en sådan katastrof kommer att hända, men vi är väl förberedda och fyllda med olika backup- och replikeringsmekanismer.

Replikering är inte ett sätt att säkerhetskopiera databaser (se nedan). Men nu kommer vi att se hur du snabbt återställer raderade data av misstag med hjälp av lat replikering: på GitLab.com Användaren raderade genvägen för projektet gitlab-ce och förlorade anslutningar med sammanslagningsförfrågningar och uppgifter.

Med en uppskjuten replik återställde vi data på bara 1,5 timmar. Titta hur det gick till.

Tidpunktsåterställning med PostgreSQL

PostgreSQL har en inbyggd funktion som återställer tillståndet för en databas till en specifik tidpunkt. Det kallas Point-in-Time-återställning (PITR) och använder samma mekanismer som håller repliken uppdaterad: börjar med en tillförlitlig ögonblicksbild av hela databasklustret (basbackup), vi tillämpar en serie tillståndsändringar upp till en viss tidpunkt.

För att använda den här funktionen för kall säkerhetskopiering gör vi regelbundet en grundläggande databassäkerhetskopiering och lagrar den i ett arkiv (GitLab-arkiv live i Google molnlagring). Vi övervakar även förändringar i databasens tillstånd genom att arkivera loggen (framskrivningslogg, WAL). Och med allt detta på plats kan vi göra en PITR för katastrofåterställning: börja med ögonblicksbilden som togs före felet och tillämpa ändringarna från WAL-arkivet till felet.

Vad är uppskjuten replikering?

Lazy replikering är tillämpningen av ändringar från WAL med en fördröjning. Det vill säga transaktionen skedde på en timme X, men det kommer att visas i repliken med en fördröjning d om en timme X + d.

PostgreSQL har två sätt att ställa in en fysisk databasreplik: säkerhetskopiering och strömmande replikering. Återställer från ett arkiv, fungerar i princip som PITR, men kontinuerligt: ​​vi hämtar ständigt ändringar från WAL-arkivet och tillämpar dem på repliken. A strömmande replikering hämtar WAL-strömmen direkt från uppströmsdatabasvärden. Vi föredrar arkivåterställning - det är lättare att hantera och har normal prestanda som hänger med i produktionsklustret.

Hur man ställer in fördröjd återställning från ett arkiv

Återställningsalternativ beskrivs i filen recovery.conf. Exempel:

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 dessa parametrar konfigurerade vi en uppskjuten replik med backup-återställning. Här används det wal-e för att extrahera WAL-segment (restore_command) från arkivet, och ändringar kommer att tillämpas efter åtta timmar (recovery_min_apply_delay). Repliken kommer att titta på tidslinjeändringar i arkivet, till exempel på grund av en kluster-failover (recovery_target_timeline).

С recovery_min_apply_delay Du kan ställa in strömmande replikering med en fördröjning, men det finns ett par fallgropar här som är relaterade till replikeringsplatser, hot standby-feedback och så vidare. WAL-arkivet låter dig undvika dem.

Parameter recovery_min_apply_delay dök endast upp i PostgreSQL 9.3. I tidigare versioner måste du konfigurera kombinationen för uppskjuten replikering återställningshanteringsfunktioner (pg_xlog_replay_pause(), pg_xlog_replay_resume()) eller håll WAL-segment i arkivet under fördröjningens varaktighet.

Hur gör PostgreSQL detta?

Det är intressant att se hur PostgreSQL implementerar lazy recovery. Låt oss titta på recoveryApplyDelay(XlogReaderState). Det kallas från huvudupprepningsslinga för varje inlägg från 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;
}

Summan av kardemumman är att fördröjningen är baserad på den fysiska tiden som registrerats i transaktionens tidsstämpel (xtime). Som du kan se, gäller fördröjningen endast commits och påverkar inte andra poster - alla ändringar tillämpas direkt, och commiten är försenad, så vi kommer bara att se ändringarna efter den konfigurerade fördröjningen.

Hur man använder en fördröjd replik för att återställa data

Låt oss säga att vi har ett databaskluster och en replika med åtta timmars fördröjning i produktionen. Låt oss se hur man återställer data med hjälp av ett exempel tar bort genvägar av misstag.

När vi lärde oss om problemet, vi arkivåterställning har pausats för en uppskjuten replik:

SELECT pg_xlog_replay_pause();

Med en paus hade vi ingen risk att repliken skulle upprepa begäran DELETE. En användbar sak om du behöver tid att lista ut allt.

Poängen är att den uppskjutna repliken måste nå ögonblicket före begäran DELETE. Vi visste ungefär den fysiska tidpunkten för borttagning. Vi har raderat recovery_min_apply_delay och tillagt recovery_target_time в recovery.conf. Så här når repliken rätt ögonblick utan dröjsmål:

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

Med tidsstämplar är det bättre att minska överskottet för att inte missa. Det är sant att ju större minskningen är, desto mer data förlorar vi. Återigen, om vi missar begäran DELETE, kommer allt att raderas igen och du måste börja om (eller till och med ta en kall säkerhetskopia för PITR).

Vi startade om den uppskjutna Postgres-instansen och WAL-segmenten upprepades till den angivna tiden. Du kan följa framstegen i detta skede genom att fråga:

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;

Om tidsstämpeln inte längre ändras är återställningen klar. Åtgärd kan anpassas recovery_target_actionför att stänga, främja eller pausa instansen efter ett nytt försök (den är avstängd som standard).

Databasen återgick till sitt tillstånd innan den olyckliga begäran. Nu kan du till exempel exportera data. Vi exporterade den raderade etikettdatan och alla länkar till frågor och sammanslagningsförfrågningar och flyttade dem till produktionsdatabasen. Om förlusterna är storskaliga kan du helt enkelt marknadsföra kopian och använda den som den huvudsakliga. Men då kommer alla förändringar efter den punkt som vi har återhämtat oss till att gå förlorade.

Istället för tidsstämplar är det bättre att använda transaktions-ID:n. Det är användbart att registrera dessa ID:n, till exempel för DDL-satser (som DROP TABLE), genom att använda log_statements = 'ddl'. Om vi ​​hade ett transaktions-ID skulle vi ta recovery_target_xid och körde allt till transaktionen före begäran DELETE.

Att komma tillbaka till jobbet är väldigt enkelt: ta bort alla ändringar från recovery.conf och starta om Postgres. Replikan kommer snart att ha åtta timmars försening igen, och vi är förberedda på framtida problem.

Återvinningsfördelar

Med en uppskjuten replik istället för en kall säkerhetskopia behöver du inte spendera timmar på att återställa hela bilden från arkivet. Till exempel tar det oss fem timmar att få hela den grundläggande 2 TB-backupen. Och då måste du fortfarande applicera hela den dagliga WAL för att återhämta dig till önskat tillstånd (i värsta fall).

En uppskjuten replik är bättre än en kall backup på två sätt:

  1. Det finns ingen anledning att ta bort hela den grundläggande säkerhetskopian från arkivet.
  2. Det finns ett fast fönster på åtta timmar med WAL-segment som måste upprepas.

Vi kontrollerar också ständigt om det är möjligt att göra en PITR från WAL, och vi skulle snabbt märka korruption eller andra problem med WAL-arkivet genom att övervaka fördröjningen av den uppskjutna repliken.

I det här exemplet tog det oss 50 minuter att återställa, vilket betyder att hastigheten var 110 GB WAL-data per timme (arkivet var fortfarande på AWS S3). Totalt löste vi problemet och återställde data på 1,5 timme.

Resultat: där en uppskjuten replik är användbar (och där den inte är det)

Använd fördröjd replikering som första hjälpen om du av misstag tappat data och upptäckte detta problem inom den konfigurerade fördröjningen.

Men kom ihåg: replikering är inte en säkerhetskopia.

Säkerhetskopiering och replikering har olika syften. En kall backup kommer väl till pass om du av misstag gjorde det DELETE eller DROP TABLE. Vi gör en säkerhetskopia från kyllagring och återställer det tidigare tillståndet för tabellen eller hela databasen. Men samtidigt begäran DROP TABLE reproduceras nästan omedelbart i alla repliker på arbetsklustret, så vanlig replikering hjälper inte här. Replikeringen i sig håller databasen tillgänglig när enskilda servrar hyrs ut och fördelar belastningen.

Även med en uppskjuten replik behöver vi ibland verkligen en kall säkerhetskopia på en säker plats om ett datacenterfel, dolda skador eller andra händelser som inte är direkt märkbara inträffar. Enbart replikering är till ingen nytta här.

Notera. på GitLab.com Vi skyddar för närvarande endast mot dataförlust på systemnivå och återställer inte data på användarnivå.

Källa: will.com

Lägg en kommentar