03 - Estratégias (Range, Hash, Directory)
video: https://youtu.be/... no frontmatter da nota.Estratégias (Range, Hash, Directory)
As estratégias de sharding definem como o valor da chave de sharding é convertido no fragmento de destino, e essa conversão é a função de distribuição do sistema. Três famílias dominam a prática: range sharding, que divide o espaço de valores em intervalos contíguos; hash sharding, que distribui pelo resultado de uma função de hash; e directory sharding, que consulta um serviço de diretório mantendo o mapa explícito entre chave e fragmento. Cada família faz um compromisso diferente entre distribuição uniforme, suporte a consultas por intervalo, custo de rebalanceamento e complexidade operacional. A escolha correta nasce do padrão de consulta do produto, e não de preferência tecnológica, porque a estratégia define quais consultas têm rota direta e quais viram varreduras globais. Em produção, trocar de estratégia depois dos dados no ar é um projeto de re-sharding completo, o que reforça a importância de decidir com critério na primeira vez.
Conceito: a função de distribuição
Toda estratégia responde à mesma pergunta: dado o valor da chave, qual shard guarda a linha e qual shard responde à consulta. A diferença está no mecanismo. No range, uma tabela de intervalos mapeia limites a fragmentos; no hash, uma função matemática calcula o destino; no directory, um registro explícito guarda o mapeamento por chave. O roteador de consultas, na aplicação, no proxy ou no próprio cluster, executa essa função a cada operação.
A estratégia interage com a chave escolhida no arquivo anterior: o mesmo user_id produz resultados muito diferentes sob range, sob hash e sob directory. Por isso as duas decisões são tomadas em conjunto, avaliando as consultas dominantes e a curva de crescimento esperada do volume de dados.
Quem executa a função de distribuição também varia por arquitetura. Sistemas como MongoDB e Elasticsearch embutem o roteador no próprio cluster; plataformas como Vitess e Citus expõem o roteamento via proxy de banco; desenhos caseiros implementam a função na biblioteca compartilhada da aplicação. A regra invariante é a existência de um único algoritmo canônico, documentado e testado, porque qualquer divergência entre roteadores significa dado no lugar errado.
A estratégia também interage com a replicação: cada fragmento carrega sua própria cadeia de primário e réplicas, e a distribuição define quantos desses pares existirão. Um cluster de 16 shards com duas réplicas cada mantém 48 instâncias de banco, e a escolha da estratégia influencia diretamente o custo dessa frota, pois distribuições desiguais forçam dimensionamento pelo maior fragmento em vez de pelo médio.
Range sharding
O range sharding ordena o espaço de valores e corta em intervalos: usuários de ID 1 a 10 milhões no shard A, de 10 a 20 milhões no shard B, ou eventos de cada mês em fragmentos próprios. O ganho característico é o suporte nativo a consultas por intervalo, pois valores vizinhos residem no mesmo fragmento, permitindo varreduras locais de faixas de tempo, de prefixos de texto e de sequências de ID. O rebalanceamento também é natural, já que um intervalo grande demais pode ser dividido ao meio e parte dos dados movida.
O defeito característico são os hot spots. Dados novos quase sempre aterrissam na borda final do espaço, que é exatamente a que concentra escrita; IDs sequenciais autoincrementais pioram o quadro, porque todo INSERT vai ao último intervalo, e o último shard trabalha enquanto os demais dormem. Chaves com viés de negócio agravam o efeito, como intervalos por inicial do sobrenome que concentram linhas em algumas letras.
O uso tipicamente correto do range é quando existe um eixo temporal ou monotônico com leitura dominante sobre os dados recentes, mas a retenção descarta o passado: sistemas de eventos, logs e faturamento mensal, nos quais o range por tempo distribui leitura por fragmentos e o hot spot de escrita pode ser mitigado com fragmentos maiores ou pré-divisão de intervalos. Para cargas de escrita uniforme por entidade, o hash costuma ganhar.
A mitigação do hot spot em range tem técnicas próprias. A pré-divisão cria intervalos menores que a borda quente, espalhando a escrita entre vários fragmentos; a rotação de intervalo arquivada torna o passado estático, e o fragmento quente de hoje vira frio no próximo ciclo; e o salting deliberado, adicionando sufixo aleatório à chave para as entidades mais quentes, espalha uma linha gigantesca em várias. Cada técnica tem custo de consulta, e a escolha depende de saber qual leitura pode pagar o preço.
Hash sharding
O hash sharding aplica uma função de hash sobre a chave e usa o resultado para escolher o fragmento, produzindo distribuição estatisticamente uniforme mesmo com chaves sequenciais ou com viés de negócio. É a resposta padrão para o hot spot do range: IDs autoincrementais, hashtags de campanha e nomes de usuário são espalhados de forma homogênea, e a capacidade do cluster é utilizada por completo.
O custo é a perda das consultas por intervalo: dois valores vizinhos na ordem natural caem em fragmentos arbitrários, e uma consulta como eventos de uma semana inteira precisa varrer todos os shards. O segundo custo é o rebalanceamento com hash de módulo simples: mudar o número de shards recalcula o destino de quase todas as chaves, exigindo migração maciça. O consistent hashing, detalhado adiante, resolve exatamente esse segundo problema ao reduzir a fração de chaves movidas quando o cluster cresce.
Para chaves individualmente gigantes, o usuário celebridade que domina o tráfego ou o produto campeão de vendas, o hash uniforme não resolve, porque o desequilíbrio está na própria chave e não na distribuição dela. As técnicas são compostas: hash da chave combinada a um sufixo de salt, criando N réplicas de escrita que depois são agregadas; ou isolamento da entidade quente em armazenamento dedicado com cache agressivo. O diagnóstico começa sempre no monitoramento por shard, pois o desvio só é gerenciável quando é medido.
| Aspecto | Range | Hash | Directory |
|---|---|---|---|
| Distribuição de carga | Suscetível a hot spots nas bordas | Uniforme por construção | Tão boa quanto o mapa mantido |
| Consultas por intervalo | Locais e rápidas | Varrer todos os shards | Depende do esquema de alocação |
| Custo de rebalanceamento | Baixo, divide intervalos | Alto com módulo, baixo com consistent hashing | Baixo, atualiza registro |
| Componente extra | Tabela de intervalos | Nenhum | Serviço de diretório no caminho |
| Flexibilidade por entidade | Uniforme para todos | Uniforme para todos | Total, alocação por chave |
| Uso típico | Séries temporais, arquivos por data | Dados de usuário, escrita uniforme | Multi-inquilino com clientes heterogêneos |
A leitura por colunas ajuda na decisão. Na coluna de consultas, o range é o único que preserva varreduras de faixa; na coluna de distribuição, o hash é o único uniforme por construção; na coluna de flexibilidade, o directory é o único que aloca por decisão de negócio. Nenhuma família vence em todas as colunas, e o desenho correto aceita o compromisso que o workload do produto tolera melhor.
Directory sharding
O directory sharding mantém um serviço de diretório que registra, para cada chave ou cada inquilino, o fragmento de destino. A alocação deixa de ser função matemática e passa a ser decisão administrável: um cliente grande pode receber um shard dedicado, um cliente médio compartilha fragmento com similares, e a movimentação entre fragmentos é uma atualização de registro seguida da cópia dos dados.
A flexibilidade tem contrapartidas operacionais sérias. O diretório está no caminho de toda operação, portanto precisa ser replicado, cacheado e monitorado como componente crítico; a latência de consulta soma um salto para resolver o destino, mitigado por cache local com invalidação; e a qualidade da distribuição depende de alguém administrar o mapa, tarefa que vira peso permanente do time de plataforma. O directory brilha em cenários multi-inquilino com clientes de tamanhos muito distintos, nos quais a alocação inteligente vale o custo do serviço extra.
O formato do diretório varia da tabela simples ao serviço dedicado. Um arranjo comum guarda o mapa em um banco central replicado, com cache na memória dos roteadores e versionamento do mapa para invalidação segura; arranjos maiores evoluem para serviço com API própria, auditoria de mudanças e métricas de taxa de acerto do cache. O que não é opcional é a planilha manual: alocação por decisão de negócio exige processo, histórico e reversibilidade, coisas que planilhas não garantem.
Rebalanceamento em cada estratégia
O rebalanceamento é o teste de fogo de cada estratégia, pois acontece em produção, com tráfego servido e dados vivos. No range, a divisão de intervalo é operação local: escolhe-se o corte, copia-se a metade para o novo fragmento, atualiza-se a tabela de intervalos e alterna o roteamento; plataformas como MongoDB automatizam o ciclo com balancer interno. No hash de módulo, o evento é traumático, pois a mudança do total recalcula todos os destinos; por isso o módulo puro só é aceitável em clusters de tamanho fixo e conhecido.
No consistent hashing, a entrada de nó move apenas o arco novo, e a saída devolve o arco ao vizinho, com taxas de migração na casa de 1/N; os virtual nodes suavizam a variação entre nós. No directory, o rebalanceamento é administrativo: atualiza-se o registro do inquilino, copiam-se os dados e altera-se o roteamento, com controle total sobre o ritmo. Em todas as famílias, o rebalanceamento seguro exige limitação de banda de cópia, idempotência no corte de tráfego e validação de contagem de linhas por fragmento ao final.
Um detalhe recorrente em produção é o rebalanceamento duplo, quando crescimento e mudança de estratégia acontecem juntos e a migração precisa reescrever tanto os dados quanto a função de destino. Esse é o cenário mais arriscado e também o mais comum em clusters antigos, e a recomendação é separar as fases: primeiro estabilizar a distribuição atual, depois trocar a função, nunca ambos no mesmo evento.
Consistent hashing e virtual nodes
O consistent hashing organiza o espaço de hash em um anel circular e atribui cada fragmento a um arco: a chave pertence ao fragmento cujo arco contém seu hash. Quando um nó entra ou sai, apenas as chaves do arco afetado se movem, tipicamente uma fração de 1 sobre o número de nós, em vez de praticamente todas as chaves do hash de módulo. O mecanismo é o fundamento da distribuição de dados em Cassandra, DynamoDB e caches distribuídos como o Memcached com ketama.
Os virtual nodes completam o desenho: cada nó físico é representado por centenas de arcos no anel, e a média estatística desses arcos corrige a desigualdade natural de poucas partições. Sem virtual nodes, um cluster pequeno com nós de capacidades distintas acumula desvio; com eles, a distribuição se aproxima do uniforme e a adição de um nó mais potente absorve proporcionalmente mais arcos. Em produção, a combinação de consistent hashing com virtual nodes é o padrão consolidado para fragmentos de tamanho controlado em clusters elásticos.
A escolha da função de hash também importa no anel: funções rápidas e bem distribuídas, como xxHash e MurmurHash, dominam implementações de produção, enquanto funções criptográficas como SHA são reservadas a casos que exigem propriedades de segurança. O requisito inegociável é a estabilidade da função ao longo do tempo, pois trocar de hash equivale a reescrever o anel inteiro e migrar tudo.
Exemplos por caso
A correspondência entre padrão de carga e estratégia fica mais clara em exemplos concretos de produção:
- Séries temporais e retenção: range por mês ou por dia, com descarte de partição antiga e leitura local das janelas consultadas; o hot spot de escrita é aceito ou mitigado com intervalos maiores.
- Dados de usuário em produto de consumo: hash do user_id, distribuição uniforme e rota direta para o perfil, pedidos e sessões de cada usuário.
- SaaS multi-inquilino heterogêneo: directory com inquilino no mapa, reservando fragmentos dedicados a clientes corporativos e agrupando clientes pequenos.
- Cache distribuído: consistent hashing com virtual nodes, para que a entrada e saída de nós mova o mínimo de chaves e o cache não esfrie a cada escala.
- Catálogo global de entidades: hash da chave natural da entidade, com índices secundários globais para os atributos alternativos de busca.
- Feed social por usuário: hash do user_id para a caixa pessoal, com materialização assíncrona do feed em cache para leitura de alta frequência.
Aplicação prática: comparando módulo e anel
O trecho abaixo compara a conversão de chaves em destino com hash de módulo e com anel de consistent hashing, ilustrando por que o segundo reduz a migração no crescimento do cluster:
# Hash de módulo: mudar o total de shards move quase todas as chaves
def shard_modulo(chave: str, total: int) -> int:
return hash(chave) % total
# Consistent hashing: anel com virtual nodes move apenas o arco afetado
import bisect
class Anel:
def __init__(self, nos: list[str], vnodes: int = 100):
self.aneis = [] # lista ordenada de posições no anel
self.mapa = {} # posição do anel -> nó físico
for no in nos:
for v in range(vnodes):
pos = hash(f"{no}:{v}")
self.aneis.append(pos)
self.mapa[pos] = no
self.aneis.sort()
def shard_de(self, chave: str) -> str:
pos = hash(chave)
# busca circular: primeiro arco à direita da posição da chave
i = bisect.bisect(self.aneis, pos) % len(self.aneis)
return self.mapa[self.aneis[i]]
Ao adicionar um nó ao Anel, apenas as chaves hashadas dentro do novo arco mudam de destino; no shard_modulo, com o total passando de 8 para 9, praticamente todas as chaves migram. Essa diferença matemática é o que permite aos clusters de dados crescerem sem janelas de migração gigantescas.
Em plataformas consolidadas, o mesmo princípio aparece embutido: o MongoDB divide coleções em chunks por range ou por hash do shard key e o balancer move chunks entre nós; o Vitess usa VSchema para declarar o tipo de particionamento e executa o movimento de faixas com VReplication; e clusters de cache usam anéis de ketama com virtual nodes. O conhecimento da mecânica permanece necessário mesmo com a automação, porque dimensionar intervalos, escolher hash e prever movimento de dados continuam sendo decisões de arquitetura.
Erros comuns e boas práticas
- Erro: escolher range com IDs sequenciais e descobrir que um único shard recebe toda a escrita; prática: range apenas com eixo de leitura dominante, ou hash quando a escrita é uniforme.
- Erro: usar hash de módulo em cluster elástico e pagar migração quase total a cada aumento; prática: consistent hashing com virtual nodes desde o primeiro desenho.
- Erro: implantar directory sem tratar o serviço como componente crítico; queda do diretório paralisa todas as consultas; prática: replicação, cache local com TTL e plano de degradação do diretório.
- Erro: misturar estratégias sem desenho, parte dos dados por range e parte por hash sem documentação; prática: uma estratégia por dataset, registrada no catálogo de dados.
- Erro: pré-dividir intervalos de range sem considerar a curva de crescimento; prática: dimensionar intervalos pelo crescimento projetado e automatizar divisão quando o tamanho exceder o limiar.
- Erro: escolher estratégia pelo hype e não pelo padrão de consulta, copiando o desenho de outra empresa; prática: mapear as dez consultas dominantes e escolher a família que as atende com rota direta.
- Erro: esquecer o custo de leitura do directory em hot path e descobrir que o cache local serve dado de alocação antigo; prática: invalidação por versão de mapa e alarme de divergência entre roteador e diretório.
- Erro: confiar na uniformidade estatística do hash sem medir o desvio real por fragmento; prática: dashboards de tamanho e carga por shard com alarme de desvio acima do limiar acordado.
A síntese comparativa orienta a decisão: range quando o eixo temporal domina a leitura e a retenção descarta o passado; hash quando a escrita precisa ser espalhada e as consultas carregam a chave exata; directory quando a alocação precisa ser decisão de negócio, com clientes heterogêneos e requisitos por contrato. Consistent hashing atravessa as três famílias como o mecanismo que torna o rebalanceamento civilizado, e virtual nodes são o ajuste fino que transforma o anel em distribuição de verdade.