Como usamos Lazy Replication para recuperação de desastres com PostgreSQL

Como usamos Lazy Replication para recuperação de desastres com PostgreSQL
A replicação não é um backup. Ou não? Veja como usamos a replicação preguiçosa para recuperação, excluindo atalhos acidentalmente.

especialistas em infraestrutura GitLab é responsável pelo trabalho GitLab.com - a maior instância do GitLab na natureza. Com 3 milhões de usuários e quase 7 milhões de projetos, é um dos maiores sites SaaS de código aberto com arquitetura dedicada. Sem o sistema de banco de dados PostgreSQL, a infraestrutura do GitLab.com não irá longe, e o que simplesmente não fazemos para tolerância a falhas em caso de falhas quando você pode perder dados. É improvável que tal catástrofe aconteça, mas nos preparamos bem e estocamos vários mecanismos de backup e replicação.

A replicação não é sua ferramenta de backup de banco de dados (veja abaixo). Mas agora veremos como recuperar rapidamente dados excluídos acidentalmente usando a replicação preguiçosa: em GitLab.com usuário atalho removido para o projeto gitlab-ce e conexões perdidas com solicitações e tarefas de mesclagem.

Com a réplica atrasada, recuperamos os dados em apenas 1,5 horas. Veja como foi.

Recuperação pontual com PostgreSQL

O PostgreSQL possui uma função interna que restaura o estado de um banco de dados para um ponto específico no tempo. é chamado Recuperação pontual (PITR) e usa os mesmos mecanismos que mantêm uma réplica atualizada: começando com um instantâneo confiável de todo o cluster de banco de dados (backup base), aplicamos uma série de mudanças de estado até um determinado ponto no tempo.

Para usar esse recurso para um backup frio, fazemos regularmente um backup de banco de dados básico e o armazenamos em um arquivo (os arquivos do GitLab residem em Armazenamento em nuvem do Google). Também monitoramos as alterações de estado do banco de dados arquivando um log write-ahead (registro de escrita antecipada, VA). E com tudo isso, podemos fazer PITR para recuperação de desastres: começamos com um instantâneo tirado antes do erro e aplicamos as alterações do arquivo WAL até a falha.

O que é replicação atrasada?

A replicação atrasada é a aplicação de alterações do WAL com um atraso. Ou seja, a transação ocorreu na hora X, mas aparecerá na réplica com um atraso d em uma hora X + d.

Existem 2 maneiras de configurar uma réplica de banco de dados física no PostgreSQL: restauração de arquivo e replicação de streaming. Restaurando de um arquivo, funciona essencialmente como PITR, mas continuamente: estamos constantemente extraindo alterações do arquivo WAL e aplicando-as à réplica. A replicação de streaming busca o fluxo WAL diretamente do host do banco de dados upstream. Preferimos restaurar a partir de um arquivo - é mais fácil de gerenciar e tem desempenho normal, que não fica atrás de um cluster de produção.

Como configurar a recuperação de backup atrasada

Opções de recuperação descrito no arquivo recovery.conf. Exemplo:

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'

Com essas configurações, configuramos uma réplica atrasada com restauração de arquivo. Usado aqui Wal-e para extrair segmentos WAL (restore_command) do arquivo, e as alterações serão aplicadas após oito horas (recovery_min_apply_delay). A réplica observará as alterações na linha do tempo no arquivo, como devido a um failover de cluster (recovery_target_timeline).

С recovery_min_apply_delay você pode configurar a replicação de streaming de latência, mas há algumas armadilhas associadas a slots de replicação, feedback de hot spare e assim por diante. O arquivo WAL os evita.

Parâmetro recovery_min_apply_delay apareceu apenas no PostgreSQL 9.3. Nas versões anteriores, a replicação atrasada requer uma combinação de funções de gerenciamento de recuperação (pg_xlog_replay_pause(), pg_xlog_replay_resume()) ou mantenha os segmentos WAL no arquivo durante o atraso.

Como o PostgreSQL faz isso?

É interessante ver como o PostgreSQL implementa a restauração preguiçosa. Vamos olhar para recoveryApplyDelay(XlogReaderState). É chamado de repetição do loop principal para cada entrada do 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;
}

O ponto principal é que o atraso é baseado no tempo físico registrado no registro de data e hora da confirmação da transação (xtime). Como você pode ver, o atraso se aplica apenas aos commits e não afeta outros registros - todas as alterações são aplicadas diretamente e o commit é atrasado, portanto veremos as alterações somente após o atraso configurado.

Como usar a réplica preguiçosa para recuperação de dados

Digamos que temos um cluster de banco de dados em produção e uma réplica com atraso de oito horas. Vamos ver como recuperar dados usando um exemplo exclusão acidental de atalhos.

Quando percebemos o problema, recuperação de backup pausada para réplica atrasada:

SELECT pg_xlog_replay_pause();

Com uma pausa, não corríamos o risco de a réplica repetir a solicitação DELETE. Coisa útil se você precisar de tempo para descobrir tudo.

O ponto principal é que a réplica atrasada deve atingir o momento antes da solicitação DELETE. Sabíamos aproximadamente o tempo físico da remoção. Nós removemos recovery_min_apply_delay e acrescentou recovery_target_time в recovery.conf. Assim, a réplica chega ao momento certo sem demora:

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

Com timestamps, é melhor reduzir o excesso para não errar. É verdade que quanto maior a diminuição, mais dados perdemos. Novamente, se pularmos a solicitação DELETE, tudo será apagado novamente e você terá que recomeçar (ou até mesmo fazer um backup frio para PITR).

Reiniciamos a instância atrasada do Postgres e os segmentos WAL foram repetidos até o tempo especificado. Você pode acompanhar o progresso neste estágio consultando:

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;

Se o registro de data e hora não for mais alterado, a restauração estará concluída. Você pode personalizar a ação recovery_target_actionpara fechar, promover ou pausar a instância após uma nova tentativa (ela pausa por padrão).

O banco de dados chegou ao estado anterior a essa solicitação malfadada. Agora você pode, por exemplo, exportar dados. Exportamos os dados do rótulo remoto e todos os links para problemas e solicitações de mesclagem e os transferimos para o banco de dados de produção. Se as perdas forem grandes, você pode simplesmente promover a réplica e usá-la como principal. Mas então todas as mudanças serão perdidas após o momento em que nos recuperamos.

É melhor usar IDs de transação em vez de timestamps. É útil registrar esses IDs, por exemplo, para instruções DDL (como DROP TABLE), usando log_statements = 'ddl'. Se tivéssemos um ID de transação, levaríamos recovery_target_xid e executei tudo até a transação antes da solicitação DELETE.

Voltar ao trabalho é muito simples: remova todas as alterações de recovery.conf e reinicie o postgres. Em breve, a deixa terá novamente um atraso de oito horas e estamos prontos para problemas futuros.

Benefícios de Recuperação

Com uma réplica atrasada, em vez de um backup frio, você não precisa gastar horas restaurando todo o instantâneo do arquivo. Por exemplo, precisamos de cinco horas para obter todo o backup básico de 2 TB. E então você ainda tem que aplicar todo o WAL diário para recuperar ao estado desejado (no pior caso).

Uma réplica atrasada é melhor do que um backup frio de duas maneiras:

  1. Você não precisa obter todo o backup básico do arquivo.
  2. Há uma janela fixa de oito horas de segmentos WAL que deve ser repetida.

Além disso, estamos constantemente verificando se WAL pode ser PITRed, e notamos rapidamente corrupção ou outros problemas com o arquivo WAL monitorando o backlog da réplica atrasada.

Neste exemplo, levamos 50 minutos para restaurar, ou seja, a velocidade era de 110 GB de dados WAL por hora (o arquivo ainda estava em AWS S3). No total, resolvemos o problema e restauramos os dados em 1,5 horas.

Resumo: onde uma réplica atrasada é útil (e onde não)

Use a replicação atrasada como um primeiro socorro se você acidentalmente perder dados e perceber esse desastre dentro do atraso configurado.

Mas lembre-se: a replicação não é um backup.

O backup e a replicação têm finalidades diferentes. Um backup frio será útil se você acidentalmente DELETE ou DROP TABLE. Fazemos um backup do armazenamento frio e restauramos o estado anterior de uma tabela ou de um banco de dados inteiro. Mas ao mesmo tempo o pedido DROP TABLE reproduzido quase instantaneamente em todas as réplicas no cluster de trabalho, portanto, a replicação regular não será salva aqui. A própria replicação mantém o banco de dados disponível quando os servidores individuais são alugados e distribui a carga.

Mesmo com uma réplica atrasada, às vezes realmente precisamos de um backup frio em um local seguro, se de repente houver uma falha no data center, danos ocultos ou outros eventos que você não percebe imediatamente. Aqui de uma replicação não há sentido.

Nota. Em GitLab.com atualmente protegemos apenas contra perda de dados no nível do sistema e não restauramos dados no nível do usuário.

Fonte: habr.com

Adicionar um comentário