Aggregazioni pre-calcolate, gestione automatica dei chunk e query di dashboard che restano veloci anche con milioni di eventi.
Il problema reale: la dashboard non dovrebbe ricalcolare sempre tutto
Immagina due database sullo stesso laptop, entrambi con 10 milioni di record di richieste HTTP. La domanda è la stessa:
- quante richieste arrivano ogni ora
- qual è la loro latenza media
Nel primo caso PostgreSQL calcola la risposta leggendo (e raggruppando) gli eventi grezzi. Nel secondo caso si legge invece un riepilogo orario già pronto.
Risultato tipico:
- query sugli eventi: ~1,6 secondi
- query sul riepilogo: ~10 millisecondi
Le risposte coincidono. A cambiare è la quantità di lavoro: o fai aggregazione “live” su milioni di righe, oppure leggi risultati già aggregati.
A questo punto viene spontaneo dire: “Ok, allora basta salvare i riepiloghi anche in PostgreSQL”. Vero. Il punto è un altro: mantenerli aggiornati in modo affidabile e incrementale, senza dover ricalcolare l’intera storia ogni volta che arrivano nuovi dati.
È qui che entrano in gioco le continuous aggregates.
Che cos’è TimescaleDB (in pratica)
La cosa più importante da chiarire subito: TimescaleDB è PostgreSQL.
Non è un fork, non è un database “a parte”, non ti costringe a un linguaggio proprietario. È un’estensione, esattamente come accade con PostGIS (geospaziale) o pgvector (embedding).
Conseguenze pratiche molto concrete:
- continui a usare SQL
- continui a usare i tuoi strumenti: migration framework (Django/Rails/Prisma), pooler,
pg_dump, monitoraggio - l’installazione a livello database è banale:
CREATE EXTENSION ...
Un dettaglio operativo spesso sottovalutato: l’estensione va abilitata per database. Se crei un nuovo database, devi eseguire di nuovo CREATE EXTENSION lì.
Perché le serie temporali stressano Postgres (anche quando Postgres è “abbastanza”)
I workload time-series hanno alcuni tratti ricorrenti:
- i record arrivano continuamente (append-heavy)
- la maggior parte dei dati storici non cambia
- le query filtrano quasi sempre un intervallo di tempo
- dashboard e report fanno spesso le stesse aggregazioni su finestre temporali simili
Esempi classici: richieste HTTP, prezzi finanziari, sensori IoT, log applicativi, metriche di pipeline/AI agent, ecc.
PostgreSQL gestisce dataset grandi, ma con una event table che cresce senza sosta emergono costi sempre più visibili:
- indici “attivi” molto grandi → più I/O e più manutenzione
- cancellare dati vecchi → lavoro di cleanup (vacuum, bloat, ecc.)
- dashboard che “scansiona la storia” → ricalcoli ripetuti e costosi
Postgres offre già strumenti (ad esempio partitioning), ma TimescaleDB aggiunge tre leve pensate specificamente per questo scenario:
- gestione automatica dei chunk (partizionamento time-oriented gestito dal motore)
- storage colonnare / compressione (in base alla configurazione)
- continuous aggregates (aggregazioni incrementali mantenute nel tempo)
L’idea non è “magia”: è ingegnerizzare i pattern più comuni delle serie temporali in modo robusto e ripetibile.
Continuous aggregates: la differenza tra “calcolare” e “leggere”
La logica è semplice:
- se la tua dashboard chiede “per ora” o “per giorno”, probabilmente non vuoi raggruppare ogni volta la tabella eventi
- vuoi una vista/materializzazione che contenga già: conteggi, medie, min/max, ecc.
Il tema però è la manutenzione:
- il primo calcolo “storico” può richiedere tempo (ed è normale)
- poi serve un meccanismo affidabile per refresh incrementali man mano che arrivano nuovi record (e, in alcuni casi, quando cambi regole o dati)
TimescaleDB rende questo aggiornamento time-aware: l’obiettivo è evitare di “rifare tutto da zero” quando ti basta aggiornare solo l’ultima finestra temporale.
Risultato: query di dashboard che restano reattive anche quando la tabella eventi cresce in modo aggressivo.
Nota importante: i numeri (1,6s vs 10ms) dipendono da schema, hardware, distribuzione dei dati, filtri, ecc. Quello che conta è il meccanismo: meno lavoro a query-time.
Il dataset “tipo”: una riga per richiesta HTTP
Uno scenario estremamente comune per ragionare su time-series è una tabella che rappresenta un access log “strutturato”:
- time: timestamp dell’evento (la colonna più importante)
- campi “chi/cosa”: server, dominio, sessione, URL
- esito: status code
- performance: durata (latenza)
- contesto: geografia, user agent, bytes, ecc.
La chiave: una riga per evento, potenzialmente milioni o miliardi.
Tutto quello che farai in analisi parte quasi sempre da:
1) filtrare un intervallo temporale
2) raggruppare
3) calcolare aggregati
Il “20% di SQL” che copre l’80% dell’analisi time-series
1) Date arithmetic con interval
Filtrare “ultime 24 ore”, “ultimi 7 giorni”, “ultimi 15 minuti” dovrebbe essere naturale. In PostgreSQL lo è:
timestamp - interval '24 hours'timestamp - interval '7 days'
Sono valori tipizzati, non stringhe “finte”. È un dettaglio che fa la differenza nella qualità delle query e nella leggibilità.
2) GROUP BY per impilare eventi in “pile”
Il salto mentale utile: gli aggregati non si calcolano “per riga”, ma per gruppo.
Esempio tipico: raggruppare per status per capire quante 200/404/500 hai visto e con quale latenza media.
3) Aggregazioni condizionali con FILTER
Quando vuoi più contatori in una sola passata sui dati (totale, errori 5xx, not found 404, ecc.), FILTER è spesso più leggibile (e in genere più efficiente) rispetto a CASE WHEN ... ripetuti.
È anche il modo più pulito per derivare metriche come error rate senza lanciare query multiple.
4) Percentili: utilissimi, ma costosi se “esatti”
La latenza media spesso racconta una storia rassicurante. Il P95/P99 racconta quella vera per gli utenti peggiori.
In PostgreSQL, i percentili esatti richiedono ordinamento dei valori (ORDER BY all’interno dell’aggregazione). Su poche migliaia di righe è ok; su decine/centinaia di milioni può diventare un problema serio.
Qui la lezione pratica è: misura e valuta alternative approssimate quando la scala cresce.
Tipi dati che contano davvero in questi schemi
Tre scelte ricorrenti fanno la differenza nel tempo:
-
timestamptz: preferiscilo sempre atimestamp“senza time zone”.timestamptzrappresenta un momento assoluto e si adatta al fuso del client in output.timestampinvece è un orario “da parete”, ambiguo. -
jsonb: perfetto per attributi rari/long-tail che non meritano una colonna dedicata, con possibilità di indicizzazione. -
uuid: utile per identificatori robusti e distribuiti, ma ha implicazioni interessanti su indici e locality (tema che in dataset grandi emerge presto).
La skill che sblocca tutto: leggere EXPLAIN
Se devi portarti a casa una sola competenza “operativa” quando lavori su performance, è questa.
-
EXPLAINmostra il piano previsto (senza eseguire) -
EXPLAIN ANALYZEesegue e mostra cosa è successo davvero -
EXPLAIN ANALYZE BUFFERSaggiunge quanta memoria/pagine sono state toccate
Quest’ultimo è sottoutilizzato, ma spesso è quello che ti dice la verità: non solo “quanto ci ha messo”, ma quanta roba ha dovuto leggere.
E soprattutto ti aiuta a distinguere:
- sequential scan: legge molte righe per trovare quelle utili
- index scan: salta più direttamente alle righe rilevanti
Nelle serie temporali, dove quasi tutto è filtrato per tempo, capire quando stai ancora scansionando troppo (e perché) è metà del lavoro.
Sintesi: cosa cambia davvero con TimescaleDB
Per dati time-series, il punto non è “Postgres è lento”. Il punto è che certi pattern diventano inevitabilmente costosi quando:
- la tabella eventi cresce continuamente
- le dashboard ricalcolano sempre gli stessi aggregati
- retention e manutenzione iniziano a pesare
TimescaleDB resta dentro Postgres (stesso SQL, stessi strumenti), ma aggiunge primitive mirate: chunk management, ottimizzazioni di storage, e soprattutto continuous aggregates per trasformare query di dashboard da “calcola su milioni di righe” a “leggi un riepilogo mantenuto aggiornato”.
L’implicazione pratica è chiara: se stai costruendo osservabilità, telemetria, metriche di prodotto o qualsiasi sistema che vive di grafici per intervalli temporali, vale la pena progettare da subito pensando a pre-aggregazione time-aware e a un workflow di misurazione basato su EXPLAIN (ANALYZE, BUFFERS)—prima ancora di inseguire micro-ottimizzazioni o indici “a tentoni”.
Articolo originale: https://frontendfacile.it/blog/timescaledb-quando-postgresql-incontra-le-serie-temporali-senza-smettere-di-esse
Top comments (0)