Tecnologias/02 - Bancos de Dados/02 - Não Relacionais/01 - MongoDB6 min
Sharding por Dentro
Conceito
Sharding é a razão de existir do MongoDB em muitos projetos: é o único dos bancos desta trilha em que escala horizontal de escrita vem na caixa. No Postgres, MySQL e Oracle, isso é projeto (Sharding, Federation, Citus, Vitess) ou opção paga.
A topologia
app → mongos (roteador, sem estado — pode ter vários)
│
┌───────────────┼───────────────┐
│ │ │
shard A shard B shard C ← cada um é um REPLICA SET
(rs: P+2S) (rs: P+2S) (rs: P+2S)
└───────────────┴───────────────┘
│
config servers (replica set)
← guarda o MAPA: qual faixa vive em qual shard
Três papéis, e cada um falha de um jeito diferente:
mongos— roteia. Sem estado, mas cacheia o mapa de rotas; um mapa velho causa erro de rota (omongosse corrige buscando a versão nova).- Config servers — a fonte da verdade do mapa. Se caírem, o cluster fica somente leitura de metadados: não há balanceamento nem split, e escritas seguem no que já está mapeado.
- Shards — cada um um replica set completo (3. Transações, Replica Set e Durabilidade).
Chunks e o balanceador
O espaço da shard key é dividido em chunks (faixas contíguas). O balanceador (rodando nos config servers desde a 6.0) move chunks entre shards para equilibrar.
shard key = cliente_id (ranged)
chunk 1: [minKey .. 1000) → shard A
chunk 2: [1000 .. 5000) → shard B
chunk 3: [5000 .. maxKey) → shard C
Quando um chunk cresce além de chunkSize (padrão 128 MB), ele é dividido. A partir da
6.0/7.0 o critério de balanceamento passou a ser o tamanho de dado por shard, não a
contagem de chunks — o que corrigiu um desequilíbrio histórico.
A shard key: a decisão mais importante e mais difícil de reverter
Uma shard key precisa de três propriedades ao mesmo tempo:
| Propriedade | Por quê | Se falhar |
|---|---|---|
| Alta cardinalidade | define o número máximo de chunks | poucos chunks → não distribui |
| Baixa frequência | nenhum valor deve concentrar o volume | hot shard |
| Não monotônica | valores crescentes vão sempre para o último chunk | hot shard na escrita |
O erro clássico é a chave monotônica:
// ❌ timestamp ou ObjectId: TODA escrita nova cai no chunk de maxKey → um shard só
sh.shardCollection("app.eventos", { criado_em: 1 })
// ✅ hashed: distribui uniformemente (perde consulta por faixa)
sh.shardCollection("app.eventos", { _id: "hashed" })
// ✅✅ composta: distribui E mantém localidade útil por dispositivo
sh.shardCollection("app.eventos", { sensor_id: 1, criado_em: 1 })
A última é a melhor forma geral: um prefixo de alta cardinalidade e boa distribuição, com um sufixo que dá localidade (os eventos do mesmo sensor ficam juntos, ordenados).
Ranged vs hashed
| Ranged | Hashed | |
|---|---|---|
| Distribuição | pode desequilibrar | uniforme |
| Consulta por faixa | eficiente (vai a 1 shard) | scatter-gather (vai a todos) |
| Chave monotônica | hot shard | resolvido |
| Localidade de dado | preservada | destruída |
Targeted vs scatter-gather: a única métrica que importa
// ✅ TARGETED: a shard key está no filtro → 1 shard responde
db.eventos.find({ sensor_id: "s-42", criado_em: { $gte: ontem } })
// ❌ SCATTER-GATHER: sem a shard key → TODOS os shards são consultados,
// e o mongos precisa mesclar (e ordenar) os resultados
db.eventos.find({ tipo: "alerta" })
Num cluster de 10 shards, uma consulta scatter-gather faz 10 consultas e uma mesclagem. Se a maior parte do seu tráfego for scatter-gather, o sharding piorou a latência — você adicionou salto de rede e coordenação sem reduzir trabalho por nó.
O teste que decide se vale shardar: a shard key candidata aparece no filtro das consultas mais frequentes? Se não, escolha outra chave — ou não sharde.
Resharding: a saída que existe (e dói)
- 5.0+:
reshardCollectionreescreve a coleção com uma chave nova. - 7.0+: mais rápido e com menos indisponibilidade.
- Ainda assim: é uma operação longa, que precisa de espaço em disco extra, e com janela de bloqueio de escrita no final.
Antes disso havia apenas refineCollectionShardKey (que só acrescenta sufixos à chave
existente). Ou seja: hoje é possível corrigir uma escolha ruim — mas continua sendo uma
operação que ninguém quer fazer com o sistema em produção.
Trade-offs
Sharding só se paga acima de um limiar. Ele adiciona mongos, config servers,
scatter-gather, transações distribuídas mais caras e uma decisão irreversível-na-prática.
Um replica set num servidor grande resolve muito mais do que a maioria imagina. Sharde por
um destes motivos concretos:
- O working set não cabe na RAM do maior servidor viável (4. WiredTiger e Armazenamento).
- A vazão de escrita passou do que um primário aguenta.
- Requisito de localidade geográfica (zone sharding para residência de dados).
Transação distribuída em cluster sharded é bem mais cara. Ela precisa de coordenação em duas fases entre shards. Se o desenho exige transações que cruzam shards com frequência, a shard key está errada (ela deveria manter o agregado junto) — ou o modelo deveria ser relacional (4. MongoDB vs Relacional).
Índice único fica limitado. Numa coleção sharded, um índice unique só é possível se
contiver a shard key. Unicidade global em outro campo (e-mail, CPF) exige uma coleção
auxiliar com aquele campo como shard key — trabalho manual, que um relacional daria de
graça.
O trade-off central: o sharding do MongoDB troca um limite físico (um servidor) por um compromisso de modelagem (a shard key). Ele é a razão de escolher MongoDB para escala de escrita e, escolhido errado, é a razão de o cluster ser mais lento que a máquina única que existia antes.
Exemplo prático
Preparar e verificar
// habilitar e shardar
sh.enableSharding("app")
db.eventos.createIndex({ sensor_id: 1, criado_em: 1 }) // o índice DEVE existir antes
sh.shardCollection("app.eventos", { sensor_id: 1, criado_em: 1 })
// o estado do cluster
sh.status()
// distribuição real por shard — o número que revela hot shard
db.eventos.getShardDistribution()
// Shard shard-a: 4.2 GB (34%) 1_204_133 docs
// Shard shard-b: 4.0 GB (33%) 1_198_004 docs
// Shard shard-c: 4.1 GB (33%) 1_201_876 docs ← equilibrado 🎉
// a consulta é targeted?
db.eventos.find({ sensor_id: "s-42" }).explain()
// → "shards": { "shard-b": {...} } ← 1 shard: targeted ✅
db.eventos.find({ tipo: "alerta" }).explain()
// → SHARD_MERGE com os 3 shards ← scatter-gather ⚠️
Zone sharding: residência de dados
// LGPD/GDPR: dado de cliente europeu fica em hardware europeu
sh.addShardToZone("shard-eu-1", "EU")
sh.addShardToZone("shard-br-1", "BR")
sh.updateZoneKeyRange("app.clientes",
{ regiao: "EU", cliente_id: MinKey }, { regiao: "EU", cliente_id: MaxKey }, "EU")
sh.updateZoneKeyRange("app.clientes",
{ regiao: "BR", cliente_id: MinKey }, { regiao: "BR", cliente_id: MaxKey }, "BR")
É um caso em que o sharding se justifica sem necessidade de escala: o requisito é legal, não de capacidade.
Como isso falha em produção
| Sintoma | Causa |
|---|---|
| Um shard com 90% da escrita | shard key monotônica (timestamp/ObjectId) |
| Um shard com 90% da leitura | shard key de baixa frequência (um tenant gigante) |
| Toda query lenta após shardar | tráfego dominado por scatter-gather |
jumbo chunk que não move |
chunk maior que o limite, com uma única shard key dentro (chave de cardinalidade baixa) |
| Balanceador nunca termina | janela de balanceamento curta, ou I/O saturado |
duplicate key que não deveria |
índice unique não pode ser global sem a shard key |
| Transação distribuída lenta/abortando | agregado espalhado entre shards |
| Cluster "sem metadados" | config servers indisponíveis |
Aprofundamento aplicado
Distribuição de dados e distribuição de tráfego
Uma coleção pode ter bytes bem distribuídos e quase todas as requisições atingir um único cliente. Balancear armazenamento não divide automaticamente uma chave lógica muito disputada. Uma chave hashed distribui valores diferentes, mas o mesmo valor continua recebendo o mesmo resultado de hash. Referência: escolha de shard key.
Cenário: um cliente concentra 60% das escritas. Acrescentar uma dimensão à chave pode espalhar esse cliente, mas consultar todos os seus pedidos passa a tocar mais partes. O projeto troca localidade de leitura por distribuição de escrita.
Plano de validação
Liste consultas principais, campos de filtro, cardinalidade da chave e distribuição de volume por cliente. Depois verifique quantos shards cada consulta alcança e como a latência muda quando uma das partes fica lenta.
Exercício: compare (clienteId) e uma chave que subdivide pedidos do cliente.
Resultado esperado: identifique qual desenho direciona uma leitura pontual, qual
espalha um cliente grande e quais consultas exigem juntar resultados de várias partes.
Relacionado
- 1. MongoDB
- 2. Modelagem de Documentos
- 3. Transações, Replica Set e Durabilidade
- 4. WiredTiger e Armazenamento
- 5. Query Planner e Índices
- Sharding
- Federation
- DynamoDB Capacidade e Índices
Parte de MOC MongoDB · Sharding (docs)