Sharding — particionar dados entre múltiplos servidores para que cada um tenha apenas uma fatia — é a ferramenta de último recurso em escalabilidade de banco. Quando vertical não basta (banco passou do hardware máximo viável), quando replicação não basta (writes saturam o primário), e quando cache não basta (dados quentes demais para caber em RAM), sobra sharding. Os benefícios são reais: capacidade efetivamente ilimitada, isolamento de carga entre tenants. Os custos são qualitativos: queries cross-shard ficam complicadas, transações distribuídas viram dor, e a escolha de partition key é tipicamente irreversível.
A história canônica de sharding começa no início dos anos 2000 com sistemas de internet em escala (Google, Amazon, Facebook, Twitter). Em 2010, Twitter publicou sobre Gizzard (sharding manual de MySQL); em 2007, Amazon publicou Dynamo (sharding por consistent hashing). Em 2015, Facebook articulou em "Scaling Memcache" como faziam sharding de cache em escala planetária. Cada um desses sistemas pagou complexidade significativa para conseguir escala — e cada um valeu o custo porque a alternativa não existia.
Em 2026, sharding é mais acessível. Bancos distribuídos modernos (CockroachDB, TiDB, Spanner, YugaByteDB) abstraem a mecânica — o usuário define partition key e o banco gerencia. DynamoDB, Cassandra, MongoDB sharded, Aurora Limitless — todos automatizam coisas que antes eram manuais. Mas a escolha estratégica — qual partition key usar, qual estratégia, qual conjunto de queries será privilegiado — continua sendo decisão humana de arquitetura, não automatizável.
Este conceito articula sharding como decisão consciente. Estratégias canônicas (range, hash, geo, tenant), o problema de hot shards e mitigations, resharding online, e os anti-padrões que aparecem em times que adotam sharding sem articular trade-offs. O conceito 04 (consistent hashing) cobriu a mecânica geral; aqui o foco é a decisão estratégica em banco transacional.
Sharding versus partitioning
Os termos são usados intercambiavelmente no cotidiano, mas há nuance.
Partitioning é o conceito geral: dividir um conjunto grande em subconjuntos menores. Pode ser vertical (separar colunas) ou horizontal (separar linhas).
Sharding é tipicamente sinônimo de partitioning horizontal distribuído entre múltiplos servidores. Cada shard vive em um servidor diferente.
Partitioning local também existe:
uma única instância de banco com tabela
particionada por mês/região/etc. PostgreSQL
PARTITION BY, MySQL partitioning. Útil
para gerenciar tabelas grandes (vacuum mais
eficiente, queries mais rápidas em range), mas não
é sharding — não distribui entre servidores.
Neste conceito, sharding implica distribuição cross-server. Particionamento local é complementar — pode ser feito antes do sharding como "purgatório" para escala vertical.
Estratégias canônicas — qual partition key
A escolha mais consequente em sharding é a partition key: qual coluna define qual shard recebe cada linha. Decide queries eficientes vs caras, hot shards vs balanceamento, e ergonomia de uso por anos.
Range partitioning
Cada shard cobre uma faixa de valores. Por exemplo, shard 1 cobre IDs 1-1M, shard 2 cobre 1M-2M, etc. Ou por timestamp: shard 1 cobre Jan 2024, shard 2 cobre Fev 2024.
Ganhos: queries de range eficientes ("todos os pedidos de Janeiro"). Fácil de compreender e operar. Útil quando há ordem natural (timestamps, sequências).
Limitações: vulnerável a hot shards quando a distribuição é desigual. Time-based partitioning concentra writes no shard atual; em pico, todo o tráfego vai para uma máquina.
Hash partitioning
Hash da chave determina o shard. Distribuição uniforme (assumindo bom hash). Adicionar shards requer rebalanceamento — exceto se usar consistent hashing (conceito 04) ou hash slots (Redis Cluster).
Ganhos: distribuição uniforme, sem hot shards intrínsecos. Adequado para padrões de acesso aleatórios.
Limitações: queries de range cruzam todos os shards (ineficiente). Não acomoda padrões com locality natural.
Geo / geographic partitioning
Cada shard cobre uma região geográfica. Útil em sistemas multi-region onde dados de usuários ficam próximos a eles fisicamente — reduz latência e atende requisitos de soberania de dados.
Ganhos: latência cross-region reduzida; compliance (GDPR, LGPD) facilitada. Limitações: hot shards possíveis (uma região pode ter mais usuários); migração de usuários entre regiões é complicada; queries cross-region são caras.
Tenant-based partitioning
Em sistemas SaaS multi-tenant, cada tenant (cliente) vive em um shard específico. Pequenos tenants compartilham shards; grandes têm shard dedicado ("noisy neighbor" mitigado). Conceito 10 do módulo detalha multi-tenancy.
Ganhos: isolamento entre tenants (incidente em um não afeta outros); queries tenant-scope são naturalmente eficientes; compliance facilitada. Limitações: rebalanceamento complexo quando um tenant cresce; queries cross-tenant raras mas caras quando precisas; "tenant gigante" que não cabe em shard único força sub-sharding.
Composite partitioning
Combinação. Cassandra usa partition key + clustering key — partition key define shard, clustering define ordem dentro. DynamoDB tem partition key + sort key.
Ganhos: queries dentro de partition são rápidas (mesmo shard) e ordenadas. Combina vantagens de hash (distribuição) com range (ordering).
Padrão dominante em bancos NoSQL distribuídos. Aprender a escolher partition + sort key é habilidade necessária.
Hot shards — o problema central
Mesmo com hash partitioning, alguns shards podem ficar mais carregados que outros. Causas típicas:
Tenant gigante. Em sistema tenant-based, um cliente é 100× maior que os outros. Seu shard é hot.
Item viral. Em e-commerce, um produto vira viral; todas as queries para ele vão para o shard que o contém. Em rede social, post celebridade lota seu shard.
Time-based pattern. Em range partitioning por tempo, writes recentes vão todos para o shard "atual"; writes antigos para shards "antigos". Hot writes, cold reads.
Acesso desigual por intent. 80% das queries acessam 20% dos dados (Pareto). Se esses 20% caem em poucos shards, hot.
Mitigations existem mas custam.
Sub-sharding. Tenant gigante é dividido em múltiplos sub-shards (tenant_A_0, tenant_A_1, etc.). Cliente da app sabe distribuir.
Replication local de hot data. Item viral é replicado em múltiplos shards; reads distribuídos. Writes ainda vão para o shard original.
Cache layer. Hot keys vão para cache distribuído (Redis), não atingem shard diretamente. Conceito 10 do módulo 05 detalhou.
Random suffix. Para writes
time-based, adicionar sufixo aleatório à chave (
2026-04-30:00,
2026-04-30:01, ..., :99)
espalha entre shards. Reads precisam saber juntar.
Resharding online — adicionando shards sem downtime
Quando shards atuais saturam, é necessário adicionar mais e migrar dados. Em sistemas com consistent hashing, a migração é incremental — só uma fração move. Em sistemas com hash modular ou range fixo, é mais complicada.
O processo canônico de resharding online:
1. Provisionar shard novo, configurar replicação cross-shard (nova range copiando do shard antigo).
2. Esperar replicação alcançar catch-up; verificar consistência.
3. Em janela curta, redirecionar tráfego para nova range no novo shard. Antes: escritor vai para shard antigo; depois: vai para novo. Período de transição usa "double-write" ou lookup table.
4. Validar que tudo está funcionando; remover dados duplicados do shard antigo.
Esse processo, em sistemas grandes, leva semanas.
Bancos distribuídos modernos (CockroachDB,
Spanner, TiDB) automatizam — usuário pede
ADD NODE e o banco gerencia migration.
Em sistemas com sharding manual (Vitess para MySQL,
Citus para Postgres), é mais explícito mas
gerenciável.
Sistemas que ignoram resharding até que doa enfrentam crise quando precisam — migração com sistema sob carga, deadline apertado, alta probabilidade de bugs. Articular plano de resharding antes de precisar é higiene de senior.
Queries cross-shard — onde sharding dói
Sharding eficiente quando query toca um shard; caro quando precisa cruzar. Queries cross-shard:
Aggregations sem partition key. "Soma total de pedidos de todos os usuários" precisa visitar todos os shards. Solução: data warehouse separado para analytics; ou materialização (count agregado mantido em outro lugar).
JOINs cross-shard. Tabela A
sharded por user_id, tabela B sharded
por product_id. JOIN entre as duas
atravessa fronteiras. Soluções: denormalizar
(carregar dados de B em A); fazer JOIN no código
cliente; usar query engine distribuído (Trino,
Presto).
Transações cross-shard. Bancos distribuídos modernos suportam (CockroachDB, Spanner via two-phase commit), mas com latência maior e custo. Bancos sharded "manuais" (Vitess MySQL) tipicamente exigem que aplicação coordene.
Scanning queries. "Encontrar todos os usuários inativos há 30 dias" sem partition key eficiente toca tudo.
Para mitigar custos cross-shard, sistemas tipicamente desenham com queries primárias em mente. Partition key é escolhida para tornar essas eficientes; queries secundárias (raras, analytics) toleram custo maior ou movem para sistema separado.
Sharding em três stacks
Cada ecossistema tem caminho próprio para sharding.
-- Postgres native partitioning (single instance)
CREATE TABLE pedidos (
id UUID NOT NULL,
cliente_id UUID NOT NULL,
created_at TIMESTAMPTZ NOT NULL,
...
) PARTITION BY HASH (cliente_id);
-- 16 partitions
DO $$ BEGIN
FOR i IN 0..15 LOOP
EXECUTE format('CREATE TABLE pedidos_p%s PARTITION OF pedidos
FOR VALUES WITH (modulus 16, remainder %s);', i, i);
END LOOP;
END $$;
-- Citus (sharding distribuído sobre Postgres)
SELECT create_distributed_table('pedidos', 'cliente_id');
-- Citus distribui dados entre nós workers automaticamente
// EF Core continua funcionando — particionamento é transparente
public async Task<List<Pedido>> ListarPedidosAsync(Guid clienteId)
{
return await _db.Pedidos
.Where(p => p.ClienteId == clienteId) // hits 1 shard apenas
.ToListAsync();
}
Postgres tem partitioning declarativo nativo (single-instance); Citus extension transforma em sharding distribuído com mesmo SQL. EF Core continua usando como tabela única. Caminho progressivo: particionar local → distribuir.
# DynamoDB com partition key + sort key
import boto3
from boto3.dynamodb.conditions import Key
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table("App")
# single-table design — várias entidades na mesma tabela
# pk = "USER#123" → todos os dados de user 123 vão para o mesmo shard
# sk = "PROFILE" ou "PEDIDO#abc" → ordena dentro da partition
# escrever pedido
table.put_item(Item={
"pk": f"USER#{user_id}",
"sk": f"PEDIDO#{pedido_id}",
"valor": 100.0,
"created_at": "2026-04-30T...",
})
# listar pedidos de um usuário (1 shard apenas — eficiente)
response = table.query(
KeyConditionExpression=Key("pk").eq(f"USER#{user_id}")
& Key("sk").begins_with("PEDIDO#"),
)
# query cross-tenant (todos os pedidos de hoje) — anti-padrão em DynamoDB
# precisa de Global Secondary Index (GSI) ou scan completo
# GSI: indice "GSI1" com pk_gsi=created_at_day, sk_gsi=pedido_id
response = table.query(
IndexName="GSI1",
KeyConditionExpression=Key("pk_gsi").eq("DAY#2026-04-30"),
)
DynamoDB força pensar em sharding desde o design. Single-table design (Alex DeBrie, Rick Houlihan) é padrão dominante: várias entidades na mesma tabela, partition key define shard, sort key define ordem. Curva de aprendizado alta; performance excelente.
package main
import (
"github.com/gocql/gocql"
)
// schema Cassandra
// CREATE TABLE pedidos (
// cliente_id UUID,
// pedido_id UUID,
// valor DECIMAL,
// created_at TIMESTAMP,
// PRIMARY KEY (cliente_id, pedido_id)
// );
// cliente_id é partition key; pedido_id é clustering key
// todos os pedidos de um cliente vão para o mesmo shard
// dentro do shard, ordenados por pedido_id
func ListarPedidos(session *gocql.Session, clienteID gocql.UUID) ([]Pedido, error) {
iter := session.Query(
`SELECT pedido_id, valor, created_at
FROM pedidos WHERE cliente_id = ?`,
clienteID,
).Iter()
var pedidos []Pedido
var p Pedido
for iter.Scan(&p.PedidoID, &p.Valor, &p.CreatedAt) {
pedidos = append(pedidos, p)
}
return pedidos, iter.Close()
}
// query cross-partition é anti-padrão em Cassandra
// requer ALLOW FILTERING (lento) ou tabela secundária com partition key diferente
// padrão: criar segunda tabela "pedidos_by_status"
// PRIMARY KEY (status, created_at_day, pedido_id)
Cassandra força query-driven design: para cada padrão de query, possivelmente uma tabela com partition key adequada. Denormalização é regra, não exceção. Adequado para sistemas com queries muito definidas e padrões previsíveis.
Como decidir a partition key
Escolher partition key é a decisão mais consequente em sharding. O método canônico:
1. Liste as queries primárias. Quais são as ~5 queries mais frequentes do sistema? Quais campos elas filtram?
2. Procure campo que aparece em todas (ou
maioria). Se as queries primárias todas
filtram por cliente_id ou
tenant_id, esse é candidato natural.
3. Verifique cardinalidade e
distribuição. A chave precisa ter
cardinalidade alta (milhões de valores distintos) e
distribuição razoavelmente uniforme. status
com 5 valores distintos não serve;
tenant_id com 10000 tenants servidos
uniformemente serve.
4. Pense em hot keys. Algum valor
vai concentrar tráfego desproporcional?
tenant_id com um cliente sendo 90% do
uso é problema. Tenha plano (sub-sharding ou
isolation).
5. Considere queries secundárias. Para queries que não filtram por partition key, qual o plano? Index secundário (com seu próprio sharding)? Materialized view? Query analítica separada?
Em sistemas SaaS multi-tenant, tenant_id
é tipicamente a escolha — todas as queries são
tenant-scope. Em sistemas time-series, frequentemente
timestamp + outro campo. Em e-commerce,
user_id ou
order_id. A escolha é sempre
contextual; raramente óbvia.
Anti-padrões frequentes
Sharding antes de precisar. Sistema novo com 1000 usuários adota sharding por "futureproofing". Complexidade alta; benefício zero. Defesa: scale up + replicação primeiro; sharding quando vertical realmente saturar.
Partition key errada. Escolha que bate com queries atuais mas não com futuras. Como partition key é geralmente irreversível, mudar depois é resharding caro. Defesa: análise detalhada antes; não adote partition key sem ter articulado as 5 queries primárias.
Hot shard ignorado. Time vê desbalanceamento; aceita "é só natural". Em pico, hot shard cai; sistema parece "lento aleatoriamente" mas na verdade é um shard saturado. Defesa: monitoramento por shard; alerta em desvio; plano de mitigação.
Cross-shard transactions naive. "Vou só fazer dois UPDATEs em shards diferentes". Sem two-phase commit, falha parcial é possível — uma escrita commita, outra falha, estado inconsistente. Defesa: bancos distribuídos reais (CockroachDB, Spanner) com 2PC nativo; ou saga pattern (módulo 09); ou redesenhar para single-shard.
Resharding sob crise. Sistema satura; time decide adicionar shards sob pressão. Migração gera bugs sob estresse. Defesa: testar resharding antes — em staging com volume representativo, validar que processo funciona, antes de precisar.
Hot shard de tenant gigante. Sistema SaaS sharded
por tenant_id hospeda 10000 tenants
pequenos e 1 tenant enterprise grande (50% de todo
o tráfego). O shard do tenant grande satura sob
carga; outros 1000 tenants no mesmo shard sofrem
latência. Os outros 9 shards estão ociosos.
Diagnóstico: distribuição "por tenant_id" não vê
que peso é desigual. Defesa estrutural:
shard dedicado para tenants enterprise;
sub-sharding por departamento dentro do tenant
grande; rotas explícitas de roteamento. Sem essa
articulação, sistemas multi-tenant invariavelmente
sofrem com este pattern.
Antes de adotar sharding, responda quatro perguntas. "Vertical + replicação ainda têm espaço?". Se sim, exaurir antes. "Quais são as 5 queries primárias e qual campo está em todas?". Sem essa resposta, sharding é palpite. "Existe risco de hot shard previsível?". Se sim, plano antes de implementar. "Como vou rebalancear quando precisar?". Se a resposta é "não sei", sharding ainda é prematuro — ou usar banco distribuído moderno que automatiza. Sharding manual em sistema com escala média frequentemente é trade-off ruim — ferramenta certa para problema do qual não se tem.
Por que importa para a sua carreira
Sharding é tópico de design de sistemas em escala. Em entrevistas para vagas em sistemas com crescimento agressivo, "como você dimensionaria o banco para 100 milhões de usuários?" é pergunta direta — a resposta forte cita scale up primeiro, replicação depois, sharding como último recurso, e articula partition key candidata baseada em queries. Em revisão de proposta de adoção de sharding, identificar quando ainda é prematuro é serviço. Em pos-mortem de hot shard, articular como mitigation depende de identificar o pattern ( tenant gigante, item viral, time-based) é trabalho de senior. Em discussão sobre escolha de banco (Postgres + Citus vs Cassandra vs CockroachDB vs DynamoDB), articular trade-offs de sharding em cada um é vocabulário maduro.
Como praticar
- Análise de partition key. Pegue um sistema seu (ou imaginário) com tabelas relacionais. Liste as 5 queries mais frequentes. Para cada coluna candidata a partition key, articule: queries que ficam eficientes, queries que ficam caras, distribuição esperada, riscos de hot shard. Decida e justifique. Esse exercício, em projeto real, antecipa decisões que viriam sob pressão.
-
Setup local com Postgres
partitioning. Implemente uma tabela com
PARTITION BY HASHem Postgres local. Carregue 10 milhões de linhas. Compare query com partition key (eficiente) vs sem partition key (full scan). Veja o EXPLAIN. Esse é caminho para entender o que sharding fornece. - Modelagem em DynamoDB single-table. Pegue um domínio (e-commerce, blog, multi-tenant SaaS) e modele em DynamoDB single-table design. Liste 5 queries primárias e desenhe partition+sort keys que servem todas. Esse é exercício mental importante mesmo se você não usa DynamoDB — consolida o pensamento em sharding.
Referências para aprofundar
- livro Designing Data-Intensive Applications — Martin Kleppmann (O'Reilly, 2017).
- livro Database Internals — Alex Petrov (O'Reilly, 2019).
- livro The DynamoDB Book — Alex DeBrie (auto-publicado, 2020).
- paper Dynamo: Amazon's Highly Available Key-value Store — DeCandia et al., Amazon (SOSP, 2007).
- paper Spanner: Google's Globally-Distributed Database — Corbett et al., Google (OSDI, 2012).
- artigo Sharding Pinterest: How we scaled our MySQL fleet — Yashh Nelapati, Marty Weiner (pinterest engineering, 2015).
- artigo Sharding & IDs at Instagram — Mike Krieger (instagram engineering, 2012).
- docs PostgreSQL Table Partitioning.
- docs Citus Documentation.
- docs Vitess.
- docs CockroachDB Architecture.
- vídeo AWS DynamoDB Patterns — Rick Houlihan (re:Invent, vários anos).