02 - Chave de Sharding (Shard Key)
video: https://youtu.be/... no frontmatter da nota.Chave de Sharding (Shard Key)
A chave de sharding é o atributo cujo valor determina em qual fragmento cada linha de dados será armazenada e, simetricamente, qual fragmento deve ser consultado para recuperá-la. Toda a mecânica do sharding deriva dessa escolha: a função de distribuição recebe o valor da chave e devolve o shard de destino, e a aplicação que consulta precisa conhecer esse mesmo caminho para acertar o nó. A decisão é de alto risco em produção porque é praticamente irreversível sem operação cara de re-sharding, que envolve mover dados entre nós com o sistema no ar. Uma chave mal escolhida produz fragmentos desbalanceados, consultas que varrem todos os shards e junções que atravessam a rede; uma chave bem escolhida faz o sistema escalar quase linearmente por anos. Entre todos os parâmetros de uma arquitetura de sharding, este é o que concentra o maior custo de erro.
Conceito e papel no desenho distribuído
Formalmente, a chave de sharding é o conjunto de colunas que alimenta a função de particionamento: hash do ID do usuário, intervalo da data, tenant identificado no diretório. O fragmento resultante é a unidade de colocação dos dados, e a rota de leitura percorre o caminho inverso: dado o valor da chave, o roteador sabe exatamente qual nó responder. Esse contrato simétrico entre escrita e leitura é o que permite ao sharding escalar, pois cada consulta típica toca um número mínimo de nós.
A chave não precisa ser uma única coluna; chaves compostas são comuns e úteis, como tenant_id seguido de user_id. O que precisa existir é estabilidade: a mesma entidade deve produzir sempre o mesmo destino, caso contrário o dado se multiplica por fragmentos e a integridade se perde. Por isso a imutabilidade é critério de primeira ordem, e atributos que o negócio altera com frequência, como e-mail, cidade ou plano, são candidatos ruins.
Um exemplo ancorado em e-commerce ilustra o papel simétrico da chave. Escolhido o user_id como chave de sharding, cada pedido recebe seu destino no momento da inserção pelo valor do usuário comprador, e dez anos depois a consulta de pedido do mesmo usuário percorre exatamente o mesmo caminho. O histórico de compras de um cliente vive em um único fragmento, as listagens do aplicativo têm rota direta e o suporte consulta o cliente com um filtro de chave. Toda a ergonomia do sistema deriva dessa única decisão tomada antes da primeira linha de dados.
O problema que resolve
Sem uma chave bem definida, o sistema distribuído degrada para dois comportamentos ruins. O primeiro é a distribuição desigual: alguns shards acumulam carga e tamanho enquanto outros ficam ociosos, e a capacidade do cluster é limitada pelo shard mais quente, não pela soma dos nós. O segundo é a consulta dispersa: sem saber onde os dados residem, cada consulta precisa ser enviada a todos os fragmentos, multiplicando latência e custo de processamento.
A chave de sharding resolve ambos por construção. A distribuição uniforme vem da combinação de alta cardinalidade com função de hash adequada, espalhando linhas de forma homogênea. O roteamento direto vem da presença da chave nas consultas do dia a dia, permitindo que a esmagadora maioria das operações toque um único shard. O desenho do domínio, portanto, antecede a técnica: identificar quais consultas dominam o workload e garantir que a chave escolhida as atenda com rota direta é o trabalho real de arquitetura.
Critérios de escolha
A escolha sistemática avalia quatro critérios, todos obrigatórios e todos em tensão entre si. Falhar em qualquer um deles compromete o arranjo inteiro, por isso vale percorrer cada um com o domínio do negócio em mãos:
- Alta cardinalidade: o espaço de valores precisa ser muito maior que a quantidade de shards, de preferência com milhões ou bilhões de valores possíveis; sexo booleano ou status com três estados espremem a distribuição.
- Distribuição uniforme: valores não podem concentrar linhas; a função de hash ou o intervalo deve produzir fragmentos de tamanho comparável ao longo do crescimento.
- Presença nas consultas: a chave deve aparecer nos predicados das consultas dominantes; uma chave perfeita que as consultas não informam só gera varreduras globais.
- Estabilidade e imutabilidade: valores que mudam forçam migração de linha entre shards; se a mudança é inevitável, o custo precisa ser conhecido e raro.
Critérios secundários completam a avaliação: co-localização de dados relacionados, para manter junções locais; baixa correlação com sazonalidade, para evitar picos concentrados; e legibilidade operacional, porque times investigam incidentes e a chave aparece em logs e dashboards. Em dúvida entre duas candidatas, simular a distribuição com dados reais, mesmo que amostrados, vale mais que qualquer raciocínio abstrato.
-- Simulação rápida de distribuição com hash de 8 shards sobre dados reais
SELECT mod(abs(hashtext(user_id::text)), 8) AS shard,
count(*) AS linhas
FROM pedidos
GROUP BY 1
ORDER BY 2 DESC;
-- Desvio grande entre máximo e mínimo indica candidata ruim
-- antes de qualquer decisão de infraestrutura
Consulta sem a chave vira scatter-gather
O efeito mais brutal de uma chave incompatível com o workload é o scatter-gather: a consulta é enviada a todos os shards simultaneamente, cada um executa sua parte, e um agregador junta os resultados. A latência da operação passa a ser a do shard mais lento, a carga multiplicada pelo número de fragmentos e o ganho de escala desaparece justamente nas consultas que mais precisam dele.
-- Consulta roteada: o filtro usa a chave, um único shard é consultado
SELECT * FROM pedidos
WHERE user_id = 918273 -- chave de sharding presente
AND status = 'aberto';
-- Consulta dispersa: sem a chave, todos os shards são varridos
SELECT count(*) FROM pedidos
WHERE status = 'aberto'
AND criado_em >= '2025-01-01';
-- No plano distribuído, a segunda consulta aparece como gather merge
-- ou scatter across shards, com custo proporcional ao total de nós
A mitigação estrutural tem três caminhos. Repensar o acesso, adicionando a chave ao fluxo da consulta quando o domínio permite, como exigir contexto de tenant na tela. Criar índices secundários globais, mantidos de forma assíncrona, que mapeiam atributos alternativos ao shard de destino, com custo de consistência eventual. Descarregar consultas globais para um sistema de análise dedicado, como um data warehouse alimentado por CDC, reservando o cluster transacional às rotas diretas.
A medição do problema também é parte da solução: instrumentar o roteador para contabilizar consultas roteadas e dispersas por minuto, por endpoint e por equipe responsável. A taxa de dispersão aceitável varia com o produto, mas números acima de poucos por cento no tráfego interativo costumam indicar fluxos desenhados contra a arquitetura, e cada ponto percentual se traduz diretamente em capacidade desperdiçada do cluster.
Tenant id versus user id
Em sistemas SaaS multi-inquilino, a disputa clássica é shardar pelo inquilino ou pelo usuário final. Shardar por tenant_id traz isolamento forte: dados de cada cliente residem em fragmentos próprios, exportação, exclusão e conformidade ficam triviais, e clientes gigantes podem receber fragmentos dedicados. O preço é a distribuição irregular, pois inquilinos variam de cinco a cinco milhões de usuários, e um único cliente corporativo pode dominar um shard inteiro.
Shardar por user_id distribui de forma muito mais uniforme, já que usuários são entidades comparáveis entre si, mas quebra o isolamento do inquilino: as consultas de listagem do cliente, que abrangem todos os seus usuários, viram scatter-gather, e requisitos de residência de dados por contrato ficam difíceis de garantir. A tabela resume o trade-off:
| Critério | Chave por tenant_id | Chave por user_id |
|---|---|---|
| Distribuição de carga | Irregular, depende do tamanho dos clientes | Uniforme, usuários comparáveis |
| Consultas de escopo do cliente | Rota direta a um shard | Scatter-gather entre shards |
| Isolamento e conformidade | Forte, dados segregados por cliente | Fraca, dados misturados por fragmento |
| Tenant gigante | Hot shard, exige dedicated shard | Espalhado, sem ponto quente |
| Uso típico | SaaS corporativo com contratos de isolamento | Produto de consumo com usuários individuais |
A resposta prática frequente é a chave composta ou o arranjo em camadas: shard por tenant com fragmentos dedicados para clientes gigantes, ou shard por user_id com um diretório que guarde o fragmento de cada inquilino. O que não existe é resposta universal; o padrão de consulta do produto decide.
Co-localização de dados relacionados
A chave determina mais que a localização de uma tabela: ela define o que pode ser juntado localmente. Pedidos e seus itens compartilhando a mesma chave de sharding, por exemplo user_id, residem no mesmo fragmento, e o JOIN entre eles permanece local e barato. Se os itens forem distribuídos por outra chave, cada junção atravessa a rede, multiplicando latência e fragilidade.
O desenho orientado a agregados orienta essa decisão: identificar as entidades que nascem, vivem e morrem juntas, e atribuir a elas a mesma chave de sharding. Relacionamentos cruzados, como amizades entre usuários de fragmentos distintos, são inevitáveis e caros; a boa prática é minimizá-los, resolvendo-os por leitura assíncrona, cache ou referência por ID sem junção transacional. O domínio que se mapeia bem em agregados co-localizados é o mesmo que se sharda bem.
A regra de engenharia que decorre disso é incluir a chave em todas as tabelas do agregado, mesmo quando redundante. Itens de pedido carregam o user_id do comprador embora o pedido já o tenha; eventos do usuário repetem o identificador em cada linha. Essa redundância deliberada é o preço da co-localização e da consulta direta em qualquer tabela do fragmento, e removê-la por normalismo de modelo relacional sabotaria a distribuição inteira.
Mudança de chave como operação custosa
A chave de sharding é presumivelmente eterna, e toda operação que a altera equivale a reconstruir a distribuição. Mudar a chave significa reparticionar todo o acervo: recalcular o destino de cada linha, mover dados entre nós, atualizar índices secundários globais e garantir consistência durante o processo, tudo com o sistema servindo tráfego de produção. O custo é comparável ao de um re-sharding completo, com semanas ou meses de engenharia.
As consequências práticas influenciam desenho e governança. Chaves de negócio mutáveis, como telefone, e-mail ou região do cliente, devem ser evitadas; se a mudança for obrigatória, caminhos indiretos amortizam o custo, como manter a chave original como âncora imutável e registrar o novo valor como atributo consultável. Quando a mudança é estrutural, o processo é o de re-sharding descrito no arquivo seguinte: camada dupla de escrita, backfill incremental, validação de integridade e corte de tráfego por fases. O planejamento da chave, portanto, deve considerar o domínio daqui a cinco anos, não apenas o produto de hoje.
Ferramentas maduras reduzem o risco, mas não o custo. O Vitess executa o re-sharding com VReplication controlando cópia e corte de tráfego; o MongoDB oferece o reshardCollection com migração coordenada pelo próprio cluster; plataformas de sharding como Citus e ShardingSphere expõem variações do mesmo fluxo. Ainda assim, o processo consome capacidade dos nós, exige janelas de validação e reserva o direito de reversão; a decisão de trocar a chave permanece entre as mais caras do ciclo de vida do produto.
Aplicação prática: guardas de roteamento
Em produção, a disciplina de chave é aplicada com guardas no código: consultas que não informam a chave são rejeitadas em revisão ou em tempo de execução, e o roteador calcula o destino a partir da chave em um único ponto do sistema.
# Roteamento direto pela chave em um único ponto do sistema
import zlib
def shard_de(chave: int, total_shards: int) -> int:
# hash estável e uniforme do valor da chave
return zlib.crc32(str(chave).encode()) % total_shards
def consultar_pedido(user_id: int, pedido_id: int):
# guarda explícita: toda consulta carrega a chave de sharding
alvo = shard_de(user_id, TOTAL_SHARDS)
return conexoes[alvo].executar(
"SELECT * FROM pedidos WHERE user_id = %s AND id = %s",
(user_id, pedido_id),
)
def consultar_sem_chave(predicado):
raise ValueError(
"consulta global bloqueada; use caminho analítico ou índice secundário"
)
Complementarmente, o monitoramento acompanha a razão entre consultas roteadas e dispersas, o tamanho relativo dos fragmentos e a distribuição de CPU por shard; desvios nessas três métricas são os primeiros sintomas de chave mal dimensionada.
Erros comuns e boas práticas
- Erro: escolher a chave pelo atributo mais óbvio sem analisar as consultas dominantes; prática: mapear o top de consultas por frequência e validar que todas têm rota direta.
- Erro: usar hash simples de módulo sobre ID sequencial e descobrir, no primeiro aumento de shards, que quase tudo migra; prática: consistent hashing ou esquemas de intervalo que minimizam movimento.
- Erro: shardar por tenant sem política para clientes gigantes; prática: fragmentos dedicados ou chave composta, decididos antes do contrato ser assinado.
- Erro: aceitar consultas sem chave como casos normais e normalizar o scatter-gather; prática: bloquear em revisão, medir a taxa e descarregar o global para camada analítica.
- Erro: assumir que a chave pode ser trocada depois "com uma migração rápida"; prática: tratar a escolha como decisão de cinco anos e documentar as alternativas descartadas.
- Erro: usar atributos de negócio mutáveis como chave, forçando migração de linha a cada alteração de cadastro; prática: chave âncora imutável, com atributos voláteis como dados adicionais.
- Erro: shardar tabelas de referência pequenas junto com o acervo principal; prática: replicar tabelas de referência em todos os fragmentos ou mantê-las em serviço dedicado.
A chave de sharding condensa a lição central do particionamento distribuído: a arquitetura é boa se as consultas forem boas para ela. Escolher a chave junto com o desenho do domínio, validar com dados reais, garantir co-localização dos agregados e monitorar a taxa de roteamento direto são as práticas que separam clusters que escalam com tranquilidade de sistemas que precisam ser refeitos no auge do crescimento.