Resumo comentado: Designing Data-Intensive Applications — Parte 4

Adriano Croco
5 min readJan 27, 2022

--

Olá!

Esse artigo faz parte de uma série. Se tiver interesse em acompanhar os anteriores, segue os links:

Parte 1
Parte 2
Parte 3

No capítulo 5, há uma breve explanação sobre os principais conceitos relacionados a Replicação de dados, que nada mais é do que manter uma cópia do mesmo dado em múltiplas máquinas conectadas via rede.

Os motivos para fazer isso variam entre: reduzir latência (como dados replicados em regiões próximas aos usuários), aumentar disponibilidade (com o aumento de localidades onde os dados estão disponíveis) ou aumentar a quantidade de máquinas que permitem leitura simultânea (e consequentemente, aumentando o throughput da aplicação no processo).

Caso a base de dados inteira caiba em uma única máquina, é possível replicar os dados através do mecanismo de leader-followers (antigamente conhecido como master-slave, termo que está em desuso, devido a conotação escravagista do mesmo), no qual toda escrita feito em uma instância leader, é replicada para um follower, demonstrada no seguinte fluxo:

Fluxo de replicação de uma query de escrita em um leader

Basicamente, tudo que é escrito na instância leader é replicado (o detalhamento das formas que isso é possível será detalhado mais adiante nesse texto) para um ou mais instâncias followers que, caso o processo tenha ocorrido de forma bem sucedida, deixa as instâncias com o mesmo estado dos dados da instância leader.

Esse mecanismo é tão comum que é usado em SGBDS como MySQL, PostgreSQL e no SQL Server, mas também em bancos NoSQL como o MongoDB e até em ferramentas de mensageria, como o Kafka e o RabbitMQ.

Com isso dito, acredito que valha mencionar que temos duas formas de fazer as replicações: síncronas ou assíncronas (cada uma com suas particularidades).

Na primeira forma, temos a robustez e rapidez em recuperação de falhas como principais vantagens: o follower consegue assumir o papel de leader a qualquer momento, exceto em caso de alguma falha na replicação (dado que todas as réplicas são atualizadas a cada operação de forma síncrona). Em caso de falha no processo de cópia, é necessário uma pausa em todos os processos de escrita do leader para que seja possível corrigir a replicação. Nesse caso, não é possível trocar o pneu com o carro andando, infelizmente.

Uma forma de evitar esse problema é algo semi-síncrono: um dos followers tem replicação síncrona e o restante opera async, justamente para evitar essa falha específica. Nesse cenário, temos uma espécie de garantia que ao menos 1 follower esteja pronto para assumir o papel como leader a qualquer momento em caso de falhas. Ainda sim, não é um metódo 100% confiável.

Em replicações assíncronas, não ocorrem esses problemas, porém, podem ocorrer delays (atrasos) na replicação, que pode gerar problemas caso um cliente esteja lendo de uma réplica que possua dados atrasados, por exemplo.

Para lidar com falhas (independente de qual método de replicação), uma solução possível para a replicação para os followers é usar um log das transações processadas e, em caso de falha em alguma réplica, solicitar a continuação da sincronização para o leader. Esse mecanismo de log possui algumas variações possíveis, detalhadas abaixo:

Statement-Based Replication: Cada operação de escrita é encaminhada para as réplicas pelo leader, uma-a-uma. O ponto de falha são comandos não-determinísticos, como RAND (que retorna números aleatórios), NOW (que retorna o timestamp corrente da máquina), chaves que possuam auto-incremento (até um simples where pode quebrar nesse cenário) e operações comuns em banco de dados (como Procedures e Triggers) que, como dependem do estado da réplica, podem quebrar o resultado da query. A recomendação é para não usar esse método, pois já existem mecanismos mais robustos e confiáveis.

Write-Ahead Log (WAL) Replication: Já mencionado na parte 2 dessa série. É o mecanismo usado no PostgreSQL e no Oracle. O mecanismo consiste em encaminhar para as réplicas os bytes escritos no leader, ipsis litteris. O problema aqui é que o log acaba ficando muito acoplado ao mecanismo de armazenamento (o grau de detalhe aqui é onde vai cada byte em cada bloco do disco). Além disso, ocorre um outro problema — dessa vez, relacionado a manutenção — que é: caso ocorra uma alteração no mecanismo de armazenamento em um upgrade de versão do SGBD, as versões que tá rodando na instância do leader e a dos followers podem se tornar incompatíveis, o que impede upgrades com zero downtime (dado que você tem que derrubar o banco de dados para fazer o upgrade de versão de ambas as instâncias).

Row-Based Replication: Se o WAL é uma replicação física (dado que replica estruturas na camada do disco entre as instâncias), aqui temos uma replicação lógica. Em outros termos, o que é replicado são os dados na granularidade de uma linha. Para inserções, são armazenados no log todos os valores de todas as colunas. Para deleções, as primary keys são armazenadas para identificar a linha afetada (caso não exista PK, são logados todos os valores de todas as colunas). Para updates, o que é armazenado é o mínimo necessário para identificar a linha atualizada (geralmente PKs, também) e os novos valores. Essa forma tem como vantagem a flexibilidade de uso, principalmente. Além desses pontos, é um formato que mantém retrocompatibilidade mais facilmente entre versões de instâncias e suporta parse de aplicações externas mais facilmente (é mecanismo que viabiliza técnicas como CDC, por exemplo).

Trigger-Based Replication: Nesse caso, é usado o próprio mecanismo de triggers do banco de dados para efetuar replicações customizadas. Talvez seja a forma mais intuitiva de se pensar em replicação. Apesar da flexibilidade considerável — pois é possível replicações baseada em atualizações de uma única coluna ou algo extremamente específico — , possui um overhead considerável que não pode ser negligenciado. Também não é recomendado seu uso.

Portanto, escolha entre WAL ou Row-Based, acho que você irá sofrer menos.

Caso o leader falhe por algum motivo, ocorre um processo chamado de failover, que nada mais é do que escolher um novo leader viável entre as réplicas. O processo se resume em: detectar que o leader está inoperante (através de checagens de ping entre as réplicas e disparar o failover quando ocorrerem timeouts do lado das instâncias, por exemplo), escolher um novo leader entre os followers — geralmente, escolhendo qual réplica está mais atualizada em relação ao leader — e direcionar o restante do sistema para usar o novo leader. Aqui cabe um detalhe: escolher um novo leader pode ser algo delicado e acarreta em problemas de consenso distribuído (tema que não pretendo abordar nesse artigo).

Como failover é um processo muito frágil, ele geralmente é feito manualmente, pois, há vários pontos de falha possíveis: como falsos positivos de um leader falsamente fora do ar ou até mesmo caso o leader substituído volte como um zumbi logo após a escolha de um novo leader no lugar.

E aqui acabamos essa série de artigos =)

Eu não abordei alguns assuntos abordados no livro de propósito. Caso você queira se aprofundar, recomendo fortemente que vá na fonte original e divirta-se!

Até a próxima!

Você gostou do conteúdo e gostaria de fazer mentoria comigo? Clique aqui e descubra como.

--

--