O Desafio das Partições Largas
Quando você armazena petabytes de dados de séries temporais no Apache Cassandra, poucas coisas podem estragar seu dia como uma partição larga. Conforme os eventos se acumulam com o tempo, as partições podem crescer para gigabytes, causando picos de latência de leitura para segundos, timeouts e até instabilidade no cluster.
A equipe da TimeSeries Abstraction da Netflix enfrentou exatamente esse problema. O sistema ingere milhões de eventos por segundo, e embora o Cassandra lide bem com a escrita, o lado da leitura começa a sofrer quando certas partições ficam grandes demais. A resposta típica seria adicionar mais hardware, mas isso é caro e não resolve a causa raiz.
Em vez disso, eles construíram um sistema de particionamento dinâmico que detecta e divide automaticamente partições largas no nível do ID individual. Essa abordagem reduziu a latência média de leitura para partições largas de segundos para dígitos duplos baixos em milissegundos, e a latência de cauda de vários segundos para cerca de 200ms.
Por que Partições Largas São um Problema
O Cassandra é projetado para alta taxa de escrita, mas as leituras podem se tornar problemáticas quando uma única partição contém muitos dados. Veja o que acontece:
- Alta latência de leitura: Ler uma partição de vários gigabytes requer escanear muitos dados, empurrando a latência para os segundos.
- Pausas de Garbage Collection: Alocações grandes de heap durante leituras podem causar pausas frequentes de GC.
- Enfileiramento de threads: Quando muitas requisições direcionam para a mesma partição larga, as threads se acumulam esperando a varredura terminar.
Esses problemas podem cascatear em timeouts e até indisponibilidade. Para dados de séries temporais, as partições crescem naturalmente com o tempo, tornando isso um desafio persistente.

Solução 1: Re-particionamento por Time Slice
A primeira abordagem foi ajustar o particionamento no nível da tabela. A Netflix usa time slices discretos, onde cada slice pode ter sua própria estratégia de particionamento. Monitorando os tamanhos das partições via nodetool tablehistograms do Cassandra, eles puderam detectar quando as partições estavam muito pequenas ou grandes demais.
Um worker em background calcula um fator de ajuste e atualiza o intervalo de time bucket para slices futuros. Aqui está um exemplo simplificado de como a lógica de detecção e ajuste pode funcionar:
import subprocess
import json
def get_partition_histogram(table_name):
"""Busca histograma de tamanho de partição do nodetool"""
output = subprocess.check_output(["nodetool", "tablehistograms", table_name])
return parse_histogram(output)
def adjust_time_bucket(histogram, target_size_mb):
"""Calcula novo intervalo de time bucket baseado nos tamanhos observados"""
p99_size_mb = histogram["p99"] / (1024 * 1024)
if p99_size_mb < target_size_mb:
# Aumenta intervalo para fazer partições maiores
new_interval = current_interval * (target_size_mb / p99_size_mb)
else:
# Diminui intervalo para fazer partições menores
new_interval = current_interval / (p99_size_mb / target_size_mb)
return new_interval
Isso funcionou bem para datasets onde a maioria das partições estava mal configurada. Mas falhou quando apenas uma pequena porcentagem de IDs gerava dados excessivos. Nesses casos, re-particionar a tabela inteira superparticionaria a maioria dos IDs normais.

Solução 2: Particionamento Dinâmico por ID
Para o problema dos outliers, a Netflix construiu um pipeline assíncrono que divide partições largas no nível do ID. Tem três estágios:
- Detecção: Cada leitura rastreia bytes lidos por partição. Se os bytes excederem um limite, um evento é enviado ao Kafka.
- Planejamento e Divisão: Um planejador lê a partição inteira para calcular um plano de divisão ótimo, e então delega a divisão para uma estratégia que distribui os dados entre múltiplos buckets.
- Atendendo Leituras: O servidor usa Bloom filters para verificar rapidamente se uma partição foi dividida, e então roteia as leituras para os pedaços menores.
Aqui está um exemplo conceitual do evento de detecção e dos metadados da divisão:
{
"time_slice": "data_20260328",
"time_series_id": "profileId:123",
"time_bucket": 7,
"event_bucket": 2,
"immutable": true,
"version": "0"
}
{
"pre_split_data": {
"time_slice": "data_20260328",
"time_series_id": "6313825",
"time_bucket": 0,
"event_bucket": 2
},
"post_split_data": {
"time_slice": "wide_data_20260328_0",
"event_bucket_partition_strategy": {
"target_event_buckets": 2,
"start_event_bucket": 32
}
}
}
Checksums garantem a integridade da divisão, e a partição original nunca é deletada, fornecendo um fallback seguro.

Lições Aprendidas e Conclusões
A jornada da Netflix oferece insights valiosos para quem enfrenta desafios similares de escalabilidade:
- Reduza a superfície de ataque: Comece com soluções mais simples que ainda tragam impacto. Eles tentaram primeiro o re-particionamento no nível da tabela antes de partir para a divisão por ID.
- Construa confiança: Invista em mecanismos de validação como checksums e rollouts em fases para garantir a correção antes da implantação completa.
- Monitore e adapte: Use ferramentas de introspecção para monitorar continuamente a saúde das partições e ajustar as estratégias dinamicamente.
Limitações e Considerações
Essa abordagem não é uma bala de prata. Dividir partições mutáveis ainda é complexo e não suportado. Além disso, a detecção depende de leituras, então há uma janela curta onde algumas leituras ainda podem atingir a partição larga. Para casos extremos, eles implementaram um recurso 'Partial Return' que aborta requisições que excedem os SLOs de latência.
Próximos Passos para Aprender
Se você está lidando com partições largas no Cassandra, comece monitorando os tamanhos das partições e as latências de leitura. Considere se você pode ajustar sua estratégia de particionamento no nível da tabela primeiro. Se você tem IDs outliers, pense em implementar um pipeline similar de detecção e divisão. Para mais sobre a estratégia de particionamento subjacente, confira o Netflix Tech Blog.
Também explore tópicos relacionados como novidades do Python 3.15 Alpha 5 ou destaques do Python 3.14.3 para mais insights técnicos.