Es gibt wirklich keinen "besten Weg", Zeitreihendaten zu speichern, und das hängt ehrlich gesagt von einer Reihe von Faktoren ab. Ich werde mich jedoch in erster Linie auf zwei Faktoren konzentrieren:
(1) Wie ernst ist dieses Projekt, das Ihre Bemühungen zur Optimierung des Schemas verdient?
(2) Wie werden Ihre Abfragezugriffsmuster wirklich aussehen?
Lassen Sie uns unter Berücksichtigung dieser Fragen einige Schemaoptionen diskutieren.
Flacher Tisch
Die Option, einen flachen Tisch zu verwenden, hat viel mehr mit Frage (1) zu tun. Wenn dies kein ernstes oder umfangreiches Projekt ist, fällt es Ihnen viel leichter, nicht zu viel über das Schema nachzudenken benutze einfach einen flachen Tisch als:
CREATE flat_table(
trip_id integer,
tstamp timestamptz,
speed float,
distance float,
temperature float,
,...);
Es gibt nicht viele Fälle, in denen ich diesen Kurs empfehlen würde, nur wenn es sich um ein kleines Projekt handelt, das nicht viel Zeit in Anspruch nimmt.
Maße und Fakten
Wenn Sie also die Hürde von Frage (1) genommen haben und ein leistungsfähigeres Schema wünschen, ist dies eine der ersten zu berücksichtigenden Optionen. Es beinhaltet einige grundlegende Normailisierungen, aber das Extrahieren der 'dimensionalen' Größen aus den gemessenen 'Tatsachen'-Größen.
Im Wesentlichen möchten Sie eine Tabelle, um Informationen über die Reisen aufzuzeichnen,
CREATE trips(
trip_id integer,
other_info text);
und eine Tabelle zum Aufzeichnen von Zeitstempeln,
CREATE tstamps(
tstamp_id integer,
tstamp timestamptz);
und schließlich alle Ihre gemessenen Fakten mit Fremdschlüsselverweisen auf die Dimensionstabellen (dh meas_facts(trip_id)
Referenzen trips(trip_id)
und meas_facts(tstamp_id)
Referenzen tstamps(tstamp_id)
)
CREATE meas_facts(
trip_id integer,
tstamp_id integer,
speed float,
distance float,
temperature float,
,...);
Dies scheint auf den ersten Blick nicht sehr hilfreich zu sein, aber wenn Sie beispielsweise Tausende von Fahrten gleichzeitig durchführen, werden die Messungen möglicherweise alle einmal pro Sekunde und in der Sekunde durchgeführt. In diesem Fall müssten Sie den Zeitstempel jedes Mal neu für jede Reise aufzeichnen, anstatt nur einen einzelnen Eintrag in der tstamps
Tabelle zu verwenden.
Anwendungsfall: Dieser Fall ist hilfreich, wenn Sie bei vielen gleichzeitigen Fahrten Daten aufzeichnen und es Ihnen nichts ausmacht, auf alle Messtypen zusammen zuzugreifen.
Da Postgres zeilenweise liest, müssen Sie zu jedem Zeitpunkt, zu dem Sie beispielsweise die speed
Messungen über einen bestimmten Zeitraum durchführen möchten, die gesamte Zeile aus der meas_facts
Tabelle auslesen , was eine Abfrage auf jeden Fall verlangsamt, auch wenn der Datensatz, mit dem Sie arbeiten, derselbe ist nicht zu groß, dann merkt man den Unterschied gar nicht.
Aufteilen Ihrer gemessenen Fakten
Um den letzten Abschnitt noch ein wenig zu erweitern, können Sie Ihre Messungen in separate Tabellen aufteilen, in denen ich zum Beispiel die Tabellen für Geschwindigkeit und Entfernung zeige:
CREATE speed_facts(
trip_id integer,
tstamp_id integer,
speed float);
und
CREATE distance_facts(
trip_id integer,
tstamp_id integer,
distance float);
Natürlich können Sie sehen, wie dies auf die anderen Messungen ausgeweitet werden kann.
Anwendungsfall: Dies bedeutet also keine enorme Geschwindigkeitssteigerung für eine Abfrage, möglicherweise nur eine lineare Geschwindigkeitssteigerung, wenn Sie nach einem Messtyp abfragen. Dies liegt daran, dass Sie zum Nachschlagen von Informationen zur Geschwindigkeit nur Zeilen aus der speed_facts
Tabelle lesen müssen und nicht alle zusätzlichen, nicht benötigten Informationen, die in einer meas_facts
Tabellenzeile vorhanden wären .
Wenn Sie also große Datenmengen nur zu einem Messtyp lesen müssen, können Sie einige Vorteile erzielen. Bei Ihrem vorgeschlagenen Fall von 10 Stunden Daten in Abständen von einer Sekunde würden Sie nur 36.000 Zeilen lesen, so dass Sie nie wirklich einen nennenswerten Nutzen daraus ziehen würden. Wenn Sie sich jedoch Geschwindigkeitsmessdaten für 5.000 Fahrten mit einer Gesamtdauer von 10 Stunden ansehen, sollten Sie jetzt 180 Millionen Zeilen lesen. Eine lineare Erhöhung der Geschwindigkeit für eine solche Abfrage kann sich als vorteilhaft erweisen, sofern Sie nur auf einen oder zwei der Messtypen gleichzeitig zugreifen müssen.
Arrays / HStore / & TOAST
Sie müssen sich wahrscheinlich nicht um diesen Teil kümmern, aber ich kenne Fälle, in denen es wichtig ist. Wenn Sie auf große Mengen von Zeitreihendaten zugreifen müssen und wissen, dass Sie auf alle Daten in einem großen Block zugreifen müssen, können Sie eine Struktur verwenden, die die TOAST-Tabellen verwendet , in denen Ihre Daten im Wesentlichen in größeren, komprimierten Formaten gespeichert werden Segmente. Dies führt zu einem schnelleren Zugriff auf die Daten, solange Ihr Ziel darin besteht, auf alle Daten zuzugreifen.
Eine beispielhafte Implementierung könnte sein
CREATE uber_table(
trip_id integer,
tstart timestamptz,
speed float[],
distance float[],
temperature float[],
,...);
In dieser Tabelle tstart
würde der Zeitstempel für den ersten Eintrag im Array gespeichert, und jeder nachfolgende Eintrag wäre der Wert eines Messwerts für die nächste Sekunde. Dazu müssen Sie den relevanten Zeitstempel für jeden Array-Wert in einer Anwendungssoftware verwalten.
Eine andere Möglichkeit ist
CREATE uber_table(
trip_id integer,
speed hstore,
distance hstore,
temperature hstore,
,...);
wo Sie Ihre Messwerte als (Schlüssel, Wert) Paare von (Zeitstempel, Messung) hinzufügen.
Anwendungsfall: Dies ist eine Implementierung, die wahrscheinlich besser jemandem überlassen wird, der mit PostgreSQL vertraut ist, und nur dann, wenn Sie sicher sind, dass Ihre Zugriffsmuster Bulk-Zugriffsmuster sein müssen.
Schlussfolgerungen
Wow, das hat viel länger gedauert als ich erwartet hatte, sorry. :)
Grundsätzlich gibt es eine Reihe von Optionen, aber Sie werden wahrscheinlich den größten Gewinn für Ihr Geld erzielen, wenn Sie die zweite oder dritte Option verwenden, da sie für den allgemeineren Fall geeignet sind.
PS: Ihre anfängliche Frage implizierte, dass Sie Ihre Daten in großen Mengen laden, nachdem sie alle gesammelt wurden. Wenn Sie die Daten in Ihre PostgreSQL-Instanz streamen, müssen Sie einige weitere Arbeiten ausführen, um sowohl die Datenerfassung als auch die Abfragearbeit zu bewältigen, aber das belassen wir für eine andere Zeit. ;)