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

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

Esperti di infrastruttura su GitLab sono responsabili del funzionamento GitLab.com — del più grande esemplare di GitLab esistente. Qui ci sono 3 milioni di utenti e quasi 7 milioni di progetti, ed è uno dei più grandi siti open source SaaS con architettura dedicata. Senza PostgreSQL, l'infrastruttura di GitLab.com non andrebbe molto lontano, e facciamo di tutto per garantire la resilienza in caso di guasti, quando potrebbero verificarsi perdite di dati. È improbabile che un disastro del genere accada, ma ci siamo preparati bene e abbiamo vari meccanismi di backup e replica.

La replica non è un mezzo per il backup dei database (vedi sotto). Ma ora vedremo quanto sia facile ripristinare dati eliminati accidentalmente utilizzando la replica ritardata: ho GitLab.com utente eliminato il collegamento per il progetto gitlab-ce e ho perso i collegamenti con le richieste di fusione e le attività.

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

Ripristino a un momento preciso con PostgreSQL

PostgreSQL dispone di una funzione integrata che ripristina lo stato del database a un determinato momento. Si chiama Ripristino a un punto nel tempo (PITR) e utilizza gli stessi meccanismi che mantengono la replica aggiornata: partendo da un'immagine affidabile dell'intero cluster di database (backup di base), applichiamo una serie di modifiche di stato fino a un certo momento.

Per utilizzare questa funzione per un backup a freddo, eseguiamo regolarmente un backup di base del database e lo conserviamo in archivio (gli archivi di GitLab vivono in Google Cloud Storage). Inoltre, monitoriamo le modifiche allo stato del database archiviate nel log delle scritture anticipate (write-ahead log, WAL). Con tutto questo, possiamo eseguire il PITR per il ripristino di emergenza: iniziamo con l'immagine scattata 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. In altre parole, 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: il ripristino da archivio e la replica streaming. Ripristino da archivio, essenzialmente 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 superiore. Preferiamo il ripristino da archivio: è più facile da gestire e offre prestazioni normali che non sono inferiori a quelle del cluster attivo.

Come configurare il ripristino ritardato da archivio

Le 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 da archivio. Qui si utilizza wal-e per estrarre i segmenti WAL (restore_command) dall'archivio, e le modifiche saranno applicate dopo otto ore (recovery_min_apply_delay). La replica seguirà le modifiche nella timeline dell'archivio, ad esempio, a causa di un failover nel cluster (recovery_target_timeline).

C recovery_min_apply_delay è possibile configurare la replica streaming con un ritardo, ma ci sono alcuni inconvenienti legati agli slot di replica, al feedback del backup attivo, e altro. L'archivio WAL permette di evitarli.

Caratteristica recovery_min_apply_delay è stato introdotto solo in PostgreSQL 9.3. Nelle versioni precedenti, per la replica ritardata, è necessario configurare una combinazione di funzioni di gestione del ripristino (pg_xlog_replay_pause(), pg_xlog_replay_resume()) o mantenere i segmenti WAL in archivio durante il periodo di ritardo.

Come fa PostgreSQL a farlo?

È interessante vedere come PostgreSQL implementa il ripristino ritardato. Diamo un'occhiata a recoveryApplyDelay(XlogReaderState). Viene chiamato dal ciclo principale di ripetizione per ogni record del WAL.

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

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

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

    /*
     * È un record di COMMIT?
     *
     * Scegliamo deliberatamente di non ritardare gli aborti poiché non hanno effetto su
     * MVCC. Consentiamo già la riproduzione di record che non hanno un timestamp,
     * quindi ci sono già opportunità per problemi causati da conflitti precoci 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 la serratura 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;
}

La questione è 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 altre registrazioni: tutte le modifiche vengono applicate direttamente, mentre il commit viene posticipato, quindi vedremo le modifiche solo dopo il ritardo impostato.

Come usare una replica ritardata per il ripristino dei dati

Supponiamo di avere un cluster di database in produzione e una replica con un ritardo di otto ore. Vediamo come ripristinare i dati utilizzando l'esempio di una cancellazione accidentale di collegamenti.

Quando siamo venuti a conoscenza del problema, abbiamo sospeso il ripristino dall'archivio per la replica ritardata:

SELECT pg_xlog_replay_pause();

Con la pausa non c'era rischio che la replica ripetesse la query DELETE. Utile, se abbiamo bisogno di tempo per capire tutto.

La questione è che la replica ritardata deve arrivare al momento prima della query DELETE. Sapevamo più o meno l'orario esatto della cancellazione. Abbiamo eliminato recovery_min_apply_delay e aggiunto recovery_target_time in recovery.conf. Così la replica arriva al momento desiderato senza ritardi:

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

Con i timestamp è meglio togliere il superfluo per non sbagliare. Ovviamente, quanto più togliamo, tanto più dati perdiamo. Ancora una volta, se saltiamo la query DELETE, tutto verrà nuovamente cancellato e sarà necessario ricominciare da capo (o prendere un backup a freddo per il PITR).

Abbiamo riavviato l'istanza posticipata di Postgres e i segmenti WAL sono stati ripetuti fino al tempo specificato. È possibile monitorare i progressi a questo stadio eseguendo la seguente query:

SELECT
  -- posizione attuale in WAL
  pg_last_xlog_replay_location(),
  -- timestamp della transazione attuale (stato della replica)
  pg_last_xact_replay_timestamp(),
  -- ora fisica attuale
  now(),
  -- quantità di tempo che deve ancora essere applicata fino al raggiungimento di 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 recupero è completato. È possibile configurare l'azione recovery_target_action, per chiudere, avanzare o sospendere l'istanza dopo la riproduzione (di default viene sospesa).

Il database è tornato allo stato prima di quella sfortunata query. Ora è possibile, ad esempio, esportare i dati. Abbiamo esportato i dati eliminati relativi ai collegamenti e tutte le associazioni con le attività e le merge request e li abbiamo trasferiti nel database di lavoro. Se le perdite sono massicce, è possibile semplicemente avanzare la replica e utilizzarla come principale. Ma in tal caso si perderanno tutte le modifiche apportate dopo il momento fino al quale siamo stati in grado di recuperare.

È meglio utilizzare gli ID delle transazioni invece dei timestamp. È utile annotare questi ID, ad esempio, per gli operatori DDL (di tipo DROP TABLE), utilizzando log_statements = 'ddl'. Se avessimo avuto l'ID della transazione, avremmo preso recovery_target_xid e saremmo tornati a tutto fino alla transazione prima della richiesta DELETE.

Riprendere a lavorare è molto semplice: rimuovi tutte le modifiche da recovery.conf e riavvia Postgres. Presto si ripresenterà un ritardo di otto ore nella replica, e saremo pronti per future problematiche.

Vantaggi del ripristino

Con la replica ritardata, invece di un backup freddo, non è necessario ripristinare l'intero snapshot dall'archivio in ore. Noi, ad esempio, impieghiamo cinque ore per recuperare l'intero backup di base da 2 TB. E poi bisogna applicare tutto il WAL giornaliero per ripristinare allo stato desiderato (nel peggiore dei casi).

La replica ritardata è superiore al backup freddo per due motivi:

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

Inoltre, controlliamo costantemente se possiamo eseguire PITR dal WAL, e ci accorgeremmo rapidamente di eventuali danni o altri problemi con l'archivio WAL, monitorando il ritardo della replica ritardata.

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

Conclusioni: dove può essere utile la replica ritardata (e dove no)

Utilizzate la replica ritardata come un primo soccorso se avete accidentalmente perso dei dati e vi siete accorti del problema entro il limite di ritardo impostato.

Ma tenete presente: la replica non è un backup.

Backup e replica hanno obiettivi differenti. Un backup a freddo è utile se avete accidentalmente creato DELETE o DROP TABLE. Effettuiamo il backup dallo storage a freddo e ripristiniamo lo stato precedente della tabella o dell'intero database. Tuttavia, la richiesta DROP TABLE viene riprodotta quasi istantaneamente su tutte le repliche nel cluster operativo, quindi la normale replica non aiuterà in questo caso. La replica stessa rende il database accessibile quando cadono singoli server e distribuisce il carico.

Anche con una replica ritardata, a volte abbiamo davvero bisogno di un backup a freddo in un luogo sicuro, nel caso in cui si verifichi un guasto del data center, un danneggiamento nascosto o altri eventi che non vengono notati immediatamente. In questo caso, la sola replica non è sufficiente.

Nota. Su GitLab.com Al momento proteggiamo i dati solo a livello di sistema e non recuperiamo 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