Read Replicas: quando escalar leitura é melhor do que escalar o banco inteiro
Quando a gente começa a estudar otimização de banco de dados, um termo que aparece bastante é Read Replica.
A ideia parece simples:
Em vez de todo mundo ler e escrever no mesmo banco principal, criamos cópias do banco apenas para leitura.
Mas apesar de parecer simples, esse conceito resolve um problema muito comum em sistemas que começam a crescer: muitas leituras para poucas escritas.
O problema
Imagine uma aplicação onde temos:
- 1 operação de escrita;
- 1000 operações de leitura.
Isso é muito comum.
Exemplos:
- feed de posts;
- catálogo de produtos;
- dashboard;
- listagem de pedidos;
- perfil de usuário;
- sistema de comentários;
- relatório administrativo;
- página pública com muitos acessos.
Na maioria das aplicações, os usuários leem muito mais do que escrevem.
Um e-commerce, por exemplo, pode ter milhares de pessoas visualizando produtos ao mesmo tempo, mas poucas finalizando compras naquele exato segundo.
Se tudo isso bater no mesmo banco principal, o banco começa a sofrer.
O que é uma Read Replica?
Uma Read Replica é uma cópia do banco principal usada para consultas de leitura.
Normalmente a arquitetura fica mais ou menos assim:
┌──────────────────┐
│ Aplicação │
└───────┬──────────┘
│
┌─────────────┴─────────────┐
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ Primary Database │ │ Read Replica │
│ writes + reads │ │ only reads │
└──────────────────┘ └──────────────────┘
│ ▲
└──────── replication ──────┘
O banco principal, também chamado de primary, continua responsável pelas escritas:
- criar usuário;
- atualizar produto;
- registrar pedido;
- alterar saldo;
- inserir pagamento.
Já a réplica fica responsável por parte das leituras:
- buscar produtos;
- listar posts;
- carregar dashboards;
- exibir relatórios;
- consultar dados públicos.
Por que isso ajuda?
Porque você tira carga do banco principal.
Em vez de todas as queries caírem no mesmo lugar, você distribui as leituras entre uma ou mais réplicas.
Exemplo:
Writes:
App ───> Primary DB
Reads:
App ───> Read Replica 1
App ───> Read Replica 2
App ───> Read Replica 3
Isso melhora a escalabilidade porque leitura costuma ser mais fácil de distribuir do que escrita.
Escrita precisa manter consistência, ordem, transação, lock, integridade etc.
Leitura, em muitos casos, pode ser distribuída sem tanto problema.
Exemplo prático
Imagine um sistema de blog.
Quando alguém publica um post, essa escrita vai para o banco principal:
POST /posts
App -> Primary DB
Mas quando vários usuários acessam a home para ver os últimos posts, essas leituras podem ir para uma réplica:
GET /posts
App -> Read Replica
Isso evita que uma grande quantidade de acessos prejudique o banco principal.
O ponto importante: replicação não é instantânea
Aqui entra um detalhe muito importante.
A réplica geralmente recebe os dados do banco principal de forma assíncrona.
Ou seja, pode existir um pequeno atraso entre o dado ser escrito no banco principal e aparecer na réplica.
Isso é chamado de replication lag.
Exemplo:
- Usuário altera o nome do perfil;
- A escrita vai para o banco principal;
- Logo depois, a aplicação busca o perfil na réplica;
- A réplica ainda não recebeu a atualização;
- O usuário vê o nome antigo por alguns segundos.
Esse é um problema clássico de consistência eventual.
Então Read Replica pode retornar dado desatualizado?
Sim.
E isso não é necessariamente um bug.
É um trade-off.
Você ganha escala e reduz carga no banco principal, mas aceita que algumas leituras possam estar temporariamente atrasadas.
Por isso, nem toda leitura deveria ir para uma réplica.
Quando usar Read Replica?
Read Replicas fazem muito sentido quando você tem muitas leituras e poucas escritas.
Bons cenários:
- páginas públicas;
- dashboards;
- relatórios;
- listagens;
- catálogos;
- feeds;
- consultas analíticas leves;
- sistemas com muito tráfego de leitura.
Exemplo:
Catálogo de produtos -> pode usar réplica
Página de detalhes de produto -> pode usar réplica
Relatório mensal -> pode usar réplica
Feed de posts -> pode usar réplica
Nesses casos, se o dado estiver alguns milissegundos ou segundos atrasado, geralmente não é o fim do mundo.
Quando evitar Read Replica?
Você precisa tomar cuidado quando a leitura precisa refletir imediatamente uma escrita recente.
Exemplos:
- saldo bancário;
- confirmação de pagamento;
- carrinho de compra;
- estoque crítico;
- permissões de acesso;
- autenticação;
- dados sensíveis logo após update.
Imagine um usuário comprando o último item disponível de um produto.
Se a leitura do estoque vier de uma réplica atrasada, outro usuário pode achar que o produto ainda está disponível.
Nesse caso, talvez seja melhor consultar o banco principal ou usar outra estratégia de controle de concorrência.
Estratégia comum: write no primary, read na replica
Uma abordagem comum é separar as conexões:
Write Connection -> Primary DB
Read Connection -> Read Replica
No código, isso pode aparecer como dois clients diferentes:
const primaryDb = createConnection({
host: "primary-db"
});
const replicaDb = createConnection({
host: "read-replica"
});
Aí você decide:
// Escrita
await primaryDb.users.update({
where: { id: userId },
data: { name: "Luis" }
});
// Leitura
const users = await replicaDb.users.findMany();
Mas em casos onde você precisa ler imediatamente o dado atualizado, pode fazer a leitura no primary:
await primaryDb.users.update({
where: { id: userId },
data: { name: "Luis" }
});
const user = await primaryDb.users.findUnique({
where: { id: userId }
});
Isso evita o problema de ler um dado antigo da réplica logo depois de escrever.
Read Replica não resolve tudo
É importante entender que Read Replica não é bala de prata.
Ela ajuda principalmente quando o gargalo está nas leituras.
Se o problema do seu sistema está nas escritas, talvez você precise pensar em outras estratégias:
- otimização de queries;
- índices;
- particionamento;
- sharding;
- filas;
- batching;
- cache;
- denormalização;
- mudança no modelo de dados.
Read Replica resolve bem o cenário:
muitas leituras + poucas escritas
Mas não resolve tão bem:
muitas escritas concorrentes no mesmo ponto
Read Replica vs Cache
Uma dúvida comum é:
Se eu tenho cache, ainda preciso de read replica?
Depende.
Cache e Read Replica resolvem problemas parecidos, mas não são a mesma coisa.
Cache normalmente guarda dados em memória, como Redis, para evitar bater no banco.
Read Replica continua sendo um banco de dados, com dados replicados do primary.
Exemplo:
Cache -> mais rápido, mas pode expirar ou ser invalidado
Read Replica -> mais próxima do banco real, mas pode ter replication lag
Em sistemas maiores, é comum usar os dois.
App -> Cache
-> Read Replica
-> Primary DB
A aplicação tenta buscar no cache primeiro. Se não encontrar, busca na réplica. Se precisar escrever, manda para o banco principal.
Resumindo
Read Replica é uma estratégia para escalar leitura.
Ela permite que o banco principal foque mais nas escritas, enquanto uma ou mais réplicas atendem consultas de leitura.
Mas existe um trade-off importante: a réplica pode estar atrasada em relação ao banco principal.
Então a grande pergunta não é apenas:
"Posso usar Read Replica?"
A pergunta melhor é:
"Essa leitura precisa estar 100% atualizada agora?"
Se a resposta for sim, talvez você deva ler do primary.
Se a resposta for não, uma réplica pode ser uma excelente opção.
No fim, otimização de banco de dados é muito sobre entender o padrão de acesso da aplicação.
Antes de sair criando réplica, índice, cache ou shard, vale olhar para o básico:
- o sistema lê mais do que escreve?
- quais queries são mais executadas?
- quais dados precisam ser consistentes imediatamente?
- quais dados podem atrasar alguns segundos?
- onde realmente está o gargalo?
Read Replica é simples na teoria, mas a decisão de quando usar depende bastante do contexto.