it-swarm.com.de

Speichern von Zeitreihendaten

Ich glaube, es handelt sich um einen Zeitreihendatensatz (bitte korrigieren Sie mich, wenn ich falsch liege), dem eine Reihe von Werten zugeordnet sind.

Ein Beispiel wäre das Modellieren eines Autos und das Verfolgen seiner verschiedenen Attribute während einer Reise. Zum Beispiel:

zeitstempel | Geschwindigkeit | zurückgelegte Strecke | Temperatur | usw

Was wäre der beste Weg, um diese Daten zu speichern, damit eine Webanwendung die Felder effizient abfragen kann, um das Maximum, die Minuten zu ermitteln und jeden Datensatz über die Zeit zu zeichnen?

Ich begann einen naiven Ansatz, den Datendump zu analysieren und die Ergebnisse zwischenzuspeichern, damit sie niemals gespeichert werden müssen. Nachdem Sie ein wenig damit gespielt haben, scheint es jedoch, dass diese Lösung aufgrund von Speicherbeschränkungen nicht langfristig skaliert werden kann. Wenn der Cache geleert werden soll, müssen alle Daten erneut analysiert und zwischengespeichert werden.

Unter der Annahme, dass Daten jede Sekunde mit der seltenen Möglichkeit von Datensätzen von mehr als 10 Stunden verfolgt werden, wird generell empfohlen, den Datensatz durch Abtasten alle N Sekunden abzuschneiden.

22
guest82

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 hauptsächlich auf zwei Faktoren konzentrieren, darunter:

(1) Wie ernst ist dieses Projekt, dass es Ihre Mühe verdient, das Schema zu optimieren?

(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 großes Projekt ist Sie werden es viel einfacher finden, nicht zu viel über das Schema nachzudenken und einfach eine flache Tabelle zu verwenden, wie:

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 dies ein winziges Projekt ist, das nicht viel Zeit in Anspruch nimmt.

Dimensionen und Fakten

Wenn Sie also die Hürde der Frage (1) genommen haben und ein leistungsfähigeres Schema wünschen, ist dies eine der ersten Optionen, die in Betracht gezogen werden müssen . Es enthält einige grundlegende Normailisierungen, aber das Extrahieren der 'dimensionalen' Größen aus den gemessenen 'Fakt'-Größen.

Im Wesentlichen möchten Sie eine Tabelle, in der Informationen zu den Reisen aufgezeichnet werden.

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üsselreferenzen auf die Dimensionstabellen (dh meas_facts(trip_id) Referenzen trips(trip_id) & 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 zunächst nicht allzu hilfreich zu sein, aber wenn Sie beispielsweise Tausende von gleichzeitigen Fahrten durchführen, werden möglicherweise alle einmal pro Sekunde und in der Sekunde Messungen durchgeführt. In diesem Fall müssten Sie den Zeitstempel jedes Mal für jede Reise neu aufzeichnen, anstatt nur einen einzelnen Eintrag in der Tabelle tstamps zu verwenden.

Anwendungsfall: Dieser Fall ist gut, wenn es viele gleichzeitige Auslösungen gibt, für die Sie Daten aufzeichnen, und es Ihnen nichts ausmacht, auf alle Messungen zuzugreifen Typen alle zusammen.

Da Postgres zeilenweise liest, müssen Sie jedes Mal, wenn Sie beispielsweise die speed -Messungen über einen bestimmten Zeitraum durchführen möchten, die gesamte Zeile aus der Tabelle meas_facts Lesen, was a definitiv verlangsamt Abfrage: Wenn der Datensatz, mit dem Sie arbeiten, nicht zu groß ist, werden Sie den Unterschied nicht einmal bemerken.

Teilen Sie Ihre gemessenen Fakten auf

Um den letzten Abschnitt etwas weiter zu erweitern, können Sie Ihre Messungen in separate Tabellen aufteilen, in denen ich beispielsweise 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 führt also nicht zu einer enormen Beschleunigung einer Abfrage, möglicherweise nur zu einer linearen Geschwindigkeitssteigerung, wenn Sie nach einer Messung fragen Art. Dies liegt daran, dass Sie beim Nachschlagen von Informationen zur Geschwindigkeit nur Zeilen aus der Tabelle speed_facts Lesen müssen und nicht alle zusätzlichen, nicht benötigten Informationen, die in einer Zeile des meas_facts Tabelle.

Sie müssen also nur riesige Datenmengen über einen Messtyp lesen, um einen gewissen Nutzen zu erzielen. Mit Ihrem vorgeschlagenen Fall von 10 Stunden Daten in Intervallen von einer Sekunde würden Sie nur 36.000 Zeilen lesen, sodass Sie nie wirklich einen signifikanten Vorteil daraus ziehen würden. Wenn Sie sich jedoch Geschwindigkeitsmessdaten für 5.000 Fahrten ansehen, die alle rund 10 Stunden dauerten, möchten Sie jetzt 180 Millionen Zeilen lesen. Eine lineare Erhöhung der Geschwindigkeit für eine solche Abfrage kann einige Vorteile bringen, solange 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 Zeitreihen zugreifen müssen Daten, und Sie wissen, dass Sie in einem großen Block auf alle Daten zugreifen müssen, können Sie eine Struktur verwenden, die TOAST Tables verwendet, die Ihre Daten im Wesentlichen in größeren, komprimierten Segmenten speichert. Dies führt zu einem schnelleren Zugriff auf die Daten, solange Sie auf alle Daten zugreifen möchten.

Eine beispielhafte Implementierung könnte sein

CREATE uber_table(
  trip_id integer,
  tstart timestamptz,
  speed float[],
  distance float[],
  temperature float[],
  ,...);

In dieser Tabelle würde tstart den Zeitstempel für den ersten Eintrag im Array speichern, 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,
  ,...);

hier fügen Sie Ihre Messwerte als (Schlüssel, Wert) Paare von (Zeitstempel, Messung) hinzu.

Anwendungsfall: Dies ist eine Implementierung, die wahrscheinlich besser jemandem überlassen wird, der mit PostgreSQL besser vertraut ist, und nur dann, wenn Sie sicher sind, dass Ihre Zugriffsmuster erforderlich sind Massenzugriffsmuster.

Schlussfolgerungen?

Wow, das wurde viel länger als ich erwartet hatte, sorry. :) :)

Grundsätzlich gibt es eine Reihe von Optionen, aber Sie werden wahrscheinlich den größten Knall für Ihr Geld bekommen, wenn Sie die zweite oder dritte verwenden, da sie für den allgemeineren Fall geeignet sind.

P.S.: Ihre erste Frage implizierte, dass Sie Ihre Daten nach dem Sammeln in großen Mengen laden werden. Wenn Sie die Daten in Ihre PostgreSQL-Instanz streamen, müssen Sie einige weitere Arbeiten ausführen, um sowohl die Datenaufnahme als auch die Abfragearbeitslast zu bewältigen. Wir belassen dies jedoch für ein anderes Mal. ;)

31
Chris

Seine 2019 und diese Frage verdient eine aktualisierte Antwort.

  • Ob der Ansatz der beste ist oder nicht, überlasse ich Ihnen beim Benchmarking und Testen, aber hier ist ein Ansatz.
  • Verwenden Sie eine Datenbankerweiterung namens timescaledb
  • Dies ist eine Erweiterung, die auf Standard-PostgreSQL installiert ist und einige Probleme behandelt, die beim Speichern von Zeitreihen auftreten

Erstellen Sie anhand Ihres Beispiels zunächst eine einfache Tabelle in PostgreSQL

Schritt 1

CREATE TABLE IF NOT EXISTS trip (
    ts TIMESTAMPTZ NOT NULL PRIMARY KEY,
    speed REAL NOT NULL,
    distance REAL NOT NULL,
    temperature REAL NOT NULL
) 

Schritt 2

  • Verwandeln Sie dies in ein sogenanntes hypertable in der Welt von timescaledb.
  • Mit einfachen Worten, es handelt sich um eine große Tabelle, die kontinuierlich in kleinere Tabellen mit einem bestimmten Zeitintervall unterteilt wird, beispielsweise einen Tag, an dem jede Minitabelle als Block bezeichnet wird
  • Diese Minitabelle ist nicht offensichtlich, wenn Sie Abfragen ausführen, obwohl Sie sie in Ihre Abfragen aufnehmen oder ausschließen können

    SELECT create_hypertable ('trip', 'ts', chunk_time_interval => Intervall '1 Stunde', if_not_exists => TRUE) ;

  • Was wir oben getan haben, ist, unseren Reisetisch zu nehmen und ihn auf der Grundlage der Spalte 'ts' stündlich in Mini-Chunk-Tische zu unterteilen. Wenn Sie einen Zeitstempel von 10:00 bis 10:59 hinzufügen, werden diese zu 1 Block hinzugefügt, aber 11:00 wird in einen neuen Block eingefügt, und dies wird unendlich fortgesetzt.

  • Wenn Sie keine Daten unendlich speichern möchten, können Sie auch Chunks verwenden, die älter als beispielsweise 3 Monate sind

    SELECT drop_chunks (Intervall '3 Monate', 'Reise') ;

  • Sie können auch eine Liste aller bis zum Datum erstellten Chunks mit einer Abfrage wie erhalten

    SELECT chunk_table, table_bytes, index_bytes, total_bytes FROM chunk_relation_size ('trip') ;

  • Auf diese Weise erhalten Sie eine Liste aller bis zum Datum erstellten Minitabellen. Wenn Sie möchten, können Sie eine Abfrage für die letzte Minitabelle aus dieser Liste ausführen

  • Sie können Ihre Abfragen so optimieren, dass sie Blöcke einschließen, ausschließen oder nur mit den letzten N Blöcken usw. arbeiten

1
PirateApp