DevGuideTK
← Trilha Backend

Séries temporais

Bancos otimizados para medições ao longo do tempo, com retenção e agregação.

O problema: dados que só crescem

Mil sensores mandando uma medição por minuto são 1,44 milhão de linhas por dia, meio bilhão por ano. Métricas de servidores, cliques, preços e posições de veículos têm o mesmo formato: um carimbo de tempo, o que foi medido e o valor.

Esse tipo de dado, a série temporal, tem características próprias:

CaracterísticaConsequência
Chega sem parar, em ordem de tempoA escrita é constante e o volume, enorme
Quase nunca é alteradoNão precisa das garantias de atualização de um cadastro
É consultado por intervalos e agregado"A média por hora da última semana", e não "a medição 8.392.112"
Perde valor com o tempoO minuto a minuto de ontem importa; o de dois anos atrás, só como média

Um banco relacional comum guarda séries temporais, mas três operações ficam caras com o volume: apagar o que é antigo, guardar com eficiência e agregar intervalos longos. Os bancos de séries temporais atacam essas três.

Os testes desta página usaram o TimescaleDB 2.32, uma extensão do PostgreSQL 17, comparando uma tabela comum com uma hipertabela do TimescaleDB, com os mesmos dados: 1.000 sensores, uma medição por minuto, 10 dias. São 14,4 milhões de linhas.

Particionar pelo tempo

A hipertabela parece uma tabela comum para quem consulta, mas é dividida em pedaços (chunks) por intervalo de tempo, um por dia no teste. Cada pedaço é uma tabela própria, com os próprios índices. As consultas de um intervalo só leem os pedaços daquele intervalo.

ConsultaTabela comumHipertabela
Média por hora de um sensor, 3 dias1,8 ms1,3 ms
Média por hora de todos os sensores, 1 dia (1,44 milhão de linhas)207 ms116 ms

A diferença na consulta foi moderada. Onde o particionamento muda tudo é na hora de apagar.

Apagar o que é antigo

Uma política comum: guardar o minuto a minuto por 7 dias. Apagamos os 3 primeiros dias dos 10, 4,3 milhões de linhas:

TempoEspaço ocupado depois
Tabela comum, DELETE ... WHERE ts < ...4,4 s1.599 MB, o mesmo de antes
Hipertabela, drop_chunks115 ms1.076 MB

O DELETE marca cada linha como apagada, gera log para todas elas e deixa o espaço para ser reaproveitado depois. O arquivo da tabela não diminui sem um VACUUM FULL, que trava a tabela inteira. O drop_chunks descarta os pedaços inteiros, como apagar arquivos: rápido, e o espaço volta na hora. Uma política de retenção roda isso sozinha, todos os dias.

Comprimir o que é antigo

As medições de um mesmo sensor, em ordem de tempo, são muito parecidas entre si. O TimescaleDB converte os pedaços antigos para um formato por coluna, agrupado por sensor, como os bancos colunares:

Espaço
6 dias de medições, sem compressão922 MB
Os mesmos 6 dias, comprimidos66 MB

Catorze vezes menos espaço, em 3,9 segundos de compressão. As consultas continuaram rápidas sobre os pedaços comprimidos: a média por hora de todos os sensores num dia comprimido levou 128 ms, e a de um sensor em 3 dias, 1,4 ms. A troca é que dados comprimidos são mais caros de alterar, o que combina com séries temporais, que quase não mudam.

Resumos prontos

O painel mostra a média por hora dos últimos 30 dias. Calcular isso a partir do minuto a minuto, a cada vez que alguém abre o painel, é refazer o mesmo trabalho. Um resumo contínuo (continuous aggregate) guarda as médias por hora já calculadas e as atualiza conforme os dados chegam:

Média por hora de todos os sensores, 1 diaTempo
Calculada a partir das medições127 ms
Lida do resumo por hora7,7 ms

As médias conferiram até a sexta casa decimal. O teste mostrou um detalhe importante: uma medição gravada depois da criação do resumo não apareceu nele até a atualização seguinte. Na versão testada, os resumos mostram só o que já foi materializado (materialized_only), e uma política de atualização periódica é que traz os dados novos. Depois de uma atualização manual, a medição apareceu.

Gravar em lotes

Séries temporais chegam aos milhares por segundo, e o custo de cada gravação individual pesa:

10 mil mediçõesTempo
Um INSERT por medição, cada um confirmado sozinho10,1 s
Um COPY com todas44 ms

Mais de 200 vezes mais rápido. A aplicação junta as medições por alguns segundos ou por quantidade, e grava o lote de uma vez, como mostra a Implementação.

Por que isso importa quando a IA escreve o código

Um assistente que grava medições costuma tratar a série temporal como um cadastro:

  • Um INSERT por medição, confirmado sozinho, a cada leitura recebida.
  • Retenção com DELETE diário em tabelas enormes, sem particionamento.
  • Painéis que recalculam médias de milhões de linhas a cada carregamento.
  • Nenhuma compressão, e o disco crescendo sem limite.
  • Carimbos de tempo sem fuso, ou com o horário do servidor em vez do horário da medição.
  • Rótulos com valores ilimitados (o identificador de cada requisição) em bancos de métricas, que explodem em número de séries.

O próximo nível compara os bancos de séries temporais e as decisões de retenção, resumo e cardinalidade.

Prompts para aprofundar

Ao abrir no ChatGPT ou no Claude, o prompt é enviado ao serviço escolhido pela URL e também fica copiado na sua área de transferência.

  • Séries temporais em cinco minutos

    Explique em no máximo cinco minutos de leitura o que são séries temporais e por que elas pedem cuidados próprios, para alguém que só guardou cadastros em bancos relacionais.
    
    Cubra só o essencial: as características desse tipo de dado, particionar pelo tempo, apagar o que é antigo, comprimir, manter resumos prontos e gravar em lotes.
    
    Use o exemplo de sensores de temperatura. No final, me faça três perguntas curtas para eu verificar se entendi, e corrija as minhas respostas.
  • Particionamento pelo tempo por dentro

    Quero entender como o particionamento pelo tempo funciona e por que ele ajuda tanto.
    
    Aja como um mentor e vá por etapas, esperando a minha resposta em cada uma:
    1. Pedaços por intervalo de tempo e como a consulta escolhe quais ler.
    2. Por que DELETE em massa é caro e não devolve espaço, e por que descartar um pedaço é instantâneo.
    3. Como escolher o tamanho dos pedaços.
    4. Compressão por coluna dos pedaços antigos.
    5. Particionamento nativo do PostgreSQL contra hipertabelas do TimescaleDB.
    
    Em cada etapa, me faça uma pergunta para verificar se entendi.
  • Resumos e retenção por resolução

    Quero entender como guardar séries temporais por muito tempo sem guardar cada medição para sempre.
    
    Explique:
    1. Retenção por resolução: bruto, por hora, por dia.
    2. Resumos contínuos e a diferença para visões materializadas comuns.
    3. Por que a média das médias pode estar errada, e como guardar soma e contagem.
    4. Resumos que mostram só o materializado contra os que incluem os dados recentes.
    5. Medições que chegam atrasadas e como elas afetam os resumos.
    
    Depois me dê um cenário e peça que eu desenhe a política de retenção.
  • Cardinalidade em métricas

    Quero entender o problema da cardinalidade em bancos de métricas como o Prometheus.
    
    Explique:
    1. O que é uma série, e como nome e rótulos formam séries diferentes.
    2. Por que rótulos com valores ilimitados explodem a memória e o disco.
    3. Exemplos de rótulos seguros e perigosos.
    4. Onde guardar identificadores de usuário, pedido e requisição, se não nas métricas.
    5. Como encontrar as métricas com cardinalidade alta.
    
    Depois me mostre cinco métricas com rótulos e pergunte quais são problemáticas.