
Die Zeitreihendatenbank (TSDB, time series database) in Prometheus 2 ist ein hervorragendes Beispiel für eine technische Lösung, die signifikante Verbesserungen im Vergleich zum Speicher v2 in Prometheus 1 hinsichtlich der Datensammlungsgeschwindigkeit und der Abfrageausführung sowie der Ressourcennutzung bietet. Wir haben Prometheus 2 in Percona Monitoring and Management (PMM) implementiert, und ich hatte die Gelegenheit, die Leistung der Prometheus 2 TSDB zu untersuchen. In diesem Artikel werde ich die Ergebnisse dieser Beobachtungen vorstellen.
Durchschnittliche Arbeitslast von Prometheus
Für diejenigen, die es gewohnt sind, mit allgemeinen Datenbanken zu arbeiten, ist die typische Arbeitslast von Prometheus ziemlich interessant. Die Datenakkumulationsgeschwindigkeit tendiert zu einem stabilen Wert: Die Dienste, die Sie überwachen, senden normalerweise eine ähnliche Anzahl von Metriken, und die Infrastruktur verändert sich relativ langsam.
Informationsanfragen können aus verschiedenen Quellen kommen. Einige von ihnen, wie Alarme, streben ebenfalls einen stabilen und vorhersehbaren Wert an. Andere, wie benutzerdefinierte Anfragen, können Spitzen verursachen, obwohl dies nicht charakteristisch für den Großteil der Last ist.
Lasttest
Im Rahmen der Tests konzentrierte ich mich auf die Fähigkeit zur Datensammlung. Ich habe Prometheus 2.3.2, kompiliert mit Go 1.10.1 (als Teil von PMM 1.14), auf einem Linode-Dienst bereitgestellt mit diesem Skript: . Um die Lasterzeugung so realistisch wie möglich zu gestalten, habe ich mit diesem mehrere MySQL-Knoten mit realer Last (Sysbench TPC-C Test) gestartet, von denen jeder 10 Linux/MySQL-Knoten emulierte.
Alle folgenden Tests wurden auf einem Linode-Server mit acht virtuellen Kernen und 32 GB RAM durchgeführt, auf dem 20 Lastsimulationen zur Überwachung von zweihundert MySQL-Instanzen laufen. Oder, in den Begriffen von Prometheus, 800 Targets, 440 Scrapes pro Sekunde, 380.000 Samples pro Sekunde und 1,7 Millionen aktive Zeitreihen.
Design
Der übliche Ansatz traditioneller Datenbanken, einschließlich des von Prometheus 1.x verwendeten, besteht darin, . Wenn dieser unzureichend ist, um die Last zu bewältigen, werden Sie mit hohen Latenzen konfrontiert sein, und einige Anfragen werden nicht bearbeitet. Der Speicherverbrauch in Prometheus 2 wird über den Schlüssel storage.tsdb.min-block-duration konfiguriert., der festlegt, wie lange Aufzeichnungen im Speicher gehalten werden, bevor sie auf die Festplatte geschrieben werden (standardmäßig 2 Stunden). Der erforderliche Speicher hängt von der Anzahl der Zeitreihen, Labels und der Häufigkeit der Datenerfassung in Verbindung mit dem reinen eingehenden Datenstrom ab. Bezüglich des Speicherplatzes strebt Prometheus an, 3 Byte pro Aufzeichnung (Sample) zu verwenden. Auf der anderen Seite sind die Anforderungen an den Speicher jedoch viel höher.
Obwohl es möglich ist, die Blockgröße anzupassen, wird nicht empfohlen, dies manuell zu tun. Daher sind Sie gezwungen, Prometheus so viel Speicher zur Verfügung zu stellen, wie es für Ihre Last benötigt.
Wenn nicht genügend Speicher vorhanden ist, um den eingehenden Strom von Metriken zu unterstützen, wird Prometheus aufgrund von unzureichendem Speicher (out of memory) oder durch den OOM-Killer abstürzen.
Swap hinzuzufügen, um den Absturzzeitpunkt hinauszuzögern, wenn Prometheus keinen Speicher mehr hat, hilft nicht wirklich, denn die Nutzung dieser Funktion führt zu einem explosiven Speicherverbrauch. Ich denke, es liegt am Go, seinem Garbage Collector und daran, wie er mit Swap umgeht.
Ein weiterer interessanter Ansatz besteht darin, die Rückführung des Head-Blocks zu einem bestimmten Zeitpunkt auf die Festplatte einzustellen, anstatt sie von der Zeit des Prozessstarts zu berechnen.

Wie Sie aus dem Diagramm sehen können, erfolgen die Rückführungen auf die Festplatte alle zwei Stunden. Wenn Sie den Parameter min-block-duration auf eine Stunde ändern, werden diese Rückführungen jede Stunde stattfinden, beginnend in einer halben Stunde.
Wenn Sie dieses und andere Diagramme in Ihrer Prometheus-Installation verwenden möchten, können Sie dieses . Es wurde für PMM entwickelt, ist aber mit einigen Änderungen für jede Prometheus-Installation geeignet.
Wir haben einen aktiven Block, der als Head-Block bezeichnet wird und im Speicher gehalten wird; Blöcke mit älteren Daten sind über mmap(). Dies beseitigt die Notwendigkeit, den Cache separat zu konfigurieren, bedeutet jedoch auch, dass Sie genügend Platz für den Betriebssystem-Cache lassen müssen, wenn Sie Abfragen zu Daten machen möchten, die älter sind als die, die im Head-Block enthalten sind.
Das bedeutet auch, dass der Verbrauch von virtueller Speicher durch Prometheus ziemlich hoch aussehen wird, über den man sich keine Sorgen machen muss.

Ein weiterer interessanter Aspekt des Designs ist die Verwendung von WAL (Write Ahead Log). Wie aus der Dokumentation zum Speichersystem hervorgeht, verwendet Prometheus WAL, um Datenverluste bei Abstürzen zu vermeiden. Die spezifischen Mechanismen zur Gewährleistung der Datenbeständigkeit sind leider nicht ausreichend dokumentiert. Die Version Prometheus 2.3.2 schreibt WAL alle 10 Sekunden auf die Festplatte, und dieser Parameter ist nicht vom Benutzer konfigurierbar.
Kompaktionen
Das Prometheus TSDB ist nach dem LSM-Speicher (Log Structured Merge – log-strukturiertes Zusammenführen) konzipiert: Der Head Block wird regelmäßig auf die Festplatte geschrieben, während der Kompaktionsmechanismus mehrere Blöcke zusammenführt, um bei Abfragen nicht zu viele Blöcke scannen zu müssen. Hier ist die Anzahl der Blöcke zu sehen, die ich auf dem Testsystem nach einem Tag Belastung beobachtet habe.

Wenn Sie mehr über den Speicher erfahren möchten, können Sie die Datei meta.json einsehen, die Informationen über die vorhandenen Blöcke und deren Entstehung enthält.
{
"ulid": "01CPZDPD1D9R019JS87TPV5MPE",
"minTime": 1536472800000,
"maxTime": 1536494400000,
"stats": {
"numSamples": 8292128378,
"numSeries": 1673622,
"numChunks": 69528220
},
"compaction": {
"level": 2,
"sources": [
"01CPYRY9MS465Y5ETM3SXFBV7X",
"01CPYZT0WRJ1JB1P0DP80VY5KJ",
"01CPZ6NR4Q3PDP3E57HEH760XS"
],
"parents": [
{
"ulid": "01CPYRY9MS465Y5ETM3SXFBV7X",
"minTime": 1536472800000,
"maxTime": 1536480000000
},
{
"ulid": "01CPYZT0WRJ1JB1P0DP80VY5KJ",
"minTime": 1536480000000,
"maxTime": 1536487200000
},
{
"ulid": "01CPZ6NR4Q3PDP3E57HEH760XS",
"minTime": 1536487200000,
"maxTime": 1536494400000
}
]
},
"version": 1
}Kompaktionen in Prometheus sind an den Zeitpunkt gebunden, zu dem der Head Block auf die Festplatte geschrieben wird. Zu diesem Zeitpunkt können mehrere solcher Operationen durchgeführt werden.

Offenbar gibt es keine Einschränkungen bei der Komprimierung, und sie kann während des Betriebs zu erheblichen Schwankungen beim Festplatten-I/O führen.

CPU-Auslastungsspitzen

Das hat natürlich einen ziemlich negativen Einfluss auf die Systemgeschwindigkeit und stellt eine ernsthafte Herausforderung für LSM-Speicher dar: Wie kann man die Komprimierung so gestalten, dass eine hohe Anfragegeschwindigkeit unterstützt wird, ohne dabei zu viel Overhead zu verursachen?
Der Speicherverbrauch während der Komprimierung sieht ebenfalls ziemlich interessant aus.

Wir können beobachten, dass nach der Komprimierung der Großteil des Speichers seinen Zustand von Cached auf Free ändert: das bedeutet, dass potenziell wertvolle Informationen daraus entfernt wurden. Interessant ist, ob hier fadvice() oder eine andere Minimierungstechnik verwendet wird, oder ob dies darauf zurückzuführen ist, dass der Cache von Blöcken befreit wurde, die während der Komprimierung gelöscht wurden?
Wiederherstellung nach einem Ausfall
Die Wiederherstellung nach Ausfällen benötigt Zeit, und das ist nachvollziehbar. Für einen eingehenden Datenstrom von eine Million Datensätzen pro Sekunde musste ich etwa 25 Minuten warten, während die Wiederherstellung unter Berücksichtigung der SSD durchgeführt wurde.
level=info ts=2018-09-13T13:38:14.09650965Z caller=main.go:222 msg="Starte Prometheus" version="(version=2.3.2, branch=v2.3.2, revision=71af5e29e815795e9dd14742ee7725682fa14b7b)"
level=info ts=2018-09-13T13:38:14.096599879Z caller=main.go:223 build_context="(go=go1.10.1, user=Jenkins, date=20180725-08:58:13OURCE)"
level=info ts=2018-09-13T13:38:14.096624109Z caller=main.go:224 host_details="(Linux 4.15.0-32-generic #35-Ubuntu SMP Fri Aug 10 17:58:07 UTC 2018 x86_64 1bee9e9b78cf (none))"
level=info ts=2018-09-13T13:38:14.096641396Z caller=main.go:225 fd_limits="(soft=1048576, hard=1048576)"
level=info ts=2018-09-13T13:38:14.097715256Z caller=web.go:415 component=web msg="Beginne mit dem Hören auf Verbindungen" address=:9090
level=info ts=2018-09-13T13:38:14.097400393Z caller=main.go:533 msg="Starte TSDB ..."
level=info ts=2018-09-13T13:38:14.098718401Z caller=repair.go:39 component=tsdb msg="Gesunder Block gefunden" mint=1536530400000 maxt=1536537600000 ulid=01CQ0FW3ME8Q5W2AN5F9CB7R0R
level=info ts=2018-09-13T13:38:14.100315658Z caller=web.go:467 component=web msg="Router-Präfix" prefix=\/prometheus
level=info ts=2018-09-13T13:38:14.101793727Z caller=repair.go:39 component=tsdb msg="Gesunder Block gefunden" mint=1536732000000 maxt=1536753600000 ulid=01CQ78486TNX5QZTBF049PQHSM
level=info ts=2018-09-13T13:38:14.102267346Z caller=repair.go:39 component=tsdb msg="Gesunder Block gefunden" mint=1536537600000 maxt=1536732000000 ulid=01CQ78DE7HSQK0C0F5AZ46YGF0
level=info ts=2018-09-13T13:38:14.102660295Z caller=repair.go:39 component=tsdb msg="Gesunder Block gefunden" mint=1536775200000 maxt=1536782400000 ulid=01CQ7SAT4RM21Y0PT5GNSS146Q
level=info ts=2018-09-13T13:38:14.103075885Z caller=repair.go:39 component=tsdb msg="Gesunder Block gefunden" mint=1536753600000 maxt=1536775200000 ulid=01CQ7SV8WJ3C2W5S3RTAHC2GHB
level=error ts=2018-09-13T14:05:18.208469169Z caller=wal.go:275 component=tsdb msg="WAL-Korruption festgestellt; truncating" err="unerwartete CRC32-Prüfziffer d0465484, möchte 0" file=\/opt\/prometheus\/data\/.prom2-data\/wal\/007357 pos=15504363
level=info ts=2018-09-13T14:05:19.471459777Z caller=main.go:543 msg="TSDB gestartet"
level=info ts=2018-09-13T14:05:19.471604598Z caller=main.go:603 msg="Lade Konfigurationsdatei" filename=\/etc\/prometheus.yml
level=info ts=2018-09-13T14:05:19.499156711Z caller=main.go:629 msg="Laden der Konfigurationsdatei abgeschlossen" filename=\/etc\/prometheus.yml
level=info ts=2018-09-13T14:05:19.499228186Z caller=main.go:502 msg="Der Server ist bereit, Webanfragen zu empfangen."Das Hauptproblem im Wiederherstellungsprozess ist der hohe Speicherverbrauch. Obwohl der Server unter normalen Umständen mit dem gleichen Arbeitsspeicher stabil arbeiten kann, kann er beim Absturz wegen OOM möglicherweise nicht neu gestartet werden. Die einzige Lösung, die ich gefunden habe, besteht darin, die Datensammlung auszuschalten, den Server hochzufahren, ihm zu erlauben, sich zu erholen und dann erneut mit eingeschalteter Datensammlung neu zu starten.
Aufwärmen
Ein weiteres Verhalten, das während des Aufwärmens zu beachten ist, ist das Verhältnis von geringer Leistung zu hohem Ressourcenverbrauch direkt nach dem Start. Bei einigen, aber nicht bei allen Starts habe ich eine erhebliche CPU- und Speicherauslastung beobachtet.


Ein Rückgang der Speichernutzung deutet darauf hin, dass Prometheus beim Start nicht alle Sammlungen konfigurieren kann und einige Informationen verloren gehen.
Ich habe die genauen Ursachen für die hohe CPU- und Speicherauslastung nicht ermittelt. Ich vermute, dass dies mit der Erstellung neuer Zeitreihen im Head-Block mit hoher Frequenz zusammenhängt.
Schwankungen der CPU-Auslastung
Neben den Komprimierungen, die eine ziemlich hohe I/O-Belastung verursachen, habe ich ernsthafte Ausbrüche der CPU-Auslastung alle zwei Minuten bemerkt. Die Spitzen sind länger bei hohem eingehendem Traffic, und es scheint, dass sie durch den Garbage Collector von Go verursacht werden, da mindestens einige Kerne vollständig ausgelastet sind.


Diese Ausbrüche sind nicht unwesentlich. Wenn sie auftreten, scheint der interne Einstiegspunkt und die Metriken von Prometheus nicht verfügbar zu sein, was zu Datenlücken in denselben Zeiträumen führt.

Es kann auch beobachtet werden, dass der Prometheus-Exporter für eine Sekunde stehen bleibt.

Wir können Korrelationen mit der Müllabfuhr (GC) feststellen.

Fazit
Die TSDB in Prometheus 2 funktioniert schnell, kann Millionen von Zeitreihen verarbeiten und gleichzeitig Tausende von Schreibvorgängen pro Sekunde bewältigen, wobei sie recht bescheidenen Hardwarebedarf hat. Die Auslastung von CPU und Disk I/O ist ebenfalls beeindruckend. Mein Beispiel zeigte bis zu 200.000 Metriken pro Sekunde auf einem genutzten Kern.
Bei der Planung der Erweiterung ist es wichtig, an ausreichend Speicher zu denken, und dies muss echter Speicher sein. Die von mir beobachtete Speicherausnutzung lag bei etwa 5 GB bei 100.000 Schreibvorgängen pro Sekunde des eingehenden Traffics, was zusammen mit dem Cache des Betriebssystems etwa 8 GB belegten Speicher ergibt.
Natürlich gibt es noch viel zu tun, um die Ausbrüche von CPU und Disk I/O zu zähmen, und das ist nicht überraschend, wenn man bedenkt, wie jung die TSDB Prometheus 2 im Vergleich zu InnoDB, TokuDB, RocksDB, WiredTiger ist, aber alle hatten zu Beginn ihres Lebenszyklus ähnliche Probleme.
Quelle: habr.com
