Noções Básicas de Séries Temporais
Cargas de trabalho de séries temporais no PostgreSQL beneficiam de particionamento por intervalo em timestamps, índices BRIN para varreduras somente de adição (append-only) e políticas de retenção que desanexam ou descartam partições antigas. O particionamento nativo cobre muitos casos de uso de métricas antes do TimescaleDB.
Receita
CREATE TABLE metrics (
device_id int NOT NULL,
ts timestamptz NOT NULL,
value double precision NOT NULL
) PARTITION BY RANGE (ts);
CREATE TABLE metrics_2026_07 PARTITION OF metrics
FOR VALUES FROM ('2026-07-01') TO ('2026-08-01');
CREATE INDEX metrics_2026_07_device_ts ON metrics_2026_07 (device_id, ts DESC);
-- BRIN para partições grandes somente de adição (complemento opcional)
CREATE INDEX metrics_2026_07_ts_brin ON metrics_2026_07 USING brin (ts);Quando usar isso: Inserções de alto volume com consultas limitadas por tempo (dashboards, IoT, métricas de auditoria) onde varreduras sequenciais em uma única tabela gigante estão falhando.
Exemplo de Trabalho
INSERT INTO metrics (device_id, ts, value)
SELECT (random() * 100)::int,
timestamptz '2026-07-01' + (g * interval '1 minute'),
random()
FROM generate_series(0, 10000) g;
EXPLAIN (ANALYZE, BUFFERS)
SELECT date_trunc('hour', ts) AS hour,
avg(value) AS avg_value
FROM metrics
WHERE ts >= timestamptz '2026-07-08'
AND ts < timestamptz '2026-07-09'
AND device_id = 42
GROUP BY 1
ORDER BY 1;
-- Retenção: descartar partição do mês anterior
DROP TABLE IF EXISTS metrics_2025_01;O que isso demonstra:
- O particionamento (partition pruning) limita os dados escaneados à tabela filha de julho de 2026.
- O índice composto
(device_id, ts DESC)suporta filtro + intervalo de tempo. - BRIN opcional para partições muito grandes com heap correlacionado ao tempo.
DROP TABLEem uma partição é uma retenção rápida em comparação comDELETEde milhões de linhas.
Mergulho Profundo
Estratégia de Particionamento
| Granularidade | Quando | Ação de Retenção |
|---|---|---|
| Mensal | Métricas gerais | DROP TABLE ou desanexar para armazenamento frio |
| Diária | Volume muito alto | Automatizar criação via pg_partman ou cron |
| Anual | Baixo volume, arquivo longo | Desanexar para tabela externa de armazenamento de objetos |
-- Criar partição do próximo mês (automatizar em produção)
CREATE TABLE metrics_2026_08 PARTITION OF metrics
FOR VALUES FROM ('2026-08-01') TO ('2026-09-01');BRIN em Timestamps
BRIN se destaca quando as linhas estão fisicamente correlacionadas com ts (somente adição). Tamanho de índice minúsculo em comparação com B-tree.
CREATE INDEX ON metrics_2026_07 USING brin (ts) WITH (pages_per_range = 128);Combine BRIN com particionamento: cada partição mensal recebe seu próprio BRIN.
B-tree vs BRIN
| Índice | Custo de Inserção | Varredura de Intervalo por Tempo | Melhor Para |
|---|---|---|---|
| B-tree (ts) | Mais Alto | Precisa | Leitura/escrita mista, dispositivo+tempo |
| BRIN (ts) | Muito Baixo | Intervalos de bloco grosseiros | Inundação somente de adição |
Frequentemente usa-se ambos: btree em (device_id, ts) para pontos/intervalos por dispositivo, BRIN em ts para agregações amplas, se necessário.
Disciplina Timestamptz
-- Sempre use timestamptz para séries (nunca timestamp sem fuso horário)
ALTER TABLE metrics ALTER COLUMN ts TYPE timestamptz USING ts AT TIME ZONE 'UTC';Armazene em UTC; converta na aplicação para exibição.
Armadilhas
- Partição padrão "catch-all" - Tudo vai para DEFAULT, o particionamento falha. Correção: Monitore os limites das partições; alerte antes que DEFAULT encha.
- Retenção com DELETE - Deletar milhões de linhas incha a tabela e o WAL. Correção:
DROP PARTITIONouDETACH+ arquivar. - Partição futura ausente - Inserções falham na virada do mês. Correção: Automatize a criação de partições com 2 meses de antecedência.
- timestamp sem fuso horário - Bugs de DST em agregações. Correção: Use apenas
timestamptz; veja o artigo sobre cenários de defeitos. - BRIN com ordem de inserção aleatória - BRIN inútil se o heap não estiver correlacionado. Correção: Padrão de carga somente de adição ou pule BRIN.
- Índice global na tabela pai - Não é possível criar índice único em todas as partições sem inclusão da chave de partição (regras PG 11+). Correção: Inclua
tsem chaves únicas.
Alternativas
| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
| TimescaleDB hypertables | Compressão, agregações contínuas aprovadas | Extensão não está na lista de permissões |
| Tabela única sem particionamento | < 50M linhas, operações simples | Deletes de retenção sobrecarregam o vacuum |
| Offload para ClickHouse/BigQuery | Escala OLAP (ver artigo ADR) | Necessidade de junções transacionais de estado do dispositivo |
| Apenas tabelas de rollup | Dashboards fixos | Análise ad-hoc em eventos brutos |
FAQs
Quando particionar?
Quando deletes de retenção ou consultas em intervalos de tempo excedem o SLO em uma única tabela (frequentemente 50GB+ ou milhões/dia).pg_partman?
Extensão popular para manutenção de partições; requer aprovação na lista de permissões.BRIN pages_per_range?
Menor = mais preciso, índice maior; ajuste com EXPLAIN em varreduras de intervalo.Métricas únicas?
UNIQUE (device_id, ts) deve incluir a chave de partição ts em tabelas particionadas PG.Preencher dados antigos?
CREATE partição para o intervalo histórico antes do COPY.Réplicas de leitura?
DDL de partição é replicado; planeje a criação antes da virada do mês na primária.Poolers de conexão?
pgbouncer é bom para picos de INSERT; observe timeouts de instrução em agregações grandes.Métricas JSONB?
Extraia chaves quentes para colunas; BRIN em ts ainda ajuda nas varreduras de partição.Preenchimento de lacunas?
generate_series em SQL para gráficos; não é uma preocupação de armazenamento.Próximo passo?
Artigo TimescaleDB se a extensão for aprovada; caso contrário, automação de partição nativa.Relacionados
- Noções Básicas de Particionamento - particionamento nativo
- BRIN - índices de intervalo de blocos
- TimescaleDB - extensão hypertables
- Melhores Práticas de Séries Temporais - retenção como código
- ADR de Offload OLAP - quando sair do Postgres
Versões da Stack: Esta página foi escrita para PostgreSQL 18.4 (estável 18, manutenção 17), pgvector 0.8+, PostGIS 3.5+, pgbouncer 1.x e Patroni 3.x.