antonio leandro

dados e armazenamento

Gorilla: A Fast, Scalable, In-Memory Time Series Database

paper · núcleo · Tuomas Pelkonen, Scott Franklin, Justin Teller, et al. ·

a tese

monitoramento não precisa de acid nem de histórico: guardar só as últimas 26 horas em memória, comprimidas de 16 para 1,37 bytes por ponto, vale mais que garantir que nenhum ponto se perca

o que fica

  1. Em monitoramento o dado recente vale mais que o antigo, e isso justifica trocar durabilidade por disponibilidade de escrita e leitura no instante presente.
  2. Delta-of-delta comprime 96% dos timestamps a um único bit, porque métrica de infraestrutura chega em intervalo fixo com jitter pequeno.
  3. XOR com o valor anterior comprime 51% dos valores a um único bit, já que mais da metade das amostras repete exatamente a amostra anterior.
  4. O bloco de duas horas é o joelho da curva: acima disso a compressão melhora pouco e consultas de janela curta pagam decode desnecessário.
  5. Derrubar a latência de leitura muda o comportamento de quem consulta — o volume saltou de 450 queries por segundo no ODS para 5.000 em regime e 40.000 no pico.
  6. Os autores registram que o protótipo comprimido saiu rápido e a tolerância a falhas levou meses: o custo do sistema estava na disponibilidade, não no algoritmo.

o problema

No começo de 2013 o monitoramento do Facebook rodava sobre o ODS, um TSDB construído em cima do HBase. Para escrever, funcionava. Para ler, não: o percentil 90 das consultas tinha subido para vários segundos, o que travava a automação de alarme, e uma análise interativa sobre alguns milhares de séries levava dezenas de segundos. Consultas maiores sobre dados esparsos davam timeout, porque o store estava tunado para priorizar escrita. O efeito mais caro não era a latência em si — era que os engenheiros passaram a se autocensurar, deixando de fazer as perguntas que a ferramenta não aguentava.

Trocar o HBase estava fora de questão: havia cerca de 2 PB ali dentro. Cache também não fechava. O read-through existente servia dashboards que compartilhavam séries, mas errava justamente na consulta ao ponto mais recente, que caía direto no HBase. Um write-through em Memcache exigiria um ciclo de read/write para anexar cada ponto novo a uma série existente. E a escala crescia: na primavera de 2015 eram 2 bilhões de séries únicas e cerca de 12 milhões de pontos por segundo, mais de 1 trilhão por dia. A 16 bytes por ponto, guardar só as últimas 26 horas custaria 16 TB de RAM.

a ideia

Duas observações sustentam o sistema inteiro. Primeira: 85% das consultas ao ODS pediam dados das últimas 26 horas. Segunda: quem olha monitoramento não olha ponto individual, olha agregado e transição de estado — saber que o serviço quebrou agora vale mais que saber que ele quebrou há uma hora.

Disso sai um write-through cache em memória das últimas 26 horas, que aceita perder pequenas quantidades de dado no caminho de escrita desde que continue respondendo. O HBase segue como fonte da verdade e como resposta para consulta histórica. E a viabilidade veio da compressão: 16 bytes por ponto viram 1,37 bytes em média, redução de 12x, o que transforma os 16 TB em algo que cabe em oitenta máquinas por cluster.

como funciona

Um ponto é uma tupla de string key, timestamp de 64 bits e um double. A chave define o shard por hash; escalar é adicionar host e reajustar o hash. Os dados vão para blocos de duas horas.

Timestamps usam delta-of-delta com prefixo de tamanho variável. O cabeçalho do bloco guarda um timestamp alinhado à janela de duas horas e o primeiro delta em 14 bits. Depois, para cada ponto, D = (tn − tn−1) − (tn−1 − tn−2). Se D é zero, um bit 0. Entre [−63, 64], 10 mais 7 bits. Entre [−255, 256], 110 mais 9 bits. Entre [−2047, 2048], 1110 mais 12 bits. Fora disso, 1111 mais D em 32 bits. As faixas foram escolhidas por amostragem da produção — a de 9 bits existe porque muita série chega a cada 4 minutos e um ponto faltando ainda cabe ali.

Valores são XORados com o anterior. Se o XOR é zero, um bit 0. Se não, bit 1 mais um bit de controle: se o bloco de bits significativos cabe dentro da janela do valor anterior, controle 0 e só os bits significativos; senão controle 1, 5 bits para a contagem de leading zeros, 6 bits para o comprimento e os bits em si. 51% dos valores viram um bit; os 30% com controle 10 ficam em 26,6 bits médios, os 19% restantes em 36,9.

Em memória, cada shard tem um TSmap: um vector de shared_ptr para as séries mais um unordered_map case-insensitive por nome. O vector permite varredura paginada, o mapa dá lookup constante, e copiar o vector leva microssegundos — o que evita seção crítica longa contra o fluxo de escrita. Um spin lock de 1 byte por série basta, porque cada série escreve pouco. O bloco aberto é uma string append-only; blocos fechados são imutáveis e copiados para slabs grandes contra fragmentação. A leitura devolve o bloco comprimido cru pela RPC: descompressão é problema do cliente.

Persistência vai para GlusterFS com 3x de replicação: um log append-only por shard, com flush a cada 64 kB, um block file a cada duas horas e um checkpoint que marca o flush bem-sucedido. Não é write-ahead log. Quando um nó cai, o ShardManager, baseado em Paxos, redistribui os shards; o cliente de escrita bufferiza um minuto e descarta o que for mais velho que isso. O novo dono relê cerca de 16 GB por shard e volta funcional em uns 5 minutos.

o que isso custou

O log não é WAL: um crash antes do flush perde alguns segundos de dado. As duas réplicas ficam em regiões diferentes e recebem o stream sem nenhuma tentativa de consistência entre si — divergência é aceita, e a leitura vai na instância mais próxima que estiver viva. Durante recuperação, a resposta volta parcial e marcada como parcial, e cabe a quem chamou decidir se dá para trabalhar assim.

O modelo de dados é pobre de propósito: uma string por série, sem o conjunto de tags do OpenTSDB nem os metadados por evento do InfluxDB. Extrair estrutura da chave é trabalho de camada superior. Só 26 horas cabem ali; o resto é HBase, com roll-up temporal que perde granularidade. E há duas cópias completas em memória, ineficiência que os autores assumem — “high availability trumps resource efficiency”. A compressão também amarra o leitor ao bloco de duas horas: quem quer trinta segundos de dado decodifica duas horas.

A admissão mais útil está na seção de experiência: o protótipo do TSDB comprimido em memória saiu em pouco tempo, e foram vários meses de trabalho para torná-lo tolerante a falhas.

onde isso aparece hoje

O par delta-of-delta para timestamps e XOR para floats virou vocabulário padrão de série temporal. O TSDB do Prometheus documenta explicitamente sua compressão como derivada deste paper, e o Facebook abriu depois uma implementação do motor em open source, o Beringei. A separação entre uma camada quente em memória com janela curta e um store frio em disco também deixou de ser exótica.

Dentro da própria casa, o paper mostra o que a latência baixa destravou: uma busca de correlação por PPMCC sobre até 1 milhão de séries, usada para responder “o que aconteceu perto da hora em que meu serviço quebrou”; visualizações densas de horizon charts; e o roll-up, que saiu de jobs map-reduce sobre o HBase para uma varredura direta dos blocos fechados. O trabalho futuro declarado era uma camada intermediária em flash, com cerca de duas semanas de dado em resolução cheia.

lido na íntegra por pipeline de llm, revisado por antonio leandro antes de publicar ·