Home Comunidade Eventos YouTube Networks Desafios // produtos Academy News CTI | CVE Contato

03 - Estratégias (Range, Hash, Directory)

#consistent-hashing#directory-sharding#hash-sharding#range-sharding
video explicativo
Video explicativoEspaco reservado para o video desta anotação — adicione 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.