DEV Community

Henrique Reis
Henrique Reis

Posted on

[SD] Replicação em bancos de dados distribuídos: Conceitos, Estratégias e Trade-offs

Hoje eu gostaria de aprofundar em uma das principais estratégias utilizadas em bancos de dados distribuídos: replicação.

Replicar significa manter cópias dos mesmos dados em diferentes nós, então vamos entender como essas cópias são mantidas, por que a replicação aumenta a disponibilidade e a capacidade de leitura, e quais problemas surgem quando os nós não estão perfeitamente sincronizados.

O que é replicação?

Replicação é o processo de manter cópias dos mesmos dados em diferentes nós de um sistema de banco de dados.

Imagine que temos um banco principal:

imagem1

Podemos adicionar réplicas:

imagem2

O principal recebe alterações e as réplicas recebem essas alterações para manter uma cópia dos dados.

O objetivo da replicação não é simplesmente "ter backups". Ela pode ser utilizada para aumentar disponibilidade, capacidade de leitura e tolerância à falhas, tentando remover o SPOF (Single Point of Failure).

Primary e Replica

Uma arquitetura bastante comum utiliza o modelo Primary/Replica, também conhecido como Leader/Follower em algum sistemas.

O Primary é responsável por receber operações de escrita, como:

INSERT
UPDATE
DELETE
Enter fullscreen mode Exit fullscreen mode

As réplicas recebem essas alterações e mantêm cópias dos dados.

Um fluxo simplificado seria:

imagem3

A aplicação pode então utilizar o Primary para operações que alteram dados e as Replicas para determiandas operações de leitura.

Isso permite separar parte da carga.

Read Replicas

Quando uma réplica é utilizada principalmente para atender operações de leitura, podemos chamá-la de Read Replica.

Imagine uma aplicação que possui:

imagem4

Se todas essas operações forem direcionadas ao mesmo banco, as leituras podem consumir uma parcela significativa dos recursos disponíveis.

Podemos distribuir parte das leituras:

imagem5

Agora o Primary continua concentrando as escirtas, enquanto as réplicas podem absorver parte das leituras.

Isso pode aumentar a capacidade de leitura sem necessariamente aumentar a capacidade do Primary na mesma proporção.

Replicação não é Sharding

Essa distinção precisa ficar muito clara porque são conceitos que aparecem juntos mas na replicação, temos cópias dos mesmos dados, como já foi mencionado:

Primary:
A B C D

Replica:
A B C D
Enter fullscreen mode Exit fullscreen mode

No particionamento, os dados são divididos:

Node 1:
A B

Node 2:
C D
Enter fullscreen mode Exit fullscreen mode

Portanto:

  • Replicação => duplicamos dados.
  • Particionamento => dividimos dados.

A replicação pode aumentar a disponibilidade e capacidade de leitura. O particionamento será utilizado principalmente para distribuir armazenamento e carga entre diferentes nós.

Podemos inclusive combinar ambos:

imagem6

Mas vamos deixar essa combinação para quando estudarmos sobre Sharding.

Quando a replicação acontece?

Quando uma alteração é realizada no Primary, precisamos propagá-la para as réplicas.

Imagine:

UPDATE Account
SET balance = 800
Enter fullscreen mode Exit fullscreen mode

Primary registra essa alteração.

Depois, a informação precisa chegar às réplicas:

imagem7

A maneira como o sistema espera ou não pela confirmação das réplicas define uma diferença muito importante entre replicação síncrona e assíncrona.

Replicação síncrona

Na replicação síncrona, o sistema espera que uma ou mais réplicas confirmem a operação antes de considerar a escrita concluída.

Um fluxo simplificado:

imagem8

Imagine que o usuário altere seu saldo. O Primary recebe a alteração e precisa garantir que a réplica também tenha recebido a informação antes de confirmar a operação.

A principal vantagem é que temos uma garantia de que os dados já estão presentes em múltiplos nós quando a operação é confirmada.

Porém, isso possui um custo.

O custo da replicação síncrona

Para confirmar uma operação, precisamos esperar pela comunicação entre os nós.

Se os nós estiverem distantes ou uma réplica estiver lenta, a operação pode demorar demais.

Primary Database => Replica => 100ms
Enter fullscreen mode Exit fullscreen mode

A aplicação pode precisar aguardar essa comunicação antes de receber a confirmação e isso aumenta a latência da escrita.

Além disso, se uma réplica necessária para a confirmação estiver indisponível, podemos ter dificuldades para completar novas escritas, dependendo da configuração do sistema.

Portanto, temos um tradeoff: Replica síncrona => maior garantia de consistência entre as réplicas, mas potencialmente maior latência e menor tolerância à indisponibilidade de réplicas necessárias.

Replicação assíncrona

Na replicação assíncrona, o Primary pode confirmar a escrita antes de todos as réplicas receberem a alteração.

O fluxo pode ser:

imagem9

Isso reduz a latência de escrita porque o Primary não precisa esperar todas as réplicas.

Porém, agora existe uma possibilidade importante: durante algum intervalo, as réplicas podem possuir dados antigos.

Imagine:

Primary:
balance = 800

Replica:
balance = 500
Enter fullscreen mode Exit fullscreen mode

O Primary já processou a alteração, mas a Replica ainda não recebeu a atualização.

Esse intervalo é conhecido como Replication Lag (importante lembrar).

Replication Lag

Replication Lag é o atraso entre o estado do Primary e o estado de uma Replica.

Imagine:

T0:
Primary = 500
Replica = 500

T1:
Primary = 800
Replica = 500

T2:
Primary = 800
Replica = 800
Enter fullscreen mode Exit fullscreen mode

Entre T1 e T2, existe uma diferença entre os nós.

Essa diferença pode durar milissegundos, segundos ou até mais tempo dependendo do sistema, da carga e das condições da infraestrutura.

Esse conceito é extremamente importante porque pode produzir comportamento inesperados na aplicação.

O problema da leitura após escrita

Imagine o seguindo fluxo:

imagem10

A aplicação acabou de informar ao usuário que a alteração foi concluída, mas imediatamente depois ele consulta os dados e recebe a versão antiga.

Isso pode acontecer devido ao Replication Lag.

Esse é uim exemplo bem simples de consistência eventual em uma arquitetura de replicação assíncrona em banco de dados

Uma solução possível é direcionar determinadas leituras para o Primary após uma escrita

Por exemplo:

Write => Primary Database
Read imediatamente após => Primary Database
Outras leituras => Replicas (até todas as replicas receberam a atualização necessária)
Enter fullscreen mode Exit fullscreen mode

Mas isso precisa ser avaliado de acordo com o requisitos do sistema.

Quando usar replicação síncrona e assíncrona?

A decisão depende principalmente do quanto o sistema precisa garantir que uma alteração esteja replicada antes de confirmar a operação.

Replicação síncrona pode fazer sentido quando a perda de uma alteração confirmada é extremamente problemática e precisamos de maior garantia de persistência em múltiplos nós.

Replicação assíncrona pode fazer sentido quando precisamos de maior desempenho de escrita e podemos aceitar que as réplicas fiquem temporariamente atrasadas.

Não existe uma opção universalmente melhor, existe uma escolha baseada em requisitos.

Failover

Agora imagine que o Primary fique indisponível.

imagem11

Se possuimos réplicas, podemos promover uma delas para assumir a função de Primary.

Isso é chamado de Failover

Por exemplo:

Antes:

Primary Database
Replica 1
Replica 2

Depois:

Primary Database (off)
Replica 1 => novo Primary
Replica 2 => continua sendo réplica
Enter fullscreen mode Exit fullscreen mode

O objetivo é permitir que o sistema continue funcionando mesmo após a falha do nó principal.

Isso aumenta a disponibilidade do banco.

Failover automático

O failover pode ser realizado manualmente ou automaticamente, dependendo da infraestrutura utilizada.

Em um failover automático, algum mecanismo monitora os nós e detecta que o Primary não está mais disponível:

imagem12

Durante esse processo pode existir um período de indisponibilidade ou degradação.

Por isso, dizer que "replicação elimina downtime" seria incorreto. Ela reduz o impacto de determinadas falhas, mas o failover possui tempo e complexidade próprias.

O problema da perda de dados no Failover

Aqui temos um ponto importante relacionado à replicação assíncrona.

Imagine:

Primary Database:
A B C D

Replica:
A B C
Enter fullscreen mode Exit fullscreen mode

O Primary recebeu a operação D, mas a Replica ainda não recebeu.

Agora o Primary falha.

Se promovermos a Replica:

Novo Primary terá:
A B C
Enter fullscreen mode Exit fullscreen mode

A operação D pode ter sido perdida.

Esse é um dos principais tradeoffs da replicação assíncrona juntamente com o mecanismo de failover.

Quanto maior o Replication Lag no momento da falha, maior pode ser a quantidade de dados que ainda não foram propagados para uma réplica promovida à primary.

Replicação e disponibilidade

A principal ideia que devemos guardar é que replicação permite que o sistema tenha mais de um nó contendo os dados necessários para continuar funcionando.

Sem réplica:

imagem13

Com réplica + failover:

imagem14

Isso reduz a dependência de um único nó e também de um ponto único de falha.

Mas novamente, replicação não significa que o sistema automaticamente possui alta disponibilidade. Precisamos considerar o mecanismo de failover, detecção de falhas, promoção de réplicas, roteamento da aplicação e possíveis perdas ou atrasos de dados (replication lag).

Replicação e escalabilidade de leitura

Além de disponibilidade, replicação pode aumentar a capacidade de leitura

imagem15

As escritas continuam concetradas no Primary, enquanto as leituras podem ser distribuídas entre as réplicas.

Isso permite aumentar a capacidade de leitura adicionando novas réplicas.

Porém, isso não resolve necessariamente o problema de escritas em excesso. Se o Primary estiver sobrecarregado por milhares de operações de escrita, adicionar Read Replicas não resolverá esse gargalo.

Essa distinção será importante para quando estudarmos particionamento e Sharding.

O tradeoff central da replicação

Podemos assumir a replicação desta maneira:

Benefícios Custos
Disponibilidade Replication Lag
Escala de leitura Complexidade
Failover Consistência
Redundância Sincronização

Quanto mais réplicas temos, maior pode ser nossa capacidade de leitura e redundância, mas também aumentam os custos de infraestrutura e a complexidade de manter o sistema funcionando corretamente de acordo com a consistência e perca de dados nos casos de falha.

O que seria legal de guardar desse artigo

Replicação consiste em manter cópias dos mesmo dados em diferentes nós. Um modelo comum é o Primary/Replica ou também chamado de Leader/Follower, no qual o Primary recebe as escritas e as réplicas mantêm cópias desses dados. As réplicas podem ser utilizadas para aumentar a capacidade de leitura e também pode assumir o papel de Primary em caso de falhas através de um mecanismo de failover.

Na replicação síncrona, a escrita depende da confirmação de uma ou mais réplicas, oferecendo maior garantia de sincronização, mas potencialmente aumentando a latência.

Na replicação assíncrona, o Primary pode confirmar a escrita antes que as réplicas sejam atualizadas, proporcionando menor latência, mas permitindo Replication Lag e períodos de inconsistência entre os nós.

Portanto, replicação melhora disponibilidade e pode escalar leituras, mas introduz tradeoffs relacionados a consistência, latência, failover e complexidade.

Top comments (0)