
Die Zeitseriendatenbank (TSDB) in Prometheus 2 ist ein hervorragendes Beispiel für eine Ingenieurlösung, die signifikante Verbesserungen gegenüber dem Speichermodul v2 in Prometheus 1 hinsichtlich der Geschwindigkeit der Datenspeicherung und Anfrageverarbeitung 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 analysieren. In diesem Artikel werde ich die Ergebnisse dieser Beobachtungen vorstellen.
Durchschnittliche Arbeitslast von Prometheus
Für diejenigen, die an die Arbeit mit relationalen Datenbanken gewöhnt sind, ist die typische Arbeitslast von Prometheus recht interessant. Die Geschwindigkeit der Datenspeicherung tendiert zu einem stabilen Wert: In der Regel senden die Dienste, die Sie überwachen, eine ähnliche Anzahl von Metriken, und die Infrastruktur ändert sich relativ langsam.
Anfragen nach Informationen können aus verschiedenen Quellen kommen. Einige davon, wie Alarme, neigen ebenfalls zu stabilen und vorhersehbaren Werten. Andere, wie Benutzeranfragen, können zu Spitzen führen, obwohl dies nicht für den Großteil der Last charakteristisch ist.
Lasttest
Während der Tests konzentrierte ich mich auf die Fähigkeit, Daten zu sammeln. Ich habe Prometheus 2.3.2, kompiliert mit Go 1.10.1 (als Teil von PMM 1.14), auf dem Linode-Dienst mit diesem Skript bereitgestellt: . Um eine möglichst realistische Last zu erzeugen, habe ich mithilfe davon mehrere MySQL-Knoten mit echter Last (Sysbench TPC-C-Test) gestartet, wobei jeder Knoten 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. In Prometheus-Begriffen bedeutet das 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 genutzten, besteht in . Wenn dies nicht ausreicht, um die Last zu bewältigen, werden Sie auf große Verzögerungen stoßen, und einige Anfragen werden nicht bearbeitet. Der Speicherverbrauch in Prometheus 2 wird über den Schlüssel storage.tsdb.min-block-duration, der bestimmt, wie lange die Aufzeichnungen im Speicher gehalten werden, bevor sie auf die Festplatte geschrieben werden (standardmäßig sind das 2 Stunden). Der benötigte Speicher hängt von der Anzahl der Zeitreihen, Labels und der Intensität der Datensammlung (Scrapes) zusammen mit dem reinen eingehenden Datenstrom ab. Im Hinblick auf den Speicherplatz strebt Prometheus an, 3 Byte pro Aufzeichnung (Sample) zu verwenden. Andererseits sind die Anforderungen an den Speicher deutlich höher.
Obwohl die Möglichkeit besteht, die Blockgröße zu konfigurieren, wird nicht empfohlen, dies manuell zu tun. Daher müssen Sie Prometheus so viel Speicher zur Verfügung stellen, wie es für Ihre Last benötigt.
Wenn nicht genügend Speicher vorhanden ist, um den eingehenden Metrikstrom aufrechtzuerhalten, wird Prometheus aufgrund von Speichermangel oder durch den OOM-Killer (Out of Memory Killer) abstürzen.
Swap hinzuzufügen, um den Absturzmoment hinauszuzögern, wenn Prometheus den Speicher erschöpft, hilft nicht besonders, da die Verwendung dieser Funktion zu einem explosiven Speicherverbrauch führt. Ich denke, das liegt an Go, seinem Garbage Collector und wie er mit Swap umgeht.
Ein weiterer interessanter Ansatz besteht darin, den Head-Block zu einem bestimmten Zeitpunkt auf die Festplatte zurückzusetzen, anstatt ihn seit dem Start des Prozesses zu zählen.

Wie Sie aus dem Diagramm ersehen können, erfolgen die Festplattenspeicherungen alle zwei Stunden. Wenn Sie den Parameter min-block-duration auf eine Stunde ändern, finden diese Speicherungen jede Stunde statt, beginnend nach 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, passt jedoch mit einigen Anpassungen auf jede Prometheus-Installation.
Wir haben einen aktiven Block, der als Head-Block bezeichnet wird, der im Speicher gespeichert wird; ältere Datenblöcke 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 Daten abfragen möchten, die älter sind als die, die der Head-Block aufnehmen kann.
Das bedeutet auch, dass der virtuelle Speicherverbrauch von Prometheus ziemlich hoch erscheinen wird, worüber man sich keine Sorgen machen sollte.

Ein weiteres interessantes Designelement ist die Verwendung von WAL (Write Ahead Log). Wie aus der Dokumentation zum Speicher hervorgeht, nutzt Prometheus WAL, um Datenverluste bei Abstürzen zu vermeiden. Die spezifischen Mechanismen zur Gewährleistung der Datenhaltbarkeit sind leider nicht ausreichend dokumentiert. In der Version 2.3.2 von Prometheus wird WAL alle 10 Sekunden auf die Festplatte geschrieben, und diese Einstellung ist für den Benutzer nicht konfigurierbar.
Kompaktionen
Die Prometheus TSDB ist nach dem Vorbild eines LSM-Speichers (Log Structured Merge) entworfen: Der Head-Block wird periodisch auf die Festplatte geschrieben, während der Mechanismus der Kompaktierung mehrere Blöcke zusammenführt, um das Scannen einer zu großen Anzahl von Blöcken bei Abfragen zu vermeiden. Hier sieht man die Anzahl der Blöcke, 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 untersuchen, 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
}In Prometheus sind die Komprimierungen an den Zeitpunkt des Schreibens des Head-Blocks auf die Disk gebunden. Zu diesem Zeitpunkt können mehrere solcher Operationen durchgeführt werden.

Offensichtlich sind die Komprimierungen nicht begrenzt und können erhebliche Sprünge im Festplatten-I/O während ihrer Ausführung verursachen.

CPU-Auslastungsspitzen

Das wirkt sich natürlich negativ auf die Systemgeschwindigkeit aus und stellt eine ernsthafte Herausforderung für LSM-Speicher dar: Wie kann man Komprimierungen zur Unterstützung einer hohen Anfragelast durchführen, ohne dabei zu viel Overhead zu erzeugen?
Der Speicherverbrauch während der Komprimierungen sieht ebenfalls recht interessant aus.

Wir können sehen, dass nach der Komprimierung der Großteil des Speichers von Cached auf Free wechselt: 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 bei der Komprimierung gelöscht wurden.
Wiederherstellung nach einem Ausfall
Die Wiederherstellung nach einem Ausfall benötigt Zeit, und das ist nachvollziehbar. Bei einem Eingangsfluss von einer Million Datensätzen pro Sekunde musste ich etwa 25 Minuten warten, bis die Wiederherstellung unter Berücksichtigung der SSD-Festplatte abgeschlossen war.
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 Lauschen 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="Gesunden 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="Gesunden Block gefunden" mint=1536732000000 maxt=1536753600000 ulid=01CQ78486TNX5QZTBF049PQHSM
level=info ts=2018-09-13T13:38:14.102267346Z caller=repair.go:39 component=tsdb msg="Gesunden Block gefunden" mint=1536537600000 maxt=1536732000000 ulid=01CQ78DE7HSQK0C0F5AZ46YGF0
level=info ts=2018-09-13T13:38:14.102660295Z caller=repair.go:39 component=tsdb msg="Gesunden Block gefunden" mint=1536775200000 maxt=1536782400000 ulid=01CQ7SAT4RM21Y0PT5GNSS146Q
level=info ts=2018-09-13T13:38:14.103075885Z caller=repair.go:39 component=tsdb msg="Gesunden 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; kürzen" err="unerwarteter CRC32-Checksumme 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="Server ist bereit, Webanfragen zu empfangen."Das Hauptproblem des Wiederherstellungsprozesses ist der hohe Speicherverbrauch. Unter normalen Umständen kann der Server mit dem gleichen Speicher Volumen stabil betrieben werden, aber im Absturzfall kann er aufgrund von OOM nicht hochgefahren werden. Die einzige Lösung, die ich gefunden habe, besteht darin, die Datensammlung zu deaktivieren, den Server hochzufahren, ihm die Möglichkeit zur Wiederherstellung zu geben und dann mit aktivierter Datensammlung neu zu starten.
Aufwärmung
Ein weiteres Verhalten, das bei der Aufwärmung zu beachten ist, ist das Verhältnis von geringer Leistung zu hohem Ressourcenverbrauch direkt nach dem Start. Bei einigen, aber nicht allen Starts habe ich eine erhebliche CPU- und Speicherbelastung beobachtet.


Lücken in der Speichernutzung deuten darauf hin, dass Prometheus es nicht schafft, alle Sammlungen beim Start zu konfigurieren, und einige Informationen verloren gehen.
Ich habe die genauen Ursachen der hohen CPU- und Speicherbelastung nicht herausgefunden. Ich vermute, dass dies mit der Erstellung neuer Zeitreihen im Head-Block bei hoher Frequenz zusammenhängt.
Schwankungen bei der CPU-Belastung
Neben den Lastspitzen, die eine recht hohe I/O-Belastung erzeugen, habe ich alle zwei Minuten erhebliche CPU-Spitzen festgestellt. Diese Spitzen dauern länger bei hohem eingehendem Datenverkehr und scheinen durch den Garbage Collector von Go verursacht zu werden, wobei zumindest einige Kerne vollständig ausgelastet sind.


Diese Spitzen sind keineswegs unwesentlich. Es scheint, dass, wenn sie auftreten, der interne Einstiegspunkt und die Prometheus-Metriken nicht verfügbar sind, was zu Datenlücken in denselben Zeiträumen führt.

Es ist auch zu bemerken, dass der Prometheus-Exporter für eine Sekunde ausfällt.

Wir können Korrelationen mit der Garbage Collection (GC) beobachten.

Fazit
Die TSDB in Prometheus 2 arbeitet schnell, ist in der Lage, Millionen von Zeitreihen zu verwalten und gleichzeitig Tausende von Aufzeichnungen pro Sekunde zu verarbeiten, und das auf recht bescheidenem Equipment. Eine Nutzung von CPU und Festplatten-I/O ist ebenfalls beeindruckend. Mein Beispiel zeigte bis zu 200.000 Metriken pro Sekunde auf einem genutzten Kern.
Bei der Planung von Erweiterungen ist es wichtig, ausreichende Speicherkapazitäten zu berücksichtigen, und dies sollte realer Speicher sein. Der beobachtete Speicherverbrauch lag bei etwa 5 GB pro 100.000 eingehenden Datensätzen pro Sekunde, was zusammen mit dem Cache des Betriebssystems etwa 8 GB belegten Speicher ergab.
Selbstverständlich gibt es noch viel zu tun, um die CPU- und I/O-Spitzen zu zähmen. Dies ist nicht verwunderlich, wenn man bedenkt, wie jung die TSDB Prometheus 2 im Vergleich zu InnoDB, TokuDB, RocksDB und WiredTiger ist, alle hatten zu Beginn ihres Lebenszyklus ähnliche Probleme.
Quelle: habr.com
