Estratégias de sharding de banco de dados

1. Fundamentos do Sharding e Quando Aplicá-lo

1.1. Definição de sharding: fragmentação horizontal vs. vertical

Sharding é uma técnica de escalabilidade horizontal que consiste em particionar um banco de dados grande em fragmentos menores e independentes chamados shards. Diferentemente da fragmentação vertical, que separa colunas de uma tabela, o sharding fragmenta linhas horizontalmente, distribuindo registros entre múltiplos servidores.

Fragmentação Vertical:
Tabela Usuários → Shard 1: (id, nome) | Shard 2: (id, email, senha)

Fragmentação Horizontal (Sharding):
Shard 1: Usuários com id 1-1000
Shard 2: Usuários com id 1001-2000
Shard 3: Usuários com id 2001-3000

1.2. Problemas resolvidos

O sharding ataca três gargalos principais:
- Gargalos de escrita: Quando um único servidor não consegue processar o volume de inserts/updates
- Limites de armazenamento: Quando o dataset excede a capacidade de armazenamento de uma única máquina
- Contenção de recursos: Quando consultas concorrentes sobrecarregam CPU, memória e I/O

1.3. Critérios de decisão

Antes de implementar sharding, avalie:
- Volume de dados > 1TB ou taxa de crescimento > 20% ao mês
- Necessidade de SLA de escrita < 10ms sob carga elevada
- Impossibilidade de otimização via índices, caching ou replicação

2. Arquiteturas de Sharding: Abordagens Principais

2.1. Sharding baseado em chave de partição (hash-based)

Aplica uma função hash à chave de sharding para determinar o shard destino. Garante distribuição uniforme, mas dificulta consultas por intervalo.

Função hash: shard_id = hash(user_id) % 3

user_id = 12345 → hash(12345) % 3 = 2 → Shard 2
user_id = 67890 → hash(67890) % 3 = 0 → Shard 0

2.2. Sharding por intervalo de valores (range-based)

Divide dados com base em faixas contínuas de valores. Facilita consultas por intervalo, mas pode causar distribuição desigual.

Shard 0: user_id 0-9999
Shard 1: user_id 10000-19999
Shard 2: user_id 20000-29999

Consulta: SELECT * FROM usuarios WHERE user_id BETWEEN 15000 AND 18000 → Shard 1

2.3. Sharding por diretório (lookup table)

Mantém uma tabela de mapeamento explícita entre chaves e shards. Flexível, mas introduz latência adicional e ponto único de falha.

Tabela de Roteamento:
+---------+---------+
| user_id | shard   |
+---------+---------+
| 12345   | shard_2 |
| 67890   | shard_0 |
| 11111   | shard_1 |
+---------+---------+

3. Estratégias de Distribuição de Dados entre Shards

3.1. Distribuição uniforme vs. baseada em carga de trabalho

Distribuição uniforme busca igualdade de registros por shard. Distribuição baseada em carga considera padrões de acesso — shards com dados "quentes" recebem mais recursos.

3.2. Sharding geográfico para latência e conformidade regulatória

Ideal para aplicações globais: dados de usuários europeus em shards na Europa (GDPR), dados americanos nos EUA.

Shard US-East: clientes com país = 'US'
Shard EU-West: clientes com país = 'DE', 'FR', 'UK'
Shard AP-Southeast: clientes com país = 'JP', 'AU'

3.3. Rebalanceamento dinâmico de shards

Consistent Hashing minimiza movimentação de dados ao adicionar/remover shards. Cada shard recebe um intervalo no anel hash.

Anel hash com 3 shards:
Shard A: hash 0-170
Shard B: hash 171-340
Shard C: hash 341-511

Ao adicionar Shard D, apenas dados adjacentes são movidos.

4. Roteamento de Consultas em Ambientes Shardeados

4.1. Roteamento no lado do cliente (client-side routing)

A aplicação calcula o shard destino usando lógica embutida. Rápido, mas exige que todos os clientes conheçam a topologia.

// Exemplo de roteamento client-side
function getShardConnection(userId):
    shardId = hash(userId) % 3
    return connections[shardId]

4.2. Roteamento via proxy de banco de dados

Um proxy intermediário (como ProxySQL, Vitess ou MongoDB Router) decide o shard. Transparente para a aplicação.

Aplicação → Proxy (Vitess) → Shard 0 | Shard 1 | Shard 2

4.3. Estratégias para consultas cross-shard

Scatter-gather envia a consulta para todos os shards e agrega resultados no proxy ou aplicação.

Consulta: SELECT COUNT(*) FROM usuarios WHERE status = 'ativo'

Shard 0: 1500 registros ativos
Shard 1: 2300 registros ativos
Shard 2: 1800 registros ativos
Resultado agregado: 5600

5. Gerenciamento de Transações e Consistência entre Shards

5.1. Transações distribuídas: 2PC (Two-Phase Commit)

Protocolo que coordena commits em múltiplos shards. Garante consistência, mas adiciona latência.

Fase 1 (Prepare):
Coordenador → Shard A: "Prepare para commit"
Coordenador → Shard B: "Prepare para commit"
Shard A: "OK" | Shard B: "OK"

Fase 2 (Commit):
Coordenador → Shard A: "Commit"
Coordenador → Shard B: "Commit"

5.2. Consistência eventual e estratégias de reconciliação

Para sistemas de alta disponibilidade, aceita-se consistência eventual com processos de reconciliação periódica.

5.3. Chaves globais únicas

UUIDs, Snowflake IDs (Twitter) ou sequencers centralizados garantem unicidade entre shards.

Snowflake ID (64 bits):
1 bit: sinal (0)
41 bits: timestamp (milissegundos)
10 bits: worker ID
12 bits: sequência (4096 IDs/ms)

6. Monitoramento, Manutenção e Resiliência de Shards

6.1. Métricas críticas

  • Latência por shard (p95, p99)
  • Skew de dados (desvio padrão do tamanho dos shards)
  • Heat spots (shards com acesso desproporcional)

6.2. Estratégias de backup e restore

Backup consistente requer snapshot coordenado entre shards. Ferramentas como pg_dump paralelo ou xtrabackup para MySQL.

6.3. Failover e replicação entre shards

Cada shard deve ter réplicas em diferentes zonas de disponibilidade. Em caso de falha, promove-se a réplica.

7. Estudos de Caso e Padrões Antipadrões

7.1. Exemplo prático: sharding de tabela de usuários com hash por ID

Configuração inicial:
- 3 shards (shard0, shard1, shard2)
- Função hash: user_id % 3

Inserção:
INSERT INTO usuarios (id, nome, email) VALUES (101, 'João', 'joao@email.com')
→ hash(101) % 3 = 2 → shard2

INSERT INTO usuarios (id, nome, email) VALUES (202, 'Maria', 'maria@email.com')
→ hash(202) % 3 = 1 → shard1

Consulta por ID:
SELECT * FROM usuarios WHERE id = 101
→ Roteia diretamente para shard2

7.2. Armadilhas comuns

  • Shard overload: Um shard recebe 80% das requisições por causa de hot keys
  • Joins cross-shard: Consultas JOIN entre shards são extremamente lentas
  • Hot keys: Um usuário famoso ou produto popular concentra tráfego em um shard

7.3. Quando evitar sharding

Alternativas mais simples antes de shardear:
- Replicação (read replicas para escalar leituras)
- Particionamento local (PARTITION BY RANGE no MySQL)
- Caching (Redis/Memcached para dados quentes)
- Indexação adequada

Referências