← Back to home

Escalando Bancos de Dados

By Pscodium · 8/4/2026 · 2 views

Escalando Bancos de Dados — Replicação, Sharding e o Perigo da Complexidade Prematura

Conforme uma aplicação cresce, chega um momento em que o banco de dados começa a se tornar um gargalo.

No início, a arquitetura costuma ser simples:

              API
               │
               ▼
           DATABASE

A aplicação cresce:

              API
               │
               ▼
           DATABASE
               ▲
               │
        100.000 Requests

O banco começa a receber:

  • Mais leituras
  • Mais escritas
  • Mais conexões
  • Mais consultas simultâneas
  • Mais dados
  • Consultas mais complexas

Em algum momento, simplesmente aumentar a capacidade da máquina pode deixar de ser suficiente.

Nesse ponto, existem duas estratégias fundamentais para escalar um banco de dados:

REPLICAÇÃO
    │
    └── Escalar LEITURAS

SHARDING
    │
    └── Escalar ESCRITAS e CAPACIDADE

A ideia simplificada é:

Replicação distribui cópias dos dados. Sharding distribui os próprios dados.

Essa diferença é fundamental.


1. O primeiro passo: escalar verticalmente

Antes de pensar em replicação ou sharding, normalmente começamos com:

Vertical Scaling

Também conhecido como:

Scale Up

Temos:

DATABASE
    │
    ▼
┌───────────────────┐
│  CPU              │
│  RAM              │
│  SSD              │
└───────────────────┘

Podemos aumentar:

CPU
RAM
IOPS
Storage

Por exemplo:

8 CPU
32 GB RAM
     │
     ▼
16 CPU
64 GB RAM
     │
     ▼
32 CPU
128 GB RAM

Essa estratégia é simples.

Não precisamos alterar a aplicação.

Não precisamos distribuir os dados.

Não precisamos sincronizar múltiplos bancos.

Por isso:

Sempre que possível, comece pelo simples.

O problema é que existe um limite físico e financeiro.

Uma máquina eventualmente pode ficar:

Cara demais

ou:

Insuficiente

É nesse momento que começamos a considerar:

Replicação

ou:

Sharding

2. Replicação

Replicação significa manter múltiplas cópias dos mesmos dados.

Imagine:

              DATABASE
                 │
        ┌────────┼────────┐
        ▼        ▼        ▼
      DB 1     DB 2     DB 3

Todos possuem os mesmos dados.

Por exemplo:

DB 1
User 123
Name = João

DB 2
User 123
Name = João

DB 3
User 123
Name = João

A vantagem é que podemos distribuir operações de leitura.


3. Primary e Replicas

Uma arquitetura muito comum é:

                  PRIMARY
                     │
          ┌──────────┴──────────┐
          │                     │
          ▼                     ▼
       REPLICA 1             REPLICA 2

A aplicação escreve no:

PRIMARY

E as réplicas recebem os dados:

PRIMARY
    │
    │ Replication
    ├──────────────► Replica 1
    │
    └──────────────► Replica 2

As leituras podem ser distribuídas:

                  LOAD BALANCER
                       │
              ┌────────┴────────┐
              ▼                 ▼
          Replica 1         Replica 2

Agora:

10.000 Reads
     │
     ├── 5.000 → Replica 1
     │
     └── 5.000 → Replica 2

O Primary fica responsável principalmente pelas escritas.


4. Por que replicação escala leituras?

Imagine uma aplicação com:

100.000 operações

Dessas:

95.000 → READ
5.000  → WRITE

Temos uma proporção:

95% READ
5% WRITE

Nesse cenário, replicação é extremamente eficiente.

Podemos ter:

             PRIMARY
                │
        ┌───────┼───────┐
        ▼       ▼       ▼
       R1      R2      R3

As leituras são distribuídas:

READ ──► R1
READ ──► R2
READ ──► R3

Enquanto:

WRITE ──► PRIMARY

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


5. O limite da replicação

Existe um problema importante.

As réplicas são cópias.

Portanto, alguém precisa atualizar todas elas.

Imagine:

PRIMARY
    │
    ├──► Replica 1
    ├──► Replica 2
    ├──► Replica 3
    └──► Replica 4

Agora temos:

1 Primary
4 Replicas

A quantidade de leituras que podemos suportar aumentou.

Mas as escritas ainda passam pelo:

PRIMARY

Se tivermos:

1.000.000 Writes

continuaremos tendo:

1.000.000 Writes
        │
        ▼
     PRIMARY

Adicionar réplicas não resolve esse gargalo.

Por isso:

Replicação é excelente para escalar leituras, mas não resolve diretamente o gargalo de escrita em um único Primary.


6. Replicação assíncrona

Uma estratégia comum é utilizar replicação assíncrona.

O fluxo é:

Application
     │
     ▼
  PRIMARY
     │
     │
     ├──────────► Replica 1
     │
     └──────────► Replica 2

O Primary confirma a escrita:

WRITE
  │
  ▼
PRIMARY
  │
  ▼
OK

E depois as réplicas recebem a atualização.

Isso reduz a latência de escrita.

Porém, existe um possível atraso:

PRIMARY
User = João

Replica
User = Pedro

Durante um curto período.

Esse fenômeno é conhecido como:

Replication Lag


7. Replication Lag

Imagine:

PRIMARY
User = João

Executamos:

UPDATE User
SET Name = 'Maria'

O Primary agora possui:

Maria

Mas a réplica ainda pode ter:

João

Durante alguns milissegundos:

PRIMARY  → Maria
REPLICA  → João

Isso pode gerar um problema:

Usuário atualiza seu nome
        │
        ▼
WRITE → Primary
        │
        ▼
"Maria"
        │
        ▼
READ → Replica
        │
        ▼
"João"

O usuário pode pensar:

"Minha alteração não foi salva."

Na realidade:

Database
   │
   ├── Primary → Maria
   │
   └── Replica → João

A réplica ainda não sincronizou.


8. Read Your Own Writes

Uma solução comum para problemas de consistência é garantir que, após uma escrita, o usuário leia do Primary durante algum tempo.

Por exemplo:

WRITE
  │
  ▼
PRIMARY
  │
  ▼
READ
  │
  ▼
PRIMARY

Depois:

READ
  │
  ▼
Replica

Isso permite aproveitar as réplicas sem criar uma experiência inconsistente imediatamente após uma escrita.


9. Replicação e Alta Disponibilidade

Replicação também pode ajudar na disponibilidade.

Imagine:

          PRIMARY
             │
      ┌──────┴──────┐
      ▼             ▼
   Replica 1     Replica 2

O Primary falha:

          PRIMARY
             X

O sistema pode promover uma réplica:

Replica 1
    │
    ▼
NEW PRIMARY

Agora:

Replica 2
     │
     ▼
Replica

O sistema volta a operar.

Isso é conhecido como:

Failover

Portanto, replicação pode fornecer:

Escalabilidade de leitura
+
Alta disponibilidade

Mas:

Alta disponibilidade não significa que a aplicação ficará automaticamente 100% disponível.

O processo de failover pode gerar:

  • Pequeno downtime
  • Perda de algumas transações em replicação assíncrona
  • Reconexões
  • Reeleição
  • Reconfiguração da aplicação

10. Quando usar replicação?

Replicação é uma excelente escolha quando:

READS >>> WRITES

Por exemplo:

95% Reads
5% Writes

Também é útil quando precisamos de:

  • Alta disponibilidade
  • Failover
  • Distribuição geográfica de leituras
  • Redução de latência de leitura

Uma arquitetura poderia ser:

                    USERS
                      │
                      ▼
                 APPLICATION
                      │
             ┌────────┴────────┐
             │                 │
          WRITES             READS
             │                 │
             ▼                 ▼
          PRIMARY         READ REPLICAS
                               │
                       ┌───────┼───────┐
                       ▼       ▼       ▼
                      R1      R2      R3

11. Sharding

Agora imagine que o problema não são apenas as leituras.

Imagine que temos:

1.000.000 Writes/second

Temos:

              PRIMARY
                 │
                 ▼
          1.000.000 Writes

Adicionar mais réplicas não resolve.

Precisamos dividir os dados.

Isso é:

Sharding

Sharding significa dividir um banco de dados em múltiplas partes.

Cada parte é chamada de:

Shard

Imagine:

             DATABASE
                 │
       ┌─────────┼─────────┐
       ▼         ▼         ▼
    SHARD 1   SHARD 2   SHARD 3

Cada shard contém uma parte dos dados.

Por exemplo:

Shard 1
Users 1 - 1.000.000

Shard 2
Users 1.000.001 - 2.000.000

Shard 3
Users 2.000.001 - 3.000.000

Agora as escritas podem ser distribuídas:

Write User 123
      │
      ▼
   Shard 1

Write User 1.500.000
      │
      ▼
   Shard 2

Write User 2.500.000
      │
      ▼
   Shard 3

Cada shard possui sua própria capacidade.


12. Sharding Horizontal

Sharding é uma forma de:

Horizontal Scaling

Também chamado:

Scale Out

Em vez de:

1 servidor enorme

temos:

3 servidores menores

Cada um processando uma parte dos dados.

       DATABASE
           │
    ┌──────┼──────┐
    ▼      ▼      ▼
 Shard 1 Shard 2 Shard 3

A capacidade total aumenta.


13. Shard Key

Para saber em qual shard um dado deve ser armazenado, precisamos de uma:

Shard Key

Por exemplo:

user_id

Podemos utilizar:

hash(user_id) % 3

Resultado:

User 123
   │
   ▼
Hash
   │
   ▼
Shard 1

Outro:

User 456
   │
   ▼
Hash
   │
   ▼
Shard 2

A aplicação ou uma camada de roteamento determina onde cada dado está.


14. Range-Based Sharding

Uma estratégia é dividir por intervalos.

Por exemplo:

Shard 1
ID 1 - 1.000.000

Shard 2
ID 1.000.001 - 2.000.000

Shard 3
ID 2.000.001 - 3.000.000

É simples de entender.

Porém, pode criar um problema:

Hot Shard

Imagine que todos os novos usuários recebem IDs sequenciais.

As novas escritas sempre vão para:

Shard 3

Enquanto:

Shard 1
Shard 2

ficam praticamente ociosos.

Temos:

Shard 1 → 10%
Shard 2 → 10%
Shard 3 → 80%

Isso é uma distribuição ruim.


15. Hash-Based Sharding

Outra estratégia é usar hash.

hash(user_id)
       │
       ▼
   Shard escolhido

Por exemplo:

hash(123) % 3 → Shard 1
hash(456) % 3 → Shard 2
hash(789) % 3 → Shard 3

Isso geralmente distribui melhor os dados.

Porém, existe um problema.

Imagine:

3 Shards

E depois precisamos de:

5 Shards

Se o cálculo for:

hash(key) % 3

ao mudar para:

hash(key) % 5

muitos dados precisam mudar de shard.

Isso é:

Resharding

E pode ser extremamente complexo.


16. Consistent Hashing

Uma alternativa é utilizar:

Consistent Hashing

Em vez de:

hash(key) % N

temos um espaço de hash.

Imagine:

          HASH RING

       Shard A
          │
    ┌─────┴─────┐
    │           │
Shard C       Shard B

As chaves são distribuídas pelo anel.

Quando adicionamos um shard:

Shard D

apenas parte das chaves precisa ser redistribuída.

Isso reduz o custo de:

Resharding

Consistent hashing é muito utilizado em sistemas distribuídos que precisam crescer dinamicamente.


17. O problema das consultas entre shards

Sharding traz uma complexidade enorme.

Imagine:

Users
Orders
Products

Podemos ter:

Shard 1 → Users A
Shard 2 → Users B
Shard 3 → Users C

Agora precisamos consultar:

SELECT *
FROM users
WHERE country = 'Brazil';

Se não sabemos em qual shard estão os dados:

Request
   │
   ├──► Shard 1
   ├──► Shard 2
   └──► Shard 3

Precisamos consultar todos.

Isso é conhecido como:

Scatter-Gather

Depois:

Shard 1 ──┐
Shard 2 ──┼──► Aggregator
Shard 3 ──┘

O resultado precisa ser combinado.

Isso aumenta:

  • Latência
  • Complexidade
  • Uso de CPU
  • Tráfego de rede

18. Joins entre shards

Imagine:

SELECT *
FROM users
JOIN orders
ON users.id = orders.user_id;

Se:

User
   │
   ▼
Shard 1

e:

Orders
   │
   ▼
Shard 3

O banco não consegue simplesmente fazer:

JOIN local

Agora precisamos realizar:

Shard 1
    │
    │ Network
    ▼
Shard 3

Ou:

Shard 1 ───┐
           ├──► Application
Shard 3 ───┘

A aplicação pode precisar combinar os dados.

Isso torna o sistema muito mais complexo.


19. Transações distribuídas

Imagine uma operação:

Transferir R$ 100

Precisamos atualizar:

Conta A → Shard 1
Conta B → Shard 2

Agora temos:

Shard 1
   │
   │ UPDATE
   ▼
Conta A - R$ 100

Shard 2
   │
   │ UPDATE
   ▼
Conta B + R$ 100

E se:

Shard 1 → SUCESSO
Shard 2 → FALHA

Temos:

Conta A → -R$ 100
Conta B → Não recebeu

Agora precisamos de mecanismos de:

  • Distributed Transactions
  • Two-Phase Commit
  • Saga Pattern
  • Compensating Transactions

A complexidade aumenta significativamente.


20. Por que sharding é tão complexo?

Quando fazemos sharding, precisamos resolver:

Routing
Shard Key
Resharding
Cross-Shard Queries
Cross-Shard Joins
Distributed Transactions
Hot Shards
Operational Complexity

Por isso:

Sharding não deve ser a primeira solução para um banco que está ficando lento.


21. O grande erro: Sharding prematuro

Um dos maiores erros em arquitetura é introduzir sharding antes de realmente precisar.

Imagine uma aplicação com:

10.000 usuários

E alguém decide:

Vamos criar 10 shards!

Arquitetura:

          Application
               │
        ┌──────┼──────┐
        ▼      ▼      ▼
      S1      S2      S3
        ...
       S10

Agora temos:

  • 10 bancos
  • 10 monitoramentos
  • 10 backups
  • 10 migrations
  • 10 conexões
  • Routing
  • Resharding
  • Queries distribuídas

Tudo isso para um sistema que talvez pudesse funcionar perfeitamente com:

1 Database

O resultado é:

Complexidade ↑
Custo ↑
Bugs ↑
Operação ↑

Sem necessariamente existir:

Performance ↑

22. A arquitetura mais simples possível

Uma progressão saudável pode ser:

ETAPA 1

Application
    │
    ▼
Database

Depois:

ETAPA 2

Application
    │
    ▼
Bigger Database

Depois:

ETAPA 3

Application
    │
    ▼
Primary
    │
    ├──► Replica 1
    └──► Replica 2

Depois:

ETAPA 4

Application
    │
    ▼
Primary
    │
    ├──► Replica 1
    ├──► Replica 2
    └──► Replica 3

E somente quando necessário:

ETAPA 5

Application
    │
    ▼
Shard Router
    │
    ├──► Shard 1
    ├──► Shard 2
    └──► Shard 3

Essa progressão evita complexidade prematura.


23. A ordem correta para escalar

Antes de fazer sharding, devemos analisar outras alternativas.

Uma progressão comum:

1. Otimizar queries
        │
        ▼
2. Criar índices
        │
        ▼
3. Corrigir N+1 queries
        │
        ▼
4. Aumentar hardware
        │
        ▼
5. Adicionar cache
        │
        ▼
6. Replicar leituras
        │
        ▼
7. Separar workloads
        │
        ▼
8. Sharding

A ordem exata depende do sistema, mas a ideia é:

Não introduza a solução mais complexa antes de eliminar os problemas mais simples.


24. Otimização antes da distribuição

Imagine uma consulta:

SELECT *
FROM users
WHERE email = 'user@email.com';

Sem índice:

Database
    │
    ▼
Scan 10.000.000 rows

Criamos:

INDEX email

Agora:

Database
    │
    ▼
Index Lookup
    │
    ▼
1 row

O problema foi resolvido sem:

Replicação

e sem:

Sharding

Por isso:

O primeiro passo para escalar um banco é descobrir o que realmente está lento.


25. Replicação vs Sharding

CaracterísticaReplicaçãoShardingDadosDuplicadosDivididosPrincipal objetivoEscalar leiturasEscalar capacidade/escritasAlta disponibilidadeSimPode ajudarRead ScalingExcelentePossívelWrite ScalingLimitadoExcelenteComplexidadeModeradaAltaCross-shard queriesNãoProblemaReshardingNãoSimDistributed TransactionsNormalmente nãoPode ser necessárioOperaçãoMais simplesMais complexa

A diferença fundamental:

REPLICAÇÃO

Mesmo dado
   │
   ├──► DB 1
   ├──► DB 2
   └──► DB 3

Enquanto:

SHARDING

Dados diferentes
   │
   ├──► Shard 1
   ├──► Shard 2
   └──► Shard 3

26. Podemos combinar os dois

Sistemas grandes frequentemente utilizam:

Sharding + Replicação

Por exemplo:

                    APPLICATION
                         │
                    SHARD ROUTER
                         │
          ┌──────────────┼──────────────┐
          ▼              ▼              ▼
       SHARD 1        SHARD 2        SHARD 3
          │              │              │
      ┌───┴───┐      ┌───┴───┐      ┌───┴───┐
      ▼       ▼      ▼       ▼      ▼       ▼
   Primary Replica Primary Replica Primary Replica

Agora:

Sharding
    │
    └── Distribui dados e escritas

Replication
    │
    └── Distribui leituras e aumenta disponibilidade

Essa combinação permite escalar grandes volumes.

Mas também combina as complexidades.


27. Como saber quando usar replicação?

Pergunte:

Meu problema é principalmente READ?

Se:

SIM

Considere:

Read Replicas

Se temos:

100 Reads
10 Writes

Replicação provavelmente é uma ótima solução.

Também considere replicação quando precisa de:

Failover
Alta disponibilidade
Leituras geograficamente distribuídas

28. Como saber quando usar sharding?

Pergunte:

Meu problema é WRITE?

E:

Meu Primary chegou ao limite?

E:

A máquina não consegue crescer verticalmente?

E:

O volume total de dados excede a capacidade de uma máquina?

Se a resposta for sim para vários desses pontos:

Sharding

pode ser necessário.


29. O papel das métricas

Nunca devemos decidir por:

"Meu sistema está crescendo."

Devemos observar métricas.

Por exemplo:

CPU
RAM
Disk I/O
IOPS
Connections
Query Latency
Transactions/sec
Reads/sec
Writes/sec
Replication Lag
Storage

Imagine:

CPU = 30%
RAM = 40%
Writes = 10.000/s

Talvez não precisemos de sharding.

Agora:

CPU = 100%
RAM = 95%
Disk I/O = 100%
Writes = 200.000/s

Talvez seja necessário escalar.

A decisão deve ser baseada em:

MÉTRICAS
    │
    ▼
GARGALO
    │
    ▼
SOLUÇÃO

Nunca:

Crescimento
    │
    ▼
Sharding

30. Uma árvore de decisão

Podemos pensar assim:

Banco está lento?
       │
       ▼
Identificar gargalo
       │
       ├── Query lenta
       │      │
       │      ▼
       │   Otimizar
       │
       ├── Falta de índice
       │      │
       │      ▼
       │   Criar índice
       │
       ├── Memória/CPU
       │      │
       │      ▼
       │   Scale Up
       │
       ├── Muitas leituras
       │      │
       │      ▼
       │   Read Replicas
       │
       └── Muitas escritas
              │
              ▼
           Sharding?
              │
              ▼
         Só se necessário

31. O principal insight

A melhor arquitetura não é:

A mais distribuída

É:

A mais simples
que atende aos requisitos

Se um banco único suporta:

10 milhões de usuários

não existe motivo técnico para utilizar:

100 shards

A arquitetura deve evoluir conforme os gargalos aparecem.


32. Resumo

Replicação

Database
   │
   ├──► Replica 1
   ├──► Replica 2
   └──► Replica 3

Ideal para:

READ SCALING

Também pode ajudar em:

Alta disponibilidade
Failover

Sharding

Database
   │
   ├──► Shard 1
   ├──► Shard 2
   └──► Shard 3

Ideal para:

WRITE SCALING

E para:

Capacidade total

Replicação

Mesmo dado
Duplicado

Sharding

Dados diferentes
Distribuídos

Conclusão

Quando um banco de dados começa a atingir seus limites, existem duas estratégias fundamentais:

REPLICAÇÃO

e:

SHARDING

Replicação é normalmente o primeiro passo para sistemas com grande volume de leituras:

                    PRIMARY
                       │
              ┌────────┴────────┐
              ▼                 ▼
          Replica 1         Replica 2

Ela permite distribuir leituras e melhorar a disponibilidade, mas não elimina o gargalo de escrita no Primary.

Quando o problema está na capacidade de escrita ou no tamanho total dos dados, podemos considerar sharding:

                  ROUTER
                    │
          ┌─────────┼─────────┐
          ▼         ▼         ▼
       SHARD 1   SHARD 2   SHARD 3

Porém, o custo é muito maior.

Sharding introduz:

  • Roteamento
  • Escolha de Shard Key
  • Resharding
  • Hot Shards
  • Scatter-Gather
  • Cross-Shard Joins
  • Transações distribuídas
  • Maior complexidade operacional

Por isso, um dos maiores erros de arquitetura é fazer:

"Minha aplicação está crescendo"
        │
        ▼
"Vamos fazer sharding"

O caminho correto é:

Medir
  │
  ▼
Identificar o gargalo
  │
  ▼
Otimizar
  │
  ▼
Scale Up
  │
  ▼
Cache
  │
  ▼
Read Replicas
  │
  ▼
Sharding

Não existe medalha por usar a arquitetura mais complexa.

O objetivo de um bom arquiteto é encontrar o menor nível de complexidade capaz de resolver o problema atual.

A regra mental pode ser resumida assim:

                    BANCO
                      │
                      ▼
             Qual é o gargalo?
                      │
          ┌───────────┴───────────┐
          ▼                       ▼
       LEITURA                  ESCRITA
          │                       │
          ▼                       ▼
     REPLICAÇÃO                SHARDING
          │                       │
          ▼                       ▼
     Read Scaling              Write Scaling

E a regra mais importante:

Não faça sharding porque você imagina que um dia precisará dele. Faça sharding quando as métricas mostrarem que você realmente precisa dele.

Até lá, mantenha o sistema simples.

Porque, em System Design, complexidade também é um custo de infraestrutura.


Comments

No comments yet.