1 pontos por GN⁺ 3 시간 전 | Ainda não há comentários. | Compartilhar no WhatsApp
  • Em clusters PostgreSQL baseados em k8s, um problema estrutural fazia com que, durante falhas de rede, o atraso de replicação (replication lag) se acumulasse e tornasse impossível um failover seguro
  • A arquitetura anterior priorizava disponibilidade (availability) acima de durabilidade (durability), então o primário continuava recebendo gravações enquanto as réplicas ficavam para trás, até não restar nenhum candidato que pudesse ser promovido sem perda de dados
  • Como solução, foi aplicada replicação síncrona (synchronous replication) aos candidatos de failover, com coordenação do gerenciador de alta disponibilidade open source Patroni
  • Em um modelo híbrido de replicação, apenas os standbys do leader pool participam da replicação síncrona, enquanto as réplicas de leitura continuam assíncronas, equilibrando durabilidade e latência
  • Apesar do custo de desempenho, como aumento de 53% na latência de escrita com o modo remote_apply, foi possível alcançar failover automático seguro após validar 5 cenários de falha

O problema revelado no GameDay

  • A Datadog realiza GameDays regularmente para encontrar antecipadamente falhas em sistemas e processos, aplicando carga intencional à plataforma para aprender como ela reage em condições reais
  • Em um desses GameDays, uma falha de zona de disponibilidade (AZ) foi simulada no ambiente de staging, provocando latência de rede e expondo uma fraqueza na arquitetura PostgreSQL
    • Os nós primários/writers de vários clusters PostgreSQL baseados em Kubernetes estavam rodando na AZ afetada
    • Com o aumento repentino da latência de rede, o primário não conseguia mais se comunicar de forma estável com as réplicas, o atraso de replicação aumentava → as escritas emperravam → a aplicação passava a servir dados antigos
  • Como não havia nenhuma réplica suficientemente atualizada, o failover não era seguro e o cluster praticamente parava
  • Essa arquitetura funcionava bem em condições normais, mas em certos cenários de falha de rede ela priorizava disponibilidade em vez de durabilidade, sem uma rota segura de recuperação
    • O primário continuava aceitando gravações mesmo enquanto a replicação atrasava, fazendo o atraso aumentar e deixando as réplicas ainda mais para trás
    • Como resultado, não era possível promover um candidato a failover sem risco de perda de dados, e a única opção era esperar a latência diminuir e as réplicas alcançarem o primário
  • O objetivo passou a ser tornar o failover automático e seguro, sem degradar mais do que o necessário as características de desempenho do PostgreSQL

Arquitetura de referência: PostgreSQL sobre Kubernetes

  • O cluster PostgreSQL baseado em Kubernetes é composto por dois pools: leader pool e read replica pool; o PostgreSQL é um sistema de single-writer
    • Com separação entre leitura e escrita, é possível escalar leituras sem sobrecarregar o líder, enquanto a latência de escrita se mantém previsível e estável
  • O leader pool é composto por 1 nó writer ativo, responsável por todas as escritas, e 2 nós standby
    • Os standbys não atendem tráfego da aplicação, mas podem ser promovidos em caso de falha do líder
  • O read replica pool é formado por vários nós que atendem tráfego somente de leitura, otimizados para escalabilidade de leitura e isolamento de consultas, e intencionalmente excluídos do failover

O papel do Patroni e do ZooKeeper

  • O Patroni gerencia replicação, failover e eleição de líder, usando o ZooKeeper como DCS (Distributed Configuration Store)
    • O ZooKeeper armazena metadados como a chave/lock do líder atual, a configuração do cluster e o estado de replicação de cada membro, incluindo o LSN mais recente
    • O Patroni usa essas informações para decidir promoções e rebaixamentos de forma conservadora, priorizando consistência dos dados acima de failovers agressivos
  • Quando um novo nó entra no cluster, ele primeiro verifica no ZooKeeper se já existe um líder
    • Se não houver líder, tenta obter a chave de liderança criando um znode temporário; o ZooKeeper garante que apenas um nó obtenha essa chave, impedindo a formação de múltiplos primários
    • Se já houver líder, o nó se configura como réplica e inicia a replicação por streaming
  • Em caso de partition de rede, uma réplica que perde conexão com o líder ou com o ZooKeeper não consegue verificar o estado do cluster, então o Patroni pausa ou rebaixa temporariamente esse nó
  • Se o líder perde a conexão, o Patroni trabalha com o ZooKeeper para que apenas um standby elegível obtenha o lock de líder, garantindo failover controlado mesmo em falhas parciais de rede
    • Quando a conexão é restaurada, se o antigo líder não conseguir readquirir o lock, ele se rebaixa para standby, evitando split brain

Por que não era possível fazer failover seguro

  • No modelo single-writer, quando ocorre uma falha, o Patroni elege um novo líder entre os standbys saudáveis
  • Para evitar perda de dados, o Patroni faz verificações de segurança antes da promoção; a principal é confirmar se o atraso de replicação está dentro do limite maximum_lag_on_failover
    • Se o standby estiver atrasado em relação ao líder, promovê-lo pode causar perda ou inconsistência de dados
  • No GameDay, quando o primário perdeu conexão, o atraso de replicação de todos os standbys ultrapassou o limite, então o Patroni corretamente recusou o failover
    • O cluster ficou sem um primário seguro para escrita não por causa do Patroni, mas porque não existia um candidato seguro para promoção

Dois modos de replicação por streaming

  • Na replicação por streaming, o líder envia continuamente às réplicas o write-ahead log (WAL) com todas as mudanças, e as réplicas aplicam o WAL localmente para manter a sincronização
  • Replicação assíncrona (padrão)

    • O líder não espera confirmação das réplicas antes de fazer commit da transação
    • Minimiza a latência de escrita e suporta alto throughput
    • Porém, se o líder falhar, transações já commitadas no primário, mas ainda não replicadas, podem se perder durante a promoção
  • Replicação síncrona

    • O líder espera confirmação de pelo menos 1 réplica antes de responder ao cliente
    • Isso reduz muito o risco de pelo menos uma réplica ficar atrasada demais e oferece durabilidade mais forte ao só responder depois de confirmar que a transação commitada existe em outro nó
    • Assim, aumenta a chance de o candidato a failover estar atualizado e poder ser promovido sem risco de divergência de dados

Configuração híbrida de replicação

  • Para equilibrar durabilidade, latência e throughput, foi adotado um modelo híbrido de replicação
    • Os nós standby do leader pool participam da replicação síncrona, e o líder só confirma a escrita após o ack do standby síncrono designado
    • As read replicas continuam com replicação assíncrona, pois atendem apenas tráfego de leitura e não são alvo de failover, limitando a carga de replicação no leader pool
  • Assim, garantias rigorosas de durabilidade são aplicadas apenas aos candidatos de failover, sem impor o mesmo custo de latência às réplicas de leitura

Ajustes em PostgreSQL e Patroni para failover seguro

  • Para ativar a replicação síncrona, foram ajustados parâmetros tanto no PostgreSQL quanto no Patroni
  • Principais parâmetros ajustados

    • synchronous_mode: ativa replicação síncrona no Patroni; com true, o commit só ocorre após a confirmação do standby síncrono conforme synchronous_node_count (padrão false → true, gerenciado pelo Patroni, obrigatório)
    • synchronous_node_count: número de nós standby síncronos, usado para gerar a lista synchronous_standby_names (padrão 1 → 1, gerenciado pelo Patroni, opcional)
    • synchronous_mode_strict: força o modo síncrono estrito; com true, se não houver réplica disponível, as escritas são bloqueadas em vez de voltar para assíncrono (padrão false → true, gerenciado pelo Patroni, opcional)
    • synchronous_commit: configuração de durabilidade de commit no PostgreSQL (padrão on → remote_apply, gerenciado pelo PostgreSQL, opcional)
  • Depois da mudança, o líder só responde a transações após o standby síncrono confirmar que recebeu e aplicou os dados

Equilíbrio entre durabilidade e latência

  • A replicação síncrona melhora a durabilidade, mas aumenta a latência de escrita porque o líder espera a confirmação do standby síncrono; sob carga contínua, isso também pode afetar o throughput
  • O impacto de desempenho depende do número de standbys síncronos (synchronous_node_count) e do nível de durabilidade configurado em synchronous_commit
  • Trade-offs por nível de durabilidade de synchronous_commit

    • remote_apply: espera a réplica gravar, flushar e reproduzir o WAL; oferece a consistência mais forte e a maior latência
    • on (internamente remote_flush): espera a réplica flushar o WAL em disco; oferece durabilidade forte, mas os dados ainda podem não estar legíveis no standby
    • remote_write: espera o WAL chegar ao cache do sistema operacional da réplica, não ao disco; tem latência menor, mas é vulnerável a crash do SO
    • local: faz commit após flush no disco local sem esperar standby; não garante durabilidade entre nós
    • off: faz commit antes do flush local do WAL; menor latência e maior risco de perda de dados

Benchmark de desempenho da replicação síncrona

  • Como cada commit passa a esperar confirmação de um ou mais standbys, a replicação síncrona adiciona latência; para quantificar isso, foi feito benchmark com o pgbench, ferramenta padrão de teste de carga do PostgreSQL (Patroni versão 3.2.1)
  • Foi usado o conjunto de transações TPC-B, que simula uma mistura simples de leitura e escrita, medindo duas métricas
    • Latência média: tempo médio de processamento por transação (ms)
    • Transações por segundo (tps): número de transações concluídas, excluindo o tempo de estabelecimento de conexão
  • Parâmetros de teste

    • Foram variados o fator de escala (tamanho do banco), número de clientes (tráfego concorrente), número de threads (CPU/paralelismo) e quantidade de transações (intensidade da carga) para simular condições parecidas com produção
    • O modo de replicação síncrona com commit por quórum não foi testado neste benchmark
  • Resultados do benchmark

    • Aumento da latência de escrita: remote_apply 53%, on 46%, remote_write 38%, local 32%
    • Queda de throughput (tps): remote_apply 34%, on 31%, remote_write 27%, local 23%
    • remote_apply apresentou consistentemente a maior latência e o menor throughput por esperar a reprodução e aplicação do WAL na réplica, mas é o mais adequado para failover seguro por oferecer a consistência mais forte
  • Aplicação em produção

    • Após o benchmark, remote_apply foi implantado em vários clusters com alta taxa de escrita, sem impacto significativo em latência ou throughput de escrita no nível da aplicação, mesmo sob carga contínua de produção
    • Para mitigar riscos de desempenho, o rollout foi gradual por datacenter e por camada de workload, com período de bake-in e monitoramento contínuo entre as etapas
    • Exemplo: workloads de processamento de recursos com alto throughput continuaram operando sem atraso de processamento nem backlog downstream, apesar do aumento de latência de escrita no banco após ativar a replicação síncrona
    • synchronous_commit pode ser ajustado imediatamente sem downtime com patronictl edit-config, oferecendo flexibilidade para reduzir rapidamente a durabilidade do commit em workloads de throughput extremamente alto

Validação do failover com cenários de falha

  • Foi verificado se a replicação síncrona e o controle estrito de failover realmente protegem a integridade dos dados, evitam split-brain e garantem recuperação automática
  • Cenário 1: perda de 1 standby síncrono

    • Quando um standby síncrono é perdido, o Patroni tenta manter a replicação síncrona atribuindo outro standby elegível
    • O Patroni no nó líder detecta conexões de streaming rompidas, travadas ou atrasadas usando pg_stat_replication, e acompanha a participação das réplicas via ZooKeeper
    • Ele recalcula a lista de réplicas de streaming saudáveis e elegíveis e atualiza synchronous_standby_names conforme synchronous_node_count, mantendo a replicação síncrona ativa
  • Cenário 2: perda de todos os standbys síncronos

    • O comportamento depende do valor de synchronous_mode_strict
    • Modo não estrito: prioridade para disponibilidade de escrita

      • O Patroni esvazia synchronous_standby_names e desativa temporariamente a replicação síncrona, permitindo que o líder continue aceitando escritas em modo assíncrono até que uma réplica saudável volte
    • Modo estrito: bloqueio de escrita por segurança

      • O Patroni define synchronous_standby_names como *; mesmo sem um standby síncrono explícito, o PostgreSQL aceita e faz commit local das transações de escrita, mas bloqueia a resposta ao cliente até que uma réplica confirme o WAL
      • Quando uma réplica adequada capaz de participar da replicação síncrona se reconecta, o Patroni a designa como standby síncrono
  • Cenário 3: indisponibilidade de todos os standbys e réplicas

    • Se todas as réplicas estiverem indisponíveis e synchronous_mode_strict = true, o PostgreSQL retém a confirmação das transações até que pelo menos 1 réplica elegível retorne
    • A consistência dos dados é preservada, mas a aplicação sofre indisponibilidade temporária de escrita
  • Cenário 4: falha do líder durante commit síncrono

    • Trata-se de um caso limite em que o líder falha enquanto espera a confirmação do standby síncrono, antes de recebê-la
    • Causas comuns incluem cancelamento da transação pelo cliente durante o commit, crash ou encerramento do processo PostgreSQL do líder e partition de rede durante a etapa de commit
    • Se o PostgreSQL flushou o WAL localmente, mas falhou em replicá-lo ao standby, a transação não fica visível na réplica por falta de confirmação
    • Se o líder cair antes de replicar o WAL ao standby síncrono e esse standby for promovido, a transação pode se perder e o histórico do antigo líder divergir do novo primário
    • O antigo líder usa pg_rewind para identificar o ponto de divergência da timeline e rebobinar o diretório de dados para a timeline do novo primário, descartando alterações locais não replicadas antes de voltar ao cluster como standby
    • Esse comportamento decorre do funcionamento interno do commit síncrono do PostgreSQL, não do Patroni, e destaca a necessidade de ajuste e monitoramento cuidadosos da configuração de quórum e do mecanismo de confirmação
  • Cenário 5: indisponibilidade do ZooKeeper

    • Quando o ZooKeeper fica indisponível, o Patroni não consegue confirmar a liderança nem eleger um novo líder e passa a operar de forma conservadora para evitar inconsistência de dados
    • Com failsafe desativado

      • Mesmo sem alcançar o ZooKeeper, o líder continua aceitando escritas se estiver acessível e todos os nós estiverem saudáveis, mas só até o TTL do lock do líder expirar
      • Quando o tempo do loop de renovação da chave de líder passa e o lock não pode ser renovado, o Patroni rebaixa o líder e coloca o cluster em modo somente leitura
    • Com failsafe ativado

      • Se o líder perde a conexão com o ZooKeeper, o Patroni verifica continuamente pela API REST se todos os membros do cluster continuam acessíveis
      • Ele só continua aceitando escritas se todos os membros estiverem acessíveis; caso contrário, rebaixa para somente leitura

Failover e switchover manuais com replicação síncrona

  • Além de failover e switchover automáticos baseados em health checks e coordenação via ZooKeeper, o Patroni também permite operações manuais com o comando patronictl; como nem todos os standbys são candidatos válidos quando a replicação síncrona está ativa, há guardrails para proteger a integridade dos dados
  • Failover para um standby assíncrono

    • patronictl failover: falha se o nó selecionado for assíncrono
    • patronictl switchover: falha se o nó selecionado for assíncrono
    • Forçar failover para um nó assíncrono com replicação síncrona ativa contornaria as garantias de durabilidade e poderia causar perda de dados
  • Ao escolher um standby síncrono

    • patronictl failover: funciona, e o líder é trocado por um standby síncrono
    • patronictl switchover: funciona, realizando uma transição graciosa entre líder e standby síncrono
  • Depois de validar o comportamento dos vários modos de synchronous_commit e os guardrails do Patroni, a replicação síncrona foi ativada em clusters de produção com workloads de alta escrita, alta leitura e mistos, sem impacto mensurável em latência ou throughput
  • Se surgir algum problema, é possível voltar com segurança para replicação assíncrona sem downtime definindo synchronous_mode: false

Por que não escolheram DRBD

  • Na avaliação de alta disponibilidade, também foi considerado o sistema de replicação em nível de bloco DRBD (Distributed Replicated Block Device), que espelha entre servidores o volume inteiro, incluindo o diretório de dados do PostgreSQL e os arquivos WAL, criando uma réplica standby quase em tempo real
  • O DRBD pode oferecer latência menor que a replicação por streaming nativa do PostgreSQL, mas exigiria uma mudança arquitetural significativa, incluindo nova infraestrutura, monitoramento e playbooks operacionais
  • Considerando o ambiente maduro baseado em Kubernetes e o controle refinado da replicação síncrona do PostgreSQL, foi escolhida a replicação no nível do banco, por oferecer melhor visibilidade, flexibilidade e confiança operacional

Monitoramento da replicação síncrona

  • Depois de ativar a replicação síncrona, o estado da replicação e a prontidão para failover passaram a ser monitorados de perto; dois sinais em especial ajudaram a manter a estabilidade em escala
  • Evento de espera SyncRep

    • Ocorre quando o primário aguarda a confirmação do standby síncrono antes de concluir o commit e retornar o status; alguma espera é normal, mas esperas longas ou frequentes indicam problemas de desempenho na réplica ou latência de rede entre nós
    • Importância: esperas prolongadas aumentam a latência de escrita e reduzem o throughput
    • Itens monitorados: duração e frequência dos eventos de espera SyncRep e WalSenderWaitForReply, coletados na métrica Datadog postgresql.activity.waits filtrada pela tag wait_event:SyncRep e baseada internamente em consultas à tabela pg_stat_activity
  • Ausência de standby síncrono detectado

    • Se o Patroni não detectar um standby síncrono saudável por um longo período, o cluster perde a capacidade de failover seguro
    • Importância: sem standby síncrono, o failover fica vulnerável a perda de dados
    • Critério de alerta: se patroni_sync_standby permanecer vazio, é disparado um alerta de saúde de alta disponibilidade (HA); esse sinal é coletado a partir de dados OpenMetrics, já que não há integração nativa da Datadog
  • A replicação síncrona melhora a durabilidade, mas pode reduzir disponibilidade e desempenho quando as réplicas estão com problema ou inacessíveis; monitorar tempos de espera e disponibilidade dos standbys é essencial para manter disponibilidade e desempenho sob carga

Failover seguro desde a fase de projeto

  • A falha simulada de AZ revelou uma fraqueza crítica da arquitetura PostgreSQL: as réplicas ficavam para trás em relação ao líder, forçando a escolha entre esperar a rede se recuperar ou aceitar divergência de dados, um trade-off inaceitável em produção
  • Com a adoção de replicação síncrona baseada em Patroni e ajustes finos de durabilidade e latência, o failover passou a ser viável e seguro mesmo sob degradação de rede; benchmarks e simulações repetidas de falha confirmaram recuperação previsível sem prejuízo relevante de desempenho em escala
  • Ao bloquear escritas durante falhas da replicação síncrona, o sistema expõe a falha explicitamente aos serviços a montante; diferente da replicação assíncrona, em que escritas podem se perder silenciosamente, isso permite reagir com retry, enfileiramento e outras estratégias, tornando o modo de falha mais visível e recuperável
  • Como próximos passos, estão sendo explorados o modo de commit baseado em quórum e uma observabilidade mais profunda do estado da replicação

Ainda não há comentários.

Ainda não há comentários.