Come abbiamo utilizzato la replica ritardata per il ripristino di emergenza con PostgreSQL

Come abbiamo utilizzato la replica ritardata per il ripristino di emergenza con PostgreSQL
La replica non è un backup. O forse sì? Ecco come abbiamo usato la replica ritardata per il ripristino dopo aver eliminato accidentalmente i collegamenti.

Specialisti di infrastruttura su GitLab sono responsabili del funzionamento GitLab.com — del più grande esempio di GitLab esistente. Qui ci sono 3 milioni di utenti e quasi 7 milioni di progetti, e questo è uno dei più grandi siti SaaS open-source con architettura dedicata. Senza il sistema di database PostgreSQL, l'infrastruttura di GitLab.com non andrebbe lontano, e qualsiasi cosa facciamo per la tolleranza ai guasti in caso di eventuali crash in cui si possono perdere dati. È improbabile che si verifichi una tale catastrofe, ma ci siamo preparati bene e abbiamo accumulato vari meccanismi di backup e replica.

La replica non è un metodo di backup per database (vedi sotto). Ma ora vedremo come ripristinare rapidamente i dati eliminati accidentalmente con la replica ritardata: nella GitLab.com utente ho eliminato il collegamento per il progetto gitlab-ce e ho perso il collegamento con le merge request e i task.

Con la replica ritardata abbiamo ripristinato i dati in sole 1,5 ore. Guarda come è stato.

Ripristino a un momento specifico con PostgreSQL

PostgreSQL ha una funzione integrata che ripristina lo stato del database a un momento specifico. Si chiama Point-in-Time Recovery (PITR) e utilizza gli stessi meccanismi che mantengono l'attualità della replica: partendo da un'istantanea affidabile dell'intero cluster del database (backup di base), applichiamo una serie di modifiche fino a un momento specifico.

Per utilizzare questa funzione per il backup a freddo, eseguiamo regolarmente un backup di base del database e lo conserviamo in archivio (gli archivi di GitLab risiedono in Google Cloud Storage). Inoltre, monitoriamo le modifiche allo stato del database, archiviando il log delle scritture anticipate (write-ahead log, WAL). E con tutto ciò possiamo eseguire PITR per il ripristino di emergenza: iniziamo con l'istantanea creata prima dell'errore e applichiamo le modifiche dall'archivio WAL fino al guasto.

Che cos'è la replica ritardata?

La replica ritardata è l'applicazione delle modifiche dal WAL con un ritardo. Cioè, la transazione è avvenuta un'ora X, ma nella replica apparirà con un ritardo d di un'ora X + d.

In PostgreSQL ci sono 2 modi per configurare una replica fisica del database: ripristino dall'archivio e replica in streaming. Ripristino dall'archivio, in sostanza, funziona come PITR, ma in modo continuo: estraiamo costantemente le modifiche dall'archivio WAL e le applichiamo alla replica. A replica streaming estrae direttamente il flusso WAL dall'host del database di livello superiore. Preferiamo il ripristino dall'archivio: è più semplice da gestire e ha prestazioni normali che non sono inferiori a quelle del cluster di lavoro.

Come configurare il ripristino ritardato dall'archivio

Opzioni di ripristino sono descritte nel file recovery.conf. Esempio:

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'

Con queste impostazioni abbiamo configurato una replica ritardata con ripristino dall'archivio. Qui si utilizza wal-e per estrarre i segmenti WAL (restore_command) dall'archivio, e le modifiche verranno applicate dopo otto ore (recovery_min_apply_delay). La replica seguirà le modifiche della timeline nell'archivio, come ad esempio a causa della gestione dei guasti nel cluster (recovery_target_timeline).

C recovery_min_apply_delay è possibile configurare la replica streaming con ritardo, ma ci sono un paio di insidie relative agli slot di replica, al feedback del backup caldo, ecc. L'archivio WAL consente di evitarle.

Parametro recovery_min_apply_delay è apparso solo in PostgreSQL 9.3. Nelle versioni precedenti, per la replica ritardata era necessario configurare una combinazione di funzioni di gestione del ripristino (pg_xlog_replay_pause(), pg_xlog_replay_resume()) o mantenere i segmenti WAL nell'archivio durante il tempo di ritardo.

Come lo fa PostgreSQL?

È interessante vedere come PostgreSQL implementa il ripristino ritardato. Guardiamo a recoveryApplyDelay(XlogReaderState). Viene chiamato dal ciclo principale di ripristino per ciascun record del WAL.

static bool
recoveryApplyDelay(XLogReaderState *record)
{
    uint8       xact_info;
    TimestampTz xtime;
    long        secs;
    int         microsecs;

    /* niente da fare se non è configurato alcun ritardo */
    if (recovery_min_apply_delay <= 0)
        return false;

    /* nessun ritardo viene applicato a un database non ancora consistente */
    if (!reachedConsistency)
        return false;

    /*
     * È un record di COMMIT?
     *
     * Abbiamo deliberatamente scelto di non ritardare gli aborti poiché non hanno effetto su
     * MVCC. Già consentiamo la riproduzione di record che non hanno un timestamp,
     * quindi c'è già opportunità di problemi causati da conflitti anticipati su
     * standby.
     * / 
    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);

    /*
     * Esci senza armare il latch se è già passato il tempo per applicare questo
     * record
     * / 
    TimestampDifference(GetCurrentTimestamp(), recoveryDelayUntilTime,
                        &secs, &microsecs);
    if (secs <= 0 && microsecs <= 0)
        return false;

    while (true)
    {
        // Accorciato:
        // Usa WaitLatch fino a raggiungere recoveryDelayUntilTime
        // e poi
        break;
    }
    return true;
}

Il punto è che il ritardo è basato sul tempo fisico, registrato nel timestamp del commit della transazione (xtime). Come si può vedere, il ritardo si applica solo ai commit e non tocca altri record: tutte le modifiche vengono applicate direttamente, mentre il commit viene rinviato, quindi vedremo le modifiche solo dopo il ritardo configurato.

Come utilizzare una replica ritardata per il recupero dei dati

Supponiamo di avere in produzione un cluster di database e una replica con un ritardo di otto ore. Vediamo come recuperare i dati usando come esempio la cancellazione accidentale di etichette.

Quando abbiamo saputo del problema, abbiamo sospeso il recupero dall'archivio per la replica ritardata:

SELECT pg_xlog_replay_pause();

Con la pausa non c'era il rischio che la replica ripetesse la richiesta DELETE. Utile se serve tempo per capire tutto.

Il punto è che la replica ritardata deve arrivare al momento prima della richiesta DELETE. Sapevamo più o meno il tempo fisico della cancellazione. Abbiamo rimosso recovery_min_apply_delay e aggiunto recovery_target_time in recovery.conf. Così la replica raggiunge il momento desiderato senza ritardi:

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

Con i timestamp è meglio ridurre il superfluo per non sbagliare. Tuttavia, quanto maggiore è la riduzione, più dati perdiamo. In ogni caso, se saltiamo la richiesta DELETE, tutto verrà di nuovo eliminato e dovremo ricominciare daccapo (o prendere un backup a freddo per il PITR).

Abbiamo riavviato l'istanza Postgres in attesa e i segmenti WAL sono stati ripetuti fino all'orario indicato. È possibile monitorare i progressi a questo stadio con la seguente query:

SELECT
  -- posizione attuale nel WAL
  pg_last_xlog_replay_location(),
  -- timestamp della transazione corrente (stato della replica)
  pg_last_xact_replay_timestamp(),
  -- tempo fisico attuale
  now(),
  -- quantità di tempo ancora da applicare fino al raggiungimento del recovery_target_time
  '2018-10-12 09:25:00+00'::timestamptz - pg_last_xact_replay_timestamp() as delay;

Se il timestamp non cambia più, il ripristino è completato. Puoi configurare l'azione recovery_target_action, per chiudere, promuovere o sospendere l'istanza dopo il ripristino (di default viene sospesa).

Il database è tornato a uno stato precedente a quella famigerata richiesta. Ora puoi, ad esempio, esportare i dati. Abbiamo esportato i dati rimossi sul collegamento e tutte le associazioni con il lavoro e le merge request e li abbiamo trasferiti nel database di lavoro. Se la perdita è ampia, puoi semplicemente promuovere la replica e usarla come principale. Ma in tal caso perderai tutte le modifiche dopo il momento fino a cui siamo tornati.

È meglio usare gli ID delle transazioni invece dei timestamp. È utile registrare questi ID, ad esempio, per gli operatori DDL (tipo , ma la situazione con), tramite log_statements = 'ddl'. Se avessimo l'ID della transazione, avremmo preso recovery_target_xid e avremmo eseguito tutte fino alla transazione prima della richiesta. DELETE.

Tornare al lavoro è molto semplice: rimuovi tutte le modifiche da recovery.conf e riavvia Postgres. Presto nella replica apparirà di nuovo un ritardo di otto ore e saremo pronti per future sgradevolezze.

Vantaggi del ripristino

Con una replica in attesa, non è necessario ripristinare per ore l'intero snapshot dall'archivio. Ci vogliono, ad esempio, cinque ore per estrarre l'intero backup base da 2 TB. E poi dovremo applicare tutto il WAL giornaliero per ripristinare lo stato desiderato (nel peggiore dei casi).

Una replica in attesa è migliore di un backup a freddo per due motivi:

  1. Non è necessario estrarre l'intero backup base dall'archivio.
  2. C'è una finestra fissa di otto ore di segmenti WAL da ripetere.

Inoltre, controlliamo costantemente se è possibile effettuare un PITR dal WAL e noteremmo rapidamente eventuali danni o altri problemi con l'archivio WAL, monitorando il ritardo della replica in attesa.

In questo esempio, abbiamo impiegato 50 minuti per il ripristino, quindi la velocità era di 110 GB di dati WAL all'ora (l'archivio all'epoca era ancora su AWS S3). Abbiamo risolto il problema e ripristinato i dati in 1,5 ore.

Risultati: dove è utile la replica differita (e dove no)

Utilizza la replica differita come mezzo di pronto soccorso, se hai perso accidentalmente dei dati e ti sei accorto di questo problema entro il ritardo impostato.

Ma ricorda: la replica non è un backup.

Il backup e la replica hanno obiettivi diversi. Un backup freddo è utile se hai fatto accidentalmente DELETE o , ma la situazione con. Effettuamo un backup da un archivio freddo e ripristiniamo lo stato precedente della tabella o dell'intera base di dati. Ma in questo caso la richiesta , ma la situazione con viene quasi istantaneamente replicata in tutte le repliche nel cluster operativo, quindi la replica normale non aiuterà. La replica stessa mantiene il database accessibile quando vengono disattivati server singoli e distribuisce il carico.

Anche con la replica differita, a volte abbiamo davvero bisogno di un backup freddo in un luogo sicuro, nel caso in cui si verifichi un guasto del data center, un danno nascosto o altri eventi che non noti immediatamente. Qui una semplice replica non è sufficiente.

Nota. Su GitLab.com Attualmente proteggiamo solo a livello di sistema contro la perdita di dati e non ripristiniamo i dati a livello utente.

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster