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ística | Consequência |
|---|---|
| Chega sem parar, em ordem de tempo | A escrita é constante e o volume, enorme |
| Quase nunca é alterado | Nã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 tempo | O 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.
| Consulta | Tabela comum | Hipertabela |
|---|---|---|
| Média por hora de um sensor, 3 dias | 1,8 ms | 1,3 ms |
| Média por hora de todos os sensores, 1 dia (1,44 milhão de linhas) | 207 ms | 116 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:
| Tempo | Espaço ocupado depois | |
|---|---|---|
Tabela comum, DELETE ... WHERE ts < ... | 4,4 s | 1.599 MB, o mesmo de antes |
Hipertabela, drop_chunks | 115 ms | 1.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ão | 922 MB |
| Os mesmos 6 dias, comprimidos | 66 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 dia | Tempo |
|---|---|
| Calculada a partir das medições | 127 ms |
| Lida do resumo por hora | 7,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ções | Tempo |
|---|---|
Um INSERT por medição, cada um confirmado sozinho | 10,1 s |
Um COPY com todas | 44 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
INSERTpor medição, confirmado sozinho, a cada leitura recebida. - Retenção com
DELETEdiá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.