DEV Community

frontendfacile.it
frontendfacile.it

Posted on Originally published at frontendfacile.it

TimescaleDB: quando PostgreSQL incontra le serie temporali (senza smettere di essere PostgreSQL)

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:

  1. gestione automatica dei chunk (partizionamento time-oriented gestito dal motore)
  2. storage colonnare / compressione (in base alla configurazione)
  3. 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 a timestamp “senza time zone”. timestamptz rappresenta un momento assoluto e si adatta al fuso del client in output. timestamp invece è 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.

  • EXPLAIN mostra il piano previsto (senza eseguire)
  • EXPLAIN ANALYZE esegue e mostra cosa è successo davvero
  • EXPLAIN ANALYZE BUFFERS aggiunge 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)