5 Tipps zu Time Series Collections mit MongoDB
Insbesondere im IoT-Umfeld sind Zeitreihen von Sensorwerten die zentrale Form, in der Daten anfallen. Seit Version 5.0 unterstützt MongoDB spezielle Time Series Collections, um der Relevanz dieser Szenarien Rechnung zu tragen und die Verwaltung dieser Daten zu vereinfachen. Die schiere Menge der Daten, die in IoT-Szenarien anfällt, führt dazu, dass die Ablage jedes Ereignisses als eigenes Dokument zu einer hohen Anzahl an Dokumenten und großen Indexen führt. Daher ist dieser Weg zwar geradlinig, aber meist nicht praxistauglich.
Vor der Verfügbarkeit der Time Series Collections wurde daher häufig auf das Bucket-Pattern gesetzt, bei dem die einzelnen Ereignisse in Buckets segmentiert wurden. Hierbei werden beispielsweise alle Messwerte eines Sensors, die innerhalb einer Stunde aufgetreten sind, in einem Dokument zusammen abgelegt. Das folgende Dokument zeigt ein Beispiel mit lediglich drei Events:
{
address: "SENSOR_ADDRESS",
probeId: "PROBE_123",
startTime: ISODate("2022-08-07T15:00:00Z"),
endTime: ISODate("2022-08-07T15:59:59.999Z"),
count: 3,
total: 7,
measurements: [
{ ts: ISODate("2022-08-07T15:23:47Z"), value: 2 },
{ ts: ISODate("2022-08-07T15:27:47Z"), value: 1 },
{ ts: ISODate("2022-08-07T15:43:47Z"), value: 4 }
]
}
Neben der Reduzierung der Dokumentenanzahl trägt dies dem Umstand Rechnung, dass der einzelne Messwert in der Rückschau oft weniger relevant als beispielsweise der Stundendurchschnitt ist. Neben diesen Vorteilen führt das Bucket-Patterns jedoch andererseits dazu, dass die Struktur nicht unbedingt eingängig ist und diese bei der Entwicklung der Anwendung entsprechend berücksichtigt werden muss.
Eine Time Series Collection setzt das Bucket-Pattern nun dergestalt um, dass das Beste aus beiden Welten erreicht wird: einerseits eine geringe Dokumentenanzahl und damit auch eine kleine Größe der Indexe. Andererseits verwendet man die Collection so, wie wenn die einzelnen Werte jeweils in einem eigenen Dokument abgelegt wären.
Der folgende Vergleich gibt ein ungefähres Gefühl für die Größenverhältnisse:
Speicherform | Anzahl Dokumente | Speicherplatz | Größe der Indexe |
---|---|---|---|
Dokument pro Event | 1.432.952 | 34,66 MB | 32,52 MB |
Bucket pro Stunde | 71.726 | 19,90 MB | 2,02 MB |
Time Series (Standard-Granularität) | 70.554 | 13,93 MB | 2,40 MB |
Time Series (Granularität Minutes) | 3.482 | 12,91 MB | 139,26 kB |
In obiger Tabelle wird als Anzahl der Dokumente für die Time Series Collection die Anzahl der Buckets angegeben - die Anzahl der Einzelevents wäre natürlich 1.432.952 Dokumente. Deutlich zu erkennen ist jedenfalls, dass die Reduzierung der Dokumentenanzahl bei passender Granularität (dazu später mehr) zu einer deutlichen kleineren Speichergröße und zu einer noch deutlicheren Reduzierung der Index-Größe führt.
Dies zeigt, dass es sich auf alle Fälle lohnt, einen Blick auf die Time Series Collections zu werfen und diese für Zeitreihen zu nutzen. Hierbei sollte man jedoch folgendes beachten:
1 - Time Series Collections oder eigenes Bucket-Schema?
Auch wenn es natürlich nahe liegt, die Unterstützung durch MongoDB anzunehmen und die Time Series Collections große Vorteile bieten: insbesondere, wenn man bereits Daten in Form von Buckets hat, sollte man zuerst prüfen, ob diese hinsichtlich des Schemas und der Speicher- und Indexgrößen ein Problem darstellen oder ob derartige Probleme in naher Zukunft zu erwarten sind.
Ist dies nicht der Fall, kann man auch weiterhin mit einem eigenen Bucket-Schema arbeiten und auf die Migration vorerst verzichten. Dadurch hat man größere Freiheiten hinsichtlich der Gesaltung der Buckets und braucht die restliche Anwendung, die ja bereits mit dem Bucket umgehen kann, nicht anzupassen.
Bei der Vorausplanung sollte man jedoch beachten, dass die Migration der Daten mit zunehmender Datenmenge nicht einfacher wird - dazu aber mehr im folgenden Tipp.
2 - Migration zur Time Series Collection planen
Die Dokumentation beschreibt die Vorgehensweise bei der Migration zu einer Time Series Collection; es ist momentan nicht mit einer Aggregation mit $merge
/$out
in eine Time Series Collection getan, sondern erfordert u.U. die Aufbereitung der Daten in einer temporären Collection mit anschließendem mongodump
und mongorestore
. Temporär wird bei großen Ereignisanzahlen also mitunter deutlich mehr Speicherplatz benötigt, um die Migration vorzubereiten. Die entstehende Last sollte man dem Produktiv-Cluster u.U. ersparen. Insgesamt gilt natürlich, Kosten und Nutzen der Migration abzuwägen, bevor man eine Entscheidung trifft.
Wichtig ist auch, zuvor zu prüfen, dass die Daten dem erwarteten Schema wirklich entsprechen. Insbesondere, wenn die Einzelwerte im Bucket versteckt sind und selten genutzt werden, kann es durchaus sein, dass das ein oder andere Datum als Zeichenkette abgelegt war und das bisher nicht aufgefallen war. Beim Import der Daten in die Time Series Collection fällt dies jedoch sofort auf - dann hat man allerdings schon einige Schritte durchgeführt. Dies sollte man also lieber früher prüfen oder sicherheitshalber eine Konvertierung bei der Datenvorbereitung einbauen.
3 - Richtige Granularität wählen
Das obige Beispiel zeigt, dass die Auswahl der passenden Granularität einen großen Einfluss insbesondere auf die Größe der Indexe hat. Im Beispiel senden die Sensoren jeweils etwa alle fünf Minuten neue Daten, so dass die Einstellung minutes
für die Granularität passender ist als die Standardeinstellung seconds
. Die Dokumentation empfiehlt die Granularität zu wählen, die dem Sendeinterval für gleiche Metadaten am nächsten kommt. Dies unterstreicht der Vergleich der beiden Time Series Collections sehr gut (70.554 vs. 3.482 Dokumente).
Die Anpassung der Granularität ist später zumindest in der Richtung von kleiner Granularität zu größerer in Einzelschritten noch möglich. Es kann also beispielsweise von seconds
auf minutes
umgestellt werden, jedoch nicht direkt von seconds
auf hours
.
4 - Sekundär-Indexe auf Metadaten nutzen
Die Daten einer Time Series Collection werden durch einen Clustered Index in zeitlicher Reihenfolge abelegt. Zusätzlich können Indexe auf dem Timestamp und auf den Metadaten-Feldern (also beispielsweise der Sensor-Id) erstellt werden. Insbesondere wenn häufig nach Metadaten ausgewertet wird, können teure Collection Scans dadurch vermieden werden.
Oft handelt es sich beim Meta-Feld um ein Dokument, das mehrere Eigenschaften umfasst. Hier ist es ohne weiteres möglich, diese Eigenschaften zu indexieren. Neben der Unterstützung bei der Einschränkung können die Indexe auch bei der Sortierung der Ergebnisse gute Dienste leisten.
5 - Richtig einschränken
Wie bereits erwähnt, basiert die Auswertung oft nicht auf den Einzelwerten, sondern eher auf der Auswertung kumulierter Ereignisse auf größeren Zeitintervallen. Es bleibt also nicht aus, dass die Daten aus der Time Series Collection aggregiert werden. Besonders aufgrund der hohen Anzahl an Einzelereignissen ist es daher wichtig, in einer Aggregation die Anzahl der Dokumente möglichst früh einzuschränken. MongoDB hat einige Anstrengungen unternommen, um die Queries, die für die Einzelereignisse zusammengestellt werden, auf die Buckets anwenden zu können. Beispielsweise werden beim Bucket der Zeitraum und die zugehörigen Metadaten abgelegt. Darüber hinaus wird auch der Maximal- und Minimalwert sowohl für den Zeitstempel als auch für die Werte hinterlegt. Damit kann oft gleich zu Anfang eine effiziente Einschränkung der Buckets erfolgen, bevor die Einzel-Ereignisse aus diesen extrahiert werden.
Vorsichtig sollte man im Bereich der Views sein, insbesondere wenn es sich nicht um Materialized-Views (also um Collections mit vorbereiteten Daten), sondern um Views handelt, deren Pipeline erst bei der Verwendung ausgeführt wird. Je nach schlussendlicher Struktur am Ende der Pipeline hat MongoDB dann nämlich oft keine Chance, aus den Einschränkungen eine passende Vorauswahl der Buckets zu treffen, so dass der gesamte Inhalt der Time Series Collection verwendet wird. Das funktioniert selbst bei überschaubaren Event-Anzahlen wie in unserem Beispiel nur mit einer spürbaren Verzögerung und wird mit steigender Event-Anzahl sicherlich nicht zu einer guten Performance führen.
Fazit
Die Einfachheit in der Verwendung und vor allem der geringe Speicherplatzbedarf bei der Verwendung von Time Series Collections sind wirklich beeindruckend. Besonders Szenarien, in denen man viel mit den einzelnen Ereignissen arbeitet, werden durch Time Series Collections hervorragend unterstützt. Aufgrund der notwendigen Schritte bei der Migration sollte man jedoch prüfen, ob und wann diese durchgeführt werden soll.
Insbesondere bei der Abfrage der Daten muss man natürlich im Hinterkopf behalten, dass sich sehr schnell eine große Menge an Dokumenten ansammeln kann. Queries und Indexe sollten also sorgfältig geplant werden. Zur Optimierung von Leseoperationen auf aggregierte Daten können Materialized Views genutzt werden, um die notwendigen Daten passend vorzubereiten.
Bonus: ein Bucket-Dokument anzeigen
Die Buckets werden in einer System-Collection gespeichert, z.B. system.buckets.probe_measurements_ts
, wenn die zugehörige Time Series Collection “probe_measurements_ts” heißt. Von Schreiboperationen in diese Collection ist natürlich unbedingt abzuraten; zum Verständnis ist es aber doch interessant, sich einmal einen Bucket und dessen Struktur anzusehen. Dies kann mit folgender Query erreicht werden:
db.getCollection("system.buckets.probe_measurements_ts").findOne()