Non esiste davvero un "modo migliore" per archiviare i dati delle serie storiche e dipende onestamente da una serie di fattori. Tuttavia, mi concentrerò principalmente su due fattori, tra cui:
(1) Quanto è serio questo progetto che merita i tuoi sforzi per ottimizzare lo schema?
(2) Come saranno realmente i tuoi schemi di accesso alle query ?
Con queste domande in mente, discutiamo alcune opzioni di schema.
Tavolo piatto
L'opzione per usare una tabella piatta ha molto più a che fare con la domanda (1) , dove se questo non è un progetto serio o su larga scala, troverai molto più facile non pensare troppo allo schema e basta usare un tavolo piatto, come:
CREATE flat_table(
trip_id integer,
tstamp timestamptz,
speed float,
distance float,
temperature float,
,...);
Non ci sono molti casi in cui consiglierei questo corso, solo se si tratta di un piccolo progetto che non ti garantisce gran parte del tuo tempo.
Dimensioni e fatti
Quindi, se hai eliminato l'ostacolo della domanda (1) e desideri uno schema più prestazionale, questa è una delle prime opzioni da considerare. Include alcune normalizzazioni di base, ma estrae le quantità "dimensionali" dalle quantità misurate "di fatto".
In sostanza, vorrai una tabella per registrare informazioni sui viaggi,
CREATE trips(
trip_id integer,
other_info text);
e una tabella per registrare i timestamp,
CREATE tstamps(
tstamp_id integer,
tstamp timestamptz);
e infine tutti i fatti misurati, con riferimenti di chiave esterna alle tabelle delle dimensioni (ovvero meas_facts(trip_id)
riferimenti trips(trip_id)
e meas_facts(tstamp_id)
riferimenti tstamps(tstamp_id)
)
CREATE meas_facts(
trip_id integer,
tstamp_id integer,
speed float,
distance float,
temperature float,
,...);
A prima vista, questo potrebbe non sembrare molto utile, ma se ad esempio hai migliaia di viaggi simultanei, tutti potrebbero prendere le misure una volta al secondo, al secondo. In tal caso, dovresti registrare nuovamente il timestamp ogni volta per ogni viaggio, anziché utilizzare una sola voce nella tstamps
tabella.
Caso d'uso: questo caso andrà bene se ci sono molti viaggi simultanei per i quali stai registrando dati e non ti dispiace accedere a tutti i tipi di misurazione tutti insieme.
Poiché Postgres legge per riga, ogni volta che si desidera, ad esempio, le speed
misurazioni in un determinato intervallo di tempo, è necessario leggere l'intera riga dalla meas_facts
tabella, che rallenterà sicuramente una query, anche se il set di dati con cui si sta lavorando è non troppo grande, quindi non noteresti nemmeno la differenza.
Suddivisione dei fatti misurati
Per estendere ulteriormente l'ultima sezione, è possibile suddividere le misure in tabelle separate, dove ad esempio mostrerò le tabelle per velocità e distanza:
CREATE speed_facts(
trip_id integer,
tstamp_id integer,
speed float);
e
CREATE distance_facts(
trip_id integer,
tstamp_id integer,
distance float);
Naturalmente, puoi vedere come questo potrebbe essere esteso ad altre misurazioni.
Caso d'uso: quindi questo non ti darà un'enorme velocità per una query, forse solo un aumento lineare della velocità quando si esegue una query su un tipo di misurazione. Questo perché quando si desidera cercare informazioni sulla velocità, è necessario solo leggere le righe dalla speed_facts
tabella, anziché tutte le informazioni extra non necessarie che sarebbero presenti in una riga della meas_facts
tabella.
Quindi, devi leggere enormi quantità di dati su un solo tipo di misurazione, potresti ottenere alcuni vantaggi. Con il caso proposto di 10 ore di dati a intervalli di un secondo, leggeresti solo 36.000 righe, quindi non otterrai mai un vantaggio significativo nel farlo. Tuttavia, se dovessi guardare i dati di misurazione della velocità per 5.000 viaggi che sono durati circa 10 ore, ora stai leggendo 180 milioni di righe. Un aumento lineare della velocità per una query di questo tipo potrebbe comportare alcuni vantaggi, purché sia necessario accedere solo a uno o due tipi di misurazione alla volta.
Array / HStore / & TOAST
Probabilmente non dovrai preoccuparti di questa parte, ma conosco i casi in cui è importante. Se devi accedere a ENORME quantità di dati di serie temporali e sai che devi accedervi tutti in un unico blocco enorme, puoi utilizzare una struttura che utilizzerà le Tabelle TOAST , che essenzialmente memorizza i tuoi dati in dimensioni più grandi e compresse segmenti. Questo porta ad un accesso più rapido ai dati, purché il tuo obiettivo sia quello di accedere a tutti i dati.
Un esempio di implementazione potrebbe essere
CREATE uber_table(
trip_id integer,
tstart timestamptz,
speed float[],
distance float[],
temperature float[],
,...);
In questa tabella, tstart
memorizzerebbe il timestamp per la prima voce nella matrice e ogni voce successiva sarebbe il valore di una lettura per il secondo successivo. Ciò richiede di gestire il timestamp rilevante per ciascun valore di array in un software applicativo.
Un'altra possibilità è
CREATE uber_table(
trip_id integer,
speed hstore,
distance hstore,
temperature hstore,
,...);
dove aggiungi i valori di misurazione come coppie (chiave, valore) di (data / ora, misurazione).
Caso d'uso: questa è un'implementazione probabilmente meglio lasciata a qualcuno che è più a suo agio con PostgreSQL, e solo se sei sicuro che i tuoi schemi di accesso debbano essere schemi di accesso in blocco.
Conclusioni?
Wow, questo è molto più lungo di quanto mi aspettassi, scusa. :)
In sostanza, ci sono una serie di opzioni, ma probabilmente otterrai il massimo profitto dal tuo dollaro usando il secondo o il terzo, poiché si adattano al caso più generale.
PS: la tua domanda iniziale implicava che caricherai in massa i tuoi dati dopo che sono stati raccolti. Se stai trasmettendo i dati in streaming all'istanza di PostgreSQL, dovrai fare qualche ulteriore lavoro per gestire sia il tuo ingestione di dati che il carico di lavoro delle query, ma lo lasceremo per un'altra volta. ;)