{"id":80733,"date":"2020-05-08T13:42:31","date_gmt":"2020-05-08T11:42:31","guid":{"rendered":"https:\/\/prohoster.info\/blog\/administrirovanie\/go-optimizations-in-victoriametrics-aleksandr-valyalkin"},"modified":"2020-05-08T13:42:31","modified_gmt":"2020-05-08T11:42:31","slug":"go-optimizations-in-victoriametrics-aleksandr-valyalkin","status":"publish","type":"post","link":"https:\/\/prohoster.info\/de\/blog\/administrirovanie\/go-optimizations-in-victoriametrics-aleksandr-valyalkin","title":{"rendered":"Go-Optimierungen in VictoriaMetrics. Alexander Valjalkin","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p><strong>Ich lade Sie ein, sich den Bericht von Alexander Valjalkin aus dem Jahr 2019 \"Go-Optimierungen in VictoriaMetrics\" anzusehen.<\/strong><\/p>\n<p><\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/victoriametrics.com\/\">VictoriaMetrics<\/a><\/noindex> \u2013 eine schnelle und skalierbare Datenbank zur Speicherung und Verarbeitung von Daten in Form von Zeitserien (ein Datensatz bildet die Zeit und eine Menge entsprechender Werte zu diesem Zeitpunkt, zum Beispiel durch regelm\u00e4\u00dfige Abfragen des Zustands von Sensoren oder das Sammeln von Metriken).<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Go-Optimierungen in VictoriaMetrics. Alexander Valjalkin\" src=\"\/wp-content\/uploads\/2020\/05\/dd13ec10594aeb138be29ce439cc476a.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<p>Hier ist der Link zu dem Video dieses Vortrags \u2013 <noindex><a rel=\"nofollow\" href=\"https:\/\/youtu.be\/MZ5P21j_HLE\">https:\/\/youtu.be\/MZ5P21j_HLE<\/a><\/noindex><\/p>\n<p><\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.google.com\/presentation\/d\/1k7OjHvxTHA7669MFwsNTCx8hII-a8lNvpmQetLxmrEU\/edit?usp=sharing\">Folien<\/a><\/noindex><\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Go-Optimierungen in VictoriaMetrics. Alexander Valjalkin\" src=\"\/wp-content\/uploads\/2020\/05\/0073390d6dbcc6ab3e5305907ac6a229.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Ich erz\u00e4hle Ihnen ein wenig \u00fcber mich. Ich bin Alexander Valjalkin. Hier ist <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/valyala\">mein GitHub-Konto<\/a><\/noindex>. Ich interessiere mich f\u00fcr Go und Leistungsoptimierung. Ich habe viele n\u00fctzliche und weniger n\u00fctzliche Bibliotheken geschrieben. Sie beginnen entweder mit <code>fast<\/code>, oder mit <code>quick<\/code> als Pr\u00e4fix. <\/p>\n<p><\/p>\n<p>Derzeit arbeite ich an VictoriaMetrics. Was ist das und was mache ich dort? Dar\u00fcber werde ich in dieser Pr\u00e4sentation sprechen. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Go-Optimierungen in VictoriaMetrics. Alexander Valjalkin\" src=\"\/wp-content\/uploads\/2020\/05\/c9194bc3fee1f615d838979bec12f7d0.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Der Plan des Vortrags ist folgender:<\/p>\n<p><\/p>\n<ul>\n<li>Zun\u00e4chst werde ich Ihnen erl\u00e4utern, was VictoriaMetrics ist. <\/li>\n<li>Dann werde ich erkl\u00e4ren, was Zeitserien sind. <\/li>\n<li>Anschlie\u00dfend erkl\u00e4re ich, wie eine Zeitseriendatenbank funktioniert.<\/li>\n<li>Dar\u00fcber hinaus werde ich \u00fcber die Architektur der Datenbank sprechen: woraus sie besteht.<\/li>\n<li>Und dann kommen wir zu den Optimierungen, die in VictoriaMetrics enthalten sind. Dazu geh\u00f6ren die Optimierung des invertierten Index und die Optimierung f\u00fcr die Bitset-Implementierung in Go.<\/li>\n<\/ul>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Go-Optimierungen in VictoriaMetrics. Alexander Valjalkin\" src=\"\/wp-content\/uploads\/2020\/05\/3e0244b6b6fe952f660e4a094778921a.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Wei\u00df jemand in der Audience, was VictoriaMetrics ist? Unglaublich, viele Leute wissen es bereits. Das ist eine gute Nachricht. F\u00fcr diejenigen, die es nicht wissen \u2013 es handelt sich um eine Datenbank f\u00fcr Zeitserien. Sie basiert auf der Architektur von ClickHouse und einigen Implementierungsdetails von ClickHouse. Zum Beispiel auf solchen wie: MergeTree, parallele Berechnungen auf allen verf\u00fcgbaren Prozessorkernen und Leistungsoptimierung durch die Bearbeitung von Datenbl\u00f6cken, die in den Prozessorcache geladen werden. <\/p>\n<p><\/p>\n<p>VictoriaMetrics bietet eine bessere Datenkompression im Vergleich zu anderen Zeitseriendatenbanken. <\/p>\n<p><\/p>\n<p>Sie skaliert vertikal \u2013 d. h. Sie k\u00f6nnen mehr Prozessoren und mehr Arbeitsspeicher auf einem Computer hinzuf\u00fcgen. VictoriaMetrics wird diese verf\u00fcgbaren Ressourcen erfolgreich nutzen und die lineare Leistung steigern.<\/p>\n<p><\/p>\n<p>VictoriaMetrics skaliert auch horizontal \u2013 das hei\u00dft, Sie k\u00f6nnen weitere Knoten in den VictoriaMetrics-Cluster hinzuf\u00fcgen, und ihre Leistung wird fast linear wachsen.<\/p>\n<p><\/p>\n<p>Wie Sie erraten haben, ist VictoriaMetrics eine schnelle Datenbank, denn \u00fcber andere kann ich nicht sprechen. Und sie ist in Go geschrieben, deshalb erz\u00e4hle ich bei diesem Meetup dar\u00fcber.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Go-Optimierungen in VictoriaMetrics. Alexander Valjalkin\" src=\"\/wp-content\/uploads\/2020\/05\/0121c9baead722eb1fe22fb6f900d4ad.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Wer wei\u00df, was eine Zeitreihe ist? Auch viele Menschen wissen das. Eine Zeitreihe ist eine Reihe von Paaren <code>(timestamp, Wert)<\/code>, wobei diese Paare nach Zeit sortiert sind. Der Wert ist eine Flie\u00dfkommazahl \u2013 float64.<\/p>\n<p><\/p>\n<p>Jede Zeitreihe ist eindeutig durch einen Schl\u00fcssel identifiziert. Woraus besteht dieser Schl\u00fcssel? Er besteht aus einer nicht leeren Menge von Schl\u00fcssel-Wert-Paaren. <\/p>\n<p><\/p>\n<p>Hier ist ein Beispiel f\u00fcr eine Zeitreihe. Der Schl\u00fcssel dieser Reihe ist eine Liste von Paaren: <code>__name__=\"cpu_usage\"<\/code> \u2013 das ist der Name der Metrik, <code>instance=\"my-server\"<\/code> \u2013 das ist der Computer, auf dem diese Metrik gesammelt wurde, <code>datacenter=\"us-east\"<\/code> \u2013 das ist das Rechenzentrum, in dem dieser Computer steht.<\/p>\n<p><\/p>\n<p>Wir haben den Namen der Zeitreihe erhalten, der aus drei Schl\u00fcssel-Wert-Paaren besteht. Zu diesem Schl\u00fcssel geh\u00f6rt eine Liste von Paaren <code>(timestamp, value)<\/code>. <code>t1, t3, t3, ..., tN<\/code> \u2013 das sind die Zeitstempel, <code>10, 20, 12, ..., 15<\/code> \u2013 die entsprechenden Werte. Das ist die CPU-Nutzung zu diesem Zeitpunkt f\u00fcr diese Reihe.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Go-Optimierungen in VictoriaMetrics. Alexander Valjalkin\" src=\"\/wp-content\/uploads\/2020\/05\/76cab9db0263318d355ac607ceb7e0f6.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Wo k\u00f6nnen Zeitreihen verwendet werden? Hat jemand Ideen? <\/p>\n<p><\/p>\n<ul>\n<li>In DevOps k\u00f6nnen wir die Auslastung von CPU, RAM, Netzwerk, RPS, die Anzahl der Fehler usw. messen. <\/li>\n<li>IoT \u2013 wir k\u00f6nnen Temperatur, Druck, geografische Koordinaten und noch mehr messen.<\/li>\n<li>Auch in der Finanzwelt \u2013 wir k\u00f6nnen die Preise von Aktien und W\u00e4hrungen \u00fcberwachen. <\/li>\n<li>Dar\u00fcber hinaus k\u00f6nnen Zeitreihen zur \u00dcberwachung von Produktionsprozessen in Fabriken verwendet werden. Wir haben Benutzer, die VictoriaMetrics zur \u00dcberwachung von Windturbinen und Robotern nutzen.<\/li>\n<li>Zeitreihen sind auch n\u00fctzlich, um Informationen von Sensoren verschiedener Ger\u00e4te zu sammeln. Zum Beispiel f\u00fcr Motoren; zur Messung des Reifendrucks; zur Messung von Geschwindigkeit, Entfernung; zur Messung des Benzinverbrauchs usw.<\/li>\n<li>Zeitreihen k\u00f6nnen auch zur \u00dcberwachung von Flugzeugen verwendet werden. Jedes Flugzeug hat eine Blackbox, die Zeitreihen zu verschiedenen Gesundheitsparametern des Flugzeugs sammelt. Zeitreihen werden auch in der Luft- und Raumfahrtindustrie verwendet. <\/li>\n<li>Gesundheitswesen \u2013 das ist Blutdruck, Puls usw.<\/li>\n<\/ul>\n<p><\/p>\n<p>Vielleicht gibt es noch weitere Anwendungen, die ich vergessen habe, aber ich hoffe, dass Sie verstanden haben, dass Zeitreihen in der modernen Welt aktiv genutzt werden. Und ihr Volumen w\u00e4chst von Jahr zu Jahr.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Go-Optimierungen in VictoriaMetrics. Alexander Valjalkin\" src=\"\/wp-content\/uploads\/2020\/05\/f1da29d372163751268b6a8f86836d04.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Warum ist eine Datenbank f\u00fcr Zeitreihen notwendig? Warum kann man keine herk\u00f6mmliche relationale Datenbank zur Speicherung von Zeitreihen verwenden?<\/p>\n<p><\/p>\n<p>Weil Zeitreihen in der Regel ein gro\u00dfes Datenvolumen aufweisen, das sich in herk\u00f6mmlichen Datenbanken schwer speichern und verarbeiten l\u00e4sst. Daher sind spezialisierte Datenbanken f\u00fcr Zeitreihen entstanden. Diese Datenbanken speichern Zeitpunkte effizient <code>(timestamp, value)<\/code> mit einem bestimmten Schl\u00fcssel. Sie bieten eine API zum Lesen der gespeicherten Daten nach Schl\u00fcssel, entweder nach einem Schl\u00fcssel-Wert-Paar, mehreren solchen Paaren oder nach regexp. Wenn Sie beispielsweise die CPU-Auslastung all Ihrer Dienste im Rechenzentrum in Amerika finden m\u00f6chten, m\u00fcssen Sie eine solche Pseudofrage verwenden.<\/p>\n<p><\/p>\n<p>Normalerweise bieten Datenbanken f\u00fcr Zeitreihen spezialisierte Abfragesprachen an, da SQL f\u00fcr Zeitreihen nicht sehr gut geeignet ist. Obwohl es Datenbanken gibt, die SQL unterst\u00fctzen, ist es nicht ideal. Besser geeignete Abfragesprachen sind <noindex><a rel=\"nofollow\" href=\"https:\/\/medium.com\/@valyala\/promql-tutorial-for-beginners-9ab455142085\">PromQL<\/a><\/noindex>, <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.influxdata.com\/influxdb\/v1.8\/query_language\/spec\/\">InfluxQL<\/a><\/noindex>, <noindex><a rel=\"nofollow\" href=\"https:\/\/www.influxdata.com\/products\/flux\/\">Flux<\/a><\/noindex>, <noindex><a rel=\"nofollow\" href=\"https:\/\/code.kx.com\/q\/\">Q<\/a><\/noindex>. Ich hoffe, dass zumindest jemand von diesen Sprachen geh\u00f6rt hat. Von PromQL haben wahrscheinlich viele geh\u00f6rt. Das ist die Abfragesprache von Prometheus.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Go-Optimierungen in VictoriaMetrics. Alexander Valjalkin\" src=\"\/wp-content\/uploads\/2020\/05\/59e5b6d82a2f7861077bc5fe34519ee4.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>So sieht die Architektur einer modernen Datenbank f\u00fcr Zeitreihen am Beispiel von VictoriaMetrics aus.<\/p>\n<p><\/p>\n<p>Sie besteht aus zwei Teilen. Es gibt einen Speicher f\u00fcr den invertierten Index und einen Speicher f\u00fcr die Werte der Zeitreihen. Diese Speicher sind getrennt. <\/p>\n<p><\/p>\n<p>Wenn ein neuer Datensatz in die Datenbank kommt, gehen wir zuerst auf den invertierten Index zu, um die ID der Zeitreihe anhand einer gegebenen Menge zu finden <code>label=value<\/code> f\u00fcr diese Metrik. Wir finden diese ID und speichern den Wert im Datenspeicher.<\/p>\n<p><\/p>\n<p>Wenn eine Abfrage zur Datenabfrage aus TSDB kommt, greifen wir zuerst auf den invertierten Index zu. Wir holen alle <code>timeseries_ids<\/code> Datens\u00e4tze, die zu einer bestimmten Menge passen <code>label=value<\/code>. Und dann holen wir alle ben\u00f6tigten Daten aus dem im Datenspeicher indizierten Bereich <code>timeseries_ids<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Go-Optimierungen in VictoriaMetrics. Alexander Valjalkin\" src=\"\/wp-content\/uploads\/2020\/05\/eb9b36fa3c6de2188b97b56ad3839b2c.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Lassen Sie uns ein Beispiel betrachten, wie eine Datenbank f\u00fcr Zeitreihen eine eingehende Select-Anfrage verarbeitet.<\/p>\n<p><\/p>\n<ul>\n<li>Zun\u00e4chst holt sie alle <code>timeseries_ids<\/code> aus dem invertierten Index, die die angegebenen Paare enthalten <code>label=value<\/code>, oder die dem gegebenen regul\u00e4ren Ausdruck entsprechen.<\/li>\n<li>Dann holt sie alle Datenpunkte aus dem Datenspeicher f\u00fcr den angegebenen Zeitraum f\u00fcr die gefundenen <code>timeseries_ids<\/code>.<\/li>\n<li>Nach diesem f\u00fchrt die Datenbank einige Berechnungen mit diesen Datenpunkten durch, entsprechend der Benutzeranfrage. Und danach wird die Antwort zur\u00fcckgegeben.<\/li>\n<\/ul>\n<p><\/p>\n<p>In dieser Pr\u00e4sentation werde ich Ihnen \u00fcber den ersten Teil erz\u00e4hlen. Das ist die Suche <code>timeseries_ids<\/code> im umgekehrten Index. Den zweiten und dritten Teil k\u00f6nnen Sie sp\u00e4ter ansehen <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/VictoriaMetrics\/VictoriaMetrics\">Quellen von VictoriaMetrics<\/a><\/noindex>, oder warten, bis ich andere Vortr\u00e4ge vorbereitet habe \ud83d\ude42<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Go-Optimierungen in VictoriaMetrics. Alexander Valjalkin\" src=\"\/wp-content\/uploads\/2020\/05\/3a7dab707dd1defb9bc674a342052a03.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Lassen Sie uns mit dem umgekehrten Index beginnen. Viele werden denken, dass das einfach ist. Wer wei\u00df, was ein umgekehrter Index ist und wie er funktioniert? Oh, es sind schon nicht mehr so viele Leute. Lassen Sie uns versuchen zu verstehen, was das ist. <\/p>\n<p><\/p>\n<p>Tats\u00e4chlich ist alles ganz einfach. Es ist einfach ein W\u00f6rterbuch, das einen Schl\u00fcssel auf einen Wert abbildet. Was ist ein Schl\u00fcssel? Dieses Paar <code>label=value<\/code><header> <code>label<\/code> und <code>value<\/code> \u2014 das sind Zeichenfolgen. Und die Werte sind eine Menge <code>timeseries_ids<\/code>, die das gegebene Paar enth\u00e4lt <code>label=value<\/code>.<\/p>\n<p><\/p>\n<p>Der umgekehrte Index erm\u00f6glicht es, schnell alle <code>timeseries_ids<\/code>, die die gegebenen <code>label=value<\/code>.<\/p>\n<p><\/p>\n<p>enth\u00e4lt. Au\u00dferdem erm\u00f6glicht er es, schnell die <code>timeseries_ids<\/code> Zeitreihen f\u00fcr mehrere Paare <code>label=value<\/code>, oder f\u00fcr Paare <code>label=regexp<\/code>. Wie geschieht das? Durch das Finden der Schnittmenge mehrerer <code>timeseries_ids<\/code> f\u00fcr jedes Paar <code>label=value<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Go-Optimierungen in VictoriaMetrics. Alexander Valjalkin\" src=\"\/wp-content\/uploads\/2020\/05\/837a15a078e8c9422c721f3f072c0c09.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Betrachten wir verschiedene Implementierungen des umgekehrten Index. Fangen wir mit der einfachsten naiven Implementierung an. Sie sieht so aus. <\/p>\n<p><\/p>\n<p>Funktion <code>getMetricIDs<\/code> erh\u00e4lt eine Liste von Zeichenfolgen. Jede Zeichenfolge enth\u00e4lt <code>label=value<\/code>. Diese Funktion gibt eine Liste zur\u00fcck <code>metricIDs<\/code>.<\/p>\n<p><\/p>\n<p>Wie funktioniert das? Hier haben wir eine globale Variable, die hei\u00dft <code>invertedIndex<\/code>. Das ist ein gew\u00f6hnliches W\u00f6rterbuch (<code>map<\/code>), das eine Zeichenfolge auf ein Slice von Ints abbildet. Die Zeichenfolge enth\u00e4lt <code>label=value<\/code>.<\/p>\n<p><\/p>\n<p>Implementierung der Funktion: wir holen <code>metricIDs<\/code> f\u00fcr den ersten <code>label=value<\/code>, dann gehen wir alle anderen durch <code>label=value<\/code>, holen <code>metricIDs<\/code> f\u00fcr sie. Und wir rufen die Funktion <code>intersectInts<\/code>, \u00fcber die sp\u00e4ter gesprochen wird. Und diese Funktion gibt die Schnittmenge dieser Listen zur\u00fcck.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Go-Optimierungen in VictoriaMetrics. Alexander Valjalkin\" src=\"\/wp-content\/uploads\/2020\/05\/709beca79884a9693098781974c26f4d.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Wie Sie sehen, ist die Implementierung des umgekehrten Index nicht sehr kompliziert. Aber das ist eine naive Implementierung. Was sind ihre Nachteile? Der Hauptnachteil der naiven Implementierung ist, dass ein solcher umgekehrter Index im Arbeitsspeicher gespeichert wird. Nach einem Neustart der Anwendung verlieren wir diesen Index. Es gibt keine Speicherung dieses Index auf der Festplatte. F\u00fcr eine Datenbank ist ein solcher umgekehrter Index kaum geeignet.<\/p>\n<p><\/p>\n<p>Der zweite Nachteil h\u00e4ngt ebenfalls mit dem Speicher zusammen. Der invertierte Index muss im Arbeitsspeicher untergebracht werden. Wenn er die Gr\u00f6\u00dfe des Arbeitsspeichers \u00fcberschreitet, bekommen wir offensichtlich einen 'Out of Memory'-Fehler. Das Programm wird dann nicht mehr funktionieren.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Go-Optimierungen in VictoriaMetrics. Alexander Valjalkin\" src=\"\/wp-content\/uploads\/2020\/05\/495394168ac03aa8cbcf56ccad1cb2b8.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Dieses Problem kann mit fertigen L\u00f6sungen wie <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/google\/leveldb\">LevelDB<\/a><\/noindex>, oder <noindex><a rel=\"nofollow\" href=\"https:\/\/rocksdb.org\/\">RocksDB<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p>Kurz gesagt, wir brauchen eine Datenbank, die es uns erm\u00f6glicht, drei Operationen schnell durchzuf\u00fchren. <\/p>\n<p><\/p>\n<ul>\n<li>Die erste Operation ist das Schreiben <code>von Schl\u00fcssel-Wert-Paaren<\/code> in diese Datenbank. Dies geschieht sehr schnell, wobei <code>von Schl\u00fcssel-Wert-Paaren<\/code> beliebige Strings sind. <\/li>\n<li>Die zweite Operation ist die schnelle Suche nach dem Wert anhand des gegebenen Schl\u00fcssels.<\/li>\n<li>Und die dritte Operation ist die schnelle Suche nach allen Werten anhand eines vorgegebenen Prefix. <\/li>\n<\/ul>\n<p><\/p>\n<p>LevelDB und RocksDB \u2013 diese Datenbanken wurden von Google und Facebook entwickelt. Zuerst erschien LevelDB. Dann haben die Leute von Facebook LevelDB genommen und verbessert, was zu RocksDB f\u00fchrte. Derzeit laufen fast alle internen Datenbanken bei Facebook auf RocksDB, einschlie\u00dflich MySQL, das ebenfalls auf RocksDB umgestellt wurde. Sie nannten es <noindex><a rel=\"nofollow\" href=\"http:\/\/myrocks.io\/\">MyRocks<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p>Der invertierte Index kann mit LevelDB implementiert werden. Wie geht das? Wir speichern als Schl\u00fcssel <code>label=value<\/code>. Und als Wert verwenden wir die ID der Zeitreihe, in der das Paar vorkommt. <code>label=value<\/code>.<\/p>\n<p><\/p>\n<p>Wenn wir viele Zeitreihen mit diesem Paar haben <code>label=value<\/code>, wird es viele Zeilen in dieser Datenbank mit denselben Schl\u00fcsseln und verschiedenen <code>timeseries_ids<\/code>geben. Um eine Liste aller <code>timeseries_ids<\/code>, die mit diesem beginnen <code>label=prefix<\/code>, zu erhalten, f\u00fchren wir einen Range Scan durch, f\u00fcr den diese Datenbank optimiert ist. Das hei\u00dft, wir w\u00e4hlen alle Zeilen aus, die mit <code>label=prefix<\/code> beginnen und erhalten die notwendigen <code>timeseries_ids<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Go-Optimierungen in VictoriaMetrics. Alexander Valjalkin\" src=\"\/wp-content\/uploads\/2020\/05\/a40877ecb758ac3bc1979bcd568d7e83.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Hier ist eine beispielhafte Umsetzung, wie sie in Go aussehen w\u00fcrde. Wir haben einen invertierten Index. Das ist LevelDB.<\/p>\n<p><\/p>\n<p>Die Funktion ist die gleiche wie bei der naiven Implementierung. Sie wiederholt fast Zeile f\u00fcr Zeile die naive Implementierung. Der einzige Unterschied ist, dass wir anstelle des Zugriffs auf <code>map<\/code> auf den invertierten Index zugreifen. Wir holen alle Werte f\u00fcr das erste <code>label=value<\/code>. Dann gehen wir alle verbleibenden Paare durch <code>label=value<\/code> und holen die entsprechenden Sets von metricIDs f\u00fcr sie. Danach finden wir die Schnittmenge. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Go-Optimierungen in VictoriaMetrics. Alexander Valjalkin\" src=\"\/wp-content\/uploads\/2020\/05\/de11b15286816ada04b22727ff78e43d.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Es scheint alles gut zu sein, aber diese L\u00f6sung hat Nachteile. VictoriaMetrics hat urspr\u00fcnglich den invertierten Index auf Basis von LevelDB implementiert. Letztendlich musste man jedoch darauf verzichten.<\/p>\n<p><\/p>\n<p>Warum? Weil LevelDB langsamer ist als die naive Implementierung. Bei der naiven Implementierung holen wir sofort den gesamten Slice f\u00fcr den gegebenen Schl\u00fcssel. <code>metricIDs<\/code>Diese sehr schnelle Operation \u2013 der gesamte Slice ist bereit zur Verwendung.<\/p>\n<p><\/p>\n<p>In LevelDB muss bei jedem Aufruf der Funktion <code>GetValues<\/code> \u00fcber alle Zeilen iteriert werden, die mit <code>label=value<\/code>beginnen. F\u00fcr jede Zeile muss der Wert <code>timeseries_ids<\/code>abgerufen werden. Aus diesen <code>timeseries_ids<\/code> muss ein Slice erstellt werden. <code>timeseries_ids<\/code>. Offensichtlich ist das viel langsamer, als einfach auf eine normale map mit dem Schl\u00fcssel zuzugreifen.<\/p>\n<p><\/p>\n<p>Ein weiterer Nachteil ist, dass LevelDB in C geschrieben ist. Der Zugriff auf C-Funktionen aus Go ist nicht sehr schnell. Es dauert Hunderte von Nanosekunden. Das ist nicht sehr schnell, da im Vergleich zu einem gew\u00f6hnlichen Funktionsaufruf, der in Go geschrieben ist und 1-5 Nanosekunden ben\u00f6tigt, der Performance-Unterschied in Zehnerordnungen betr\u00e4gt. F\u00fcr VictoriaMetrics war das ein fataler Nachteil \ud83d\ude42<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Go-Optimierungen in VictoriaMetrics. Alexander Valjalkin\" src=\"\/wp-content\/uploads\/2020\/05\/47cace825807b7b062064c2e790dc9d3.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Deshalb habe ich meine eigene Implementierung eines invertierten Index geschrieben. Ich nannte sie <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/VictoriaMetrics\/VictoriaMetrics\/tree\/master\/lib\/mergeset\">mergeset<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p>Mergeset basiert auf der Datenstruktur MergeTree. Diese Datenstruktur stammt aus ClickHouse. Offensichtlich muss der Mergeset f\u00fcr eine schnelle Suche <code>timeseries_ids<\/code> nach dem angegebenen Schl\u00fcssel optimiert sein. Mergeset ist vollst\u00e4ndig in Go geschrieben. Sie k\u00f6nnen die <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/VictoriaMetrics\/VictoriaMetrics\">Quellcodes von VictoriaMetrics auf GitHub einsehen.<\/a><\/noindex>Die Implementierung des Mergeset befindet sich im Ordner <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/VictoriaMetrics\/VictoriaMetrics\/tree\/master\/lib\/mergeset\">\/lib\/mergeset<\/a><\/noindex>. Sie k\u00f6nnen versuchen, zu verstehen, was dort passiert.<\/p>\n<p><\/p>\n<p>Die API des Mergeset \u00e4hnelt der von LevelDB und RocksDB. Das hei\u00dft, sie erm\u00f6glicht es, neue Eintr\u00e4ge schnell zu speichern und Eintr\u00e4ge anhand eines angegebenen Pr\u00e4fixes schnell auszuw\u00e4hlen.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Go-Optimierungen in VictoriaMetrics. Alexander Valjalkin\" src=\"\/wp-content\/uploads\/2020\/05\/845704ed36f70beb468907d22701af59.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>\u00dcber die Nachteile des Mergeset werden wir sp\u00e4ter sprechen. Jetzt reden wir \u00fcber die Probleme, die bei VictoriaMetrics in der Produktion bei der Implementierung des invertierten Index aufgetreten sind.<\/p>\n<p><\/p>\n<p>Warum sind sie aufgetreten?<\/p>\n<p><\/p>\n<p>Der erste Grund ist die hohe Wechselrate. Auf Russisch \u00fcbersetzt bedeutet das die h\u00e4ufige \u00c4nderung von Zeitreihen. Dies geschieht, wenn eine Zeitreihe endet und eine neue beginnt oder viele neue Zeitreihen beginnen. Und das passiert h\u00e4ufig.<\/p>\n<p><\/p>\n<p>Der zweite Grund ist die gro\u00dfe Anzahl von Zeitreihen. Zu Beginn, als das Monitoring an Popularit\u00e4t gewann, war die Anzahl der Zeitreihen gering. Beispielsweise muss jeder Computer die CPU-, Arbeitsspeicher-, Netzwerk- und Festplattennutzung \u00fcberwachen. 4 Zeitreihen pro Computer. Angenommen, Sie haben 100 Computer und 400 Zeitreihen. Das ist sehr wenig. <\/p>\n<p><\/p>\n<p>Im Laufe der Zeit haben die Menschen herausgefunden, dass man detailliertere Informationen messen kann. Zum Beispiel kann man nicht nur die Auslastung des gesamten Prozessors messen, sondern auch die der einzelnen Prozessorkerne. Wenn Sie 40 Prozessorkerne haben, haben Sie dementsprechend 40-mal so viele Zeitreihen zur Messung der Prozessorlast. <\/p>\n<p><\/p>\n<p>Aber das ist noch nicht alles. Jeder Prozessorkern kann mehrere Zust\u00e4nde haben, wie zum Beispiel idle, wenn er nicht aktiv ist. Au\u00dferdem gibt es die Arbeit im User Space, die Arbeit im Kernel Space und andere Zust\u00e4nde. Jedes dieser Zust\u00e4nde kann ebenfalls als separate Zeitreihe gemessen werden. Dies erh\u00f6ht die Anzahl der Reihen zus\u00e4tzlich um das 7- bis 8-fache.<\/p>\n<p><\/p>\n<p>Aus einer Metrik ergeben sich somit 40 x 8 = 320 Metriken nur f\u00fcr einen Computer. Multiplizieren wir das mit 100, erhalten wir 32.000 statt 400. <\/p>\n<p><\/p>\n<p>Dann kam Kubernetes. Und das Ganze wird noch komplizierter, da in Kubernetes viele verschiedene Dienste gehostet werden k\u00f6nnen. Jeder Dienst in Kubernetes besteht aus vielen Pods. Und all das muss \u00fcberwacht werden. Dar\u00fcber hinaus haben wir einen st\u00e4ndigen Deployment-Prozess neuer Versionen Ihrer Dienste. F\u00fcr jede neue Version m\u00fcssen neue Zeitreihen erstellt werden. Am Ende w\u00e4chst die Anzahl der Zeitreihen exponentiell, und wir stehen vor dem Problem einer hohen Anzahl von Zeitreihen, das als High-Cardinality bekannt ist. VictoriaMetrics bew\u00e4ltigt dies im Vergleich zu anderen Zeitreihendatenbanken erfolgreich. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Go-Optimierungen in VictoriaMetrics. Alexander Valjalkin\" src=\"\/wp-content\/uploads\/2020\/05\/c11b7ce6d3294ae420243f72636af4b9.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Lassen Sie uns den hohen Churn-Rate genauer betrachten. Was verursacht einen hohen Churn-Rate in der Produktion? Weil einige Werte von Labels und Tags sich st\u00e4ndig \u00e4ndern.<\/p>\n<p><\/p>\n<p>Nehmen wir zum Beispiel Kubernetes, wo es das Konzept gibt von <code>deployment<\/code>, d. h. wenn eine neue Version Ihrer Anwendung ausgerollt wird. Die Entwickler von Kubernetes haben seltsamerweise beschlossen, die ID des Deployments in das Label aufzunehmen.<\/p>\n<p><\/p>\n<p>Was hat das zur Folge? Bei jedem neuen Deployment werden alle alten Zeitreihen unterbrochen, und stattdessen beginnen neue Zeitreihen mit einem neuen Labelwert. <code>deployment_id<\/code>. Solcherart Reihen k\u00f6nnen Hunderttausende und sogar Millionen betragen.<\/p>\n<p><\/p>\n<p>Eine wichtige Besonderheit dabei ist, dass die Gesamtzahl der Zeitreihen steigt, w\u00e4hrend die Anzahl der derzeit aktiven Zeitreihen, f\u00fcr die Daten eintreffen, konstant bleibt. Ein solcher Zustand wird als hoher Churn-Rate bezeichnet.<\/p>\n<p><\/p>\n<p>Das Hauptproblem der hohen Abwanderungsrate besteht darin, eine konstante Suchgeschwindigkeit f\u00fcr alle Zeitreihen \u00fcber einen festgelegten Satz von Labels in einem bestimmten Zeitraum zu gew\u00e4hrleisten. In der Regel handelt es sich um den Zeitraum der letzten Stunde oder des letzten Tages. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Go-Optimierungen in VictoriaMetrics. Alexander Valjalkin\" src=\"\/wp-content\/uploads\/2020\/05\/ac18482b87cc37624d163b30c68cf7be.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Wie kann man dieses Problem l\u00f6sen? Hier ist die erste Variante. Man unterteilt den Inverted Index in unabh\u00e4ngige Teile nach Zeit. Das hei\u00dft, ein bestimmter Zeitraum verstreicht, der aktuelle Inverted Index wird beendet. Und wir erstellen einen neuen Inverted Index. Verstreicht ein weiterer Zeitraum, erstellen wir einen weiteren und noch einen. <\/p>\n<p><\/p>\n<p>Bei der Abfrage dieser Inverted Indizes finden wir eine Menge von Inverted Indizes, die in den festgelegten Zeitraum fallen. Dementsprechend w\u00e4hlen wir die IDs der Zeitreihen daraus aus. <\/p>\n<p><\/p>\n<p>Das spart Ressourcen, denn wir m\u00fcssen keine Teile durchsuchen, die nicht in den festgelegten Zeitraum fallen. Das hei\u00dft, normalerweise \u00fcberspringen wir Anfragen f\u00fcr vorherige Zeitintervalle, wenn wir Daten f\u00fcr die letzte Stunde ausw\u00e4hlen. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Go-Optimierungen in VictoriaMetrics. Alexander Valjalkin\" src=\"\/wp-content\/uploads\/2020\/05\/1afa3a88caa7423941ae3494b9cec706.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Es gibt eine weitere Variante zur L\u00f6sung dieses Problems. Man speichert f\u00fcr jeden Tag eine separate Liste von IDs der Zeitreihen, die an diesem Tag aufgetreten sind.<\/p>\n<p><\/p>\n<p>Der Vorteil dieser L\u00f6sung im Vergleich zur vorherigen ist, dass wir keine Informationen \u00fcber Zeitreihen duplizieren, die mit der Zeit nicht verschwinden. Sie sind st\u00e4ndig vorhanden und ver\u00e4ndern sich nicht. <\/p>\n<p><\/p>\n<p>Der Nachteil ist, dass eine solche L\u00f6sung schwieriger umzusetzen und schwieriger zu debuggen ist. Und VictoriaMetrics hat sich f\u00fcr diese L\u00f6sung entschieden. Historisch gesehen hat sich das so entwickelt. Diese L\u00f6sung zeigt sich auch besser im Vergleich zur vorherigen. Denn diese L\u00f6sung wurde nicht umgesetzt, weil es notwendig ist, Daten in jeder Partition f\u00fcr Zeitreihen zu duplizieren, die sich nicht \u00e4ndern, das hei\u00dft, die mit der Zeit nicht verschwinden. VictoriaMetrics wurde in erster Linie hinsichtlich des Speicherplatzverbrauchs optimiert, und die vorherige Implementierung verschlechterte den Speicherplatzverbrauch. Diese Implementierung eignet sich besser zur Minimierung des Speicherplatzverbrauchs, weshalb sie ausgew\u00e4hlt wurde. <\/p>\n<p><\/p>\n<p>Wir mussten damit k\u00e4mpfen. Der Kampf bestand darin, dass in dieser Implementierung trotzdem deutlich mehr Daten ausgew\u00e4hlt werden m\u00fcssen, <code>timeseries_ids<\/code> als wenn der Inverted Index zeitlich unterteilt wird.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Go-Optimierungen in VictoriaMetrics. Alexander Valjalkin\" src=\"\/wp-content\/uploads\/2020\/05\/5b10e61aaa803428ed3bfe31815f09d3.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Wie haben wir dieses Problem gel\u00f6st? Wir haben es auf originelle Weise gel\u00f6st \u2013 indem wir mehrere Identifikatoren von Zeitreihen in jedem Datensatz des invertierten Index anstelle eines einzigen Identifikators gespeichert haben. Das hei\u00dft, wir haben einen Schl\u00fcssel <code>label=value<\/code>, der in jeder Zeitreihe vorkommt. Und jetzt speichern wir mehrere <code>timeseries_ids<\/code> in einem Datensatz.<\/p>\n<p><\/p>\n<p>Hier ist ein Beispiel. Fr\u00fcher hatten wir N Datens\u00e4tze, jetzt haben wir einen Datensatz, dessen Pr\u00e4fix das gleiche ist wie das aller anderen. Der vorherige Datensatz enth\u00e4lt alle id der Zeitreihen. <\/p>\n<p><\/p>\n<p>Das hat es erm\u00f6glicht, die Scangeschwindigkeit eines solchen invertierten Index um das 10-Fache zu erh\u00f6hen. Au\u00dferdem konnte der Speicherverbrauch f\u00fcr den Cache gesenkt werden, weil wir jetzt die Zeichenfolge <code>label=value<\/code> nur einmal im Cache zusammen mit N mal speichern. Und diese Zeichenfolge kann gro\u00df sein, wenn in Ihren Tags und Labels lange Zeichenfolgen gespeichert sind, die Kubernetes gerne da einf\u00fcgt.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Go-Optimierungen in VictoriaMetrics. Alexander Valjalkin\" src=\"\/wp-content\/uploads\/2020\/05\/ae4f38a440203bad2d03cf2abd1faafe.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Eine weitere M\u00f6glichkeit zur Beschleunigung der Suche im invertierten Index ist das Sharding. Erstellen mehrerer invertierter Indizes statt eines und das Sharding der Daten zwischen ihnen nach Schl\u00fcssel. Das ist eine Menge <code>key=value<\/code> Paare. Das hei\u00dft, wir haben mehrere unabh\u00e4ngige invertierte Indizes, die wir parallel auf mehreren Prozessoren abfragen k\u00f6nnen. fr\u00fchere Implementierungen erm\u00f6glichten nur den Betrieb im Einzelprozessmodus, das hei\u00dft, Daten nur auf einem Kern zu scannen. Diese L\u00f6sung erm\u00f6glicht es, Daten sofort auf mehreren Kernen zu scannen, wie es ClickHouse gerne macht. Das planen wir zu implementieren.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Go-Optimierungen in VictoriaMetrics. Alexander Valjalkin\" src=\"\/wp-content\/uploads\/2020\/05\/5ced6446efbf8ded8d7fb91694a999b0.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Und jetzt zur\u00fcck zu unseren Schafen \u2013 zur Schnittfunktion <code>timeseries_ids<\/code>. Betrachten wir, welche Implementierungen m\u00f6glich sind. Diese Funktion erm\u00f6glicht es, <code>timeseries_ids<\/code> f\u00fcr eine gegebene Menge <code>label=value<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Go-Optimierungen in VictoriaMetrics. Alexander Valjalkin\" src=\"\/wp-content\/uploads\/2020\/05\/073a155018a91120c6996c324772f9af.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Die erste M\u00f6glichkeit ist die naive Implementierung. Zwei geschachtelte Schleifen. Wir erhalten als Eingabe der Funktion <code>intersectInts<\/code> zwei Slices \u2014 <code>a<\/code> und <code>b<\/code>. Am Ende sollte sie uns die Schnittmenge dieser Slices zur\u00fcckgeben.<\/p>\n<p><\/p>\n<p>Die naive Implementierung sieht so aus. Wir gehen alle Werte aus dem Slice <code>a<\/code>durch, innerhalb dieser Schleife durchlaufen wir alle Werte aus Slice <code>b<\/code>. Und vergleichen sie. Wenn sie \u00fcbereinstimmen, dann haben wir die Schnittmenge gefunden. Und speichern sie in <code>result<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Go-Optimierungen in VictoriaMetrics. Alexander Valjalkin\" src=\"\/wp-content\/uploads\/2020\/05\/b89dcbb4f6d73377f9cf4f50169d6dd7.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>. Welche Nachteile gibt es? Die quadratische Komplexit\u00e4t \u2014 das ist ihr Hauptnachteil. Zum Beispiel, wenn Sie Gr\u00f6\u00dfen des Slices haben <code>a<\/code> und <code>b<\/code> Wenn Sie eine Million pro Iteration verwenden, wird diese Funktion Ihnen niemals eine Antwort zur\u00fcckgeben. Denn sie m\u00fcsste eine Billion Iterationen durchf\u00fchren, was selbst f\u00fcr moderne Computer extrem viel ist.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Go-Optimierungen in VictoriaMetrics. Alexander Valjalkin\" src=\"\/wp-content\/uploads\/2020\/05\/bad26b0092a2fd7fc813bcb79a9138ce.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Die zweite Implementierung basiert auf map. Wir erstellen eine map und f\u00fcgen alle Werte aus dem Slice in diese map ein. <code>a<\/code>Dann durchlaufen wir den Slice mit einer separaten Schleife. <code>b<\/code>Und \u00fcberpr\u00fcfen, ob dieser Wert im Slice <code>b<\/code> in der map vorhanden ist. Wenn ja, f\u00fcgen wir ihn dem Ergebnis hinzu. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Go-Optimierungen in VictoriaMetrics. Alexander Valjalkin\" src=\"\/wp-content\/uploads\/2020\/05\/1e610acf641a9cfe4c80aac1fe26044c.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Welche Vorteile gibt es? Der Vorteil liegt darin, dass hier nur eine lineare Komplexit\u00e4t vorliegt. Das hei\u00dft, die Funktion wird f\u00fcr gr\u00f6\u00dfere Slice-Gr\u00f6\u00dfen viel schneller ausgef\u00fchrt. F\u00fcr einen Slice mit einer Million Elementen wird diese Funktion in 2 Millionen Iterationen ausgef\u00fchrt, im Gegensatz zu einer Billion Iterationen wie in der vorherigen Funktion.<\/p>\n<p><\/p>\n<p>Ein Nachteil ist jedoch, dass diese Funktion mehr Speicher ben\u00f6tigt, um die map zu erstellen.<\/p>\n<p><\/p>\n<p>Ein zweiter Nachteil ist der gro\u00dfe Overhead beim Hashing. Dieser Nachteil ist nicht sehr offensichtlich. Auch uns war er anfangs nicht sehr klar, weshalb die Intersection in VictoriaMetrics zuerst \u00fcber map implementiert wurde. Aber das Profiling zeigte, dass die meiste CPU-Zeit f\u00fcr das Schreiben in die map und die \u00dcberpr\u00fcfung, ob ein Wert in dieser map vorhanden ist, verwendet wird.<\/p>\n<p><\/p>\n<p>Warum wird in diesen Stellen CPU-Zeit verbraucht? Weil in diesen Zeilen Go die Hashing-Operation durchf\u00fchrt. Das hei\u00dft, es berechnet den Hash vom Schl\u00fcssel, um dann \u00fcber den angegebenen Index in der HashMap zuzugreifen. Der Vorgang zur Berechnung des Hashes dauert mehrere Dutzend Nanosekunden. Das ist f\u00fcr VictoriaMetrics langsam.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Go-Optimierungen in VictoriaMetrics. Alexander Valjalkin\" src=\"\/wp-content\/uploads\/2020\/05\/969f10d875d88998e958894ca28b1181.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Ich habe beschlossen, ein Bitset zu implementieren, das speziell f\u00fcr diesen Fall optimiert ist. So sieht jetzt die Intersection von zwei Slices aus. Hier erstellen wir ein Bitset. Wir f\u00fcgen die Elemente aus dem ersten Slice hinzu und pr\u00fcfen dann die Anwesenheit dieser Elemente im zweiten Slice und f\u00fcgen sie dem Ergebnis hinzu. Es unterscheidet sich fast nicht vom vorherigen Beispiel. Das einzige, was wir hier ge\u00e4ndert haben, ist der Zugriff auf die map durch benutzerdefinierte Funktionen. <code>add<\/code> und <code>has<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Go-Optimierungen in VictoriaMetrics. Alexander Valjalkin\" src=\"\/wp-content\/uploads\/2020\/05\/b4ec0753d30d9684868a031f605632c0.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Auf den ersten Blick scheint es, dass das langsamer sein sollte, wenn zuvor eine Standardmap verwendet wurde und hier zus\u00e4tzliche Funktionen aufgerufen werden, aber das Profiling zeigt, dass dieses Ding zehnmal schneller arbeitet als die Standardmap f\u00fcr den Fall mit VictoriaMetrics.<\/p>\n<p><\/p>\n<p>Dar\u00fcber hinaus verbraucht es wesentlich weniger Speicher im Vergleich zur Implementierung mit map. Denn wir speichern hier Bits statt achtbyte-Werte.<\/p>\n<p><\/p>\n<p>Ein Nachteil dieser Umsetzung ist, dass sie nicht so offensichtlich und nicht trivial ist. <\/p>\n<p><\/p>\n<p>Ein weiterer Nachteil, den viele m\u00f6glicherweise \u00fcbersehen, ist, dass diese Implementierung in bestimmten F\u00e4llen schlecht funktionieren kann. Das hei\u00dft, sie ist f\u00fcr einen spezifischen Fall optimiert, n\u00e4mlich f\u00fcr den Schnittpunkt der IDs der Zeitreihen von VictoriaMetrics. Das bedeutet nicht, dass sie f\u00fcr alle F\u00e4lle geeignet ist. Wenn sie falsch verwendet wird, f\u00fchrt das nicht zu einer Leistungssteigerung, sondern zu einem Out-of-Memory-Fehler und einer Verlangsamung der Leistung. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Go-Optimierungen in VictoriaMetrics. Alexander Valjalkin\" src=\"\/wp-content\/uploads\/2020\/05\/94bd174afe43e67b3cf279dc7a1be17c.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Betrachten wir die Implementierung dieser Struktur. Wenn Sie schauen m\u00f6chten, befindet sie sich im Quellcode von VictoriaMetrics im Ordner <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/VictoriaMetrics\/VictoriaMetrics\/tree\/master\/lib\/uint64set\">lib\/uint64set<\/a><\/noindex>. Sie ist speziell f\u00fcr den Fall von VictoriaMetrics optimiert, wo <code>timeseries_id<\/code> einen 64-Bit-Wert darstellt, wobei die ersten 32 Bits konstant sind und nur die letzten 32 Bits sich \u00e4ndern.<\/p>\n<p><\/p>\n<p>Diese Datenstruktur wird nicht auf der Festplatte gespeichert, sie funktioniert nur im Speicher. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Go-Optimierungen in VictoriaMetrics. Alexander Valjalkin\" src=\"\/wp-content\/uploads\/2020\/05\/e24ca65f860e2b04037e073f7acae0c1.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Hier ist ihre API. Sie ist nicht kompliziert. Die API ist genau auf das spezifische Beispiel der Nutzung von VictoriaMetrics abgestimmt. Das hei\u00dft, es gibt hier keine \u00fcberfl\u00fcssigen Funktionen. Hier sind die Funktionen, die VictoriaMetrics eindeutig verwendet.<\/p>\n<p><\/p>\n<p>Es gibt die Funktion <code>add<\/code>, die neue Werte hinzuf\u00fcgt. Es gibt die Funktion <code>has<\/code>, die neue Werte \u00fcberpr\u00fcft. Und es gibt die Funktion <code>del<\/code>, die Werte l\u00f6scht. Es gibt eine Hilfsfunktion <code>len<\/code>, die die Gr\u00f6\u00dfe der Menge zur\u00fcckgibt. Die Funktion <code>clone<\/code> klont die Menge. Und die Funktion <code>appendto<\/code> wandelt dieses Set in einen Slice um. <code>timeseries_ids<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Go-Optimierungen in VictoriaMetrics. Alexander Valjalkin\" src=\"\/wp-content\/uploads\/2020\/05\/874b04042d13f342d39f8f393e7fb78c.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>So sieht die Implementierung dieser Datenstruktur aus. Im Set gibt es zwei Elemente:<\/p>\n<p><\/p>\n<ul>\n<li>\n<p><code>ItemsCount<\/code> \u2013 dies ist ein Hilfsfeld, um schnell die Anzahl der Elemente im Set zur\u00fcckzugeben. Man k\u00f6nnte auf dieses Hilfsfeld verzichten, aber es musste hier hinzugef\u00fcgt werden, da VictoriaMetrics oft in seinen Algorithmen die L\u00e4nge des Bitsets abfragt.<\/p>\n<p>\n<\/li>\n<li>\n<p>Das zweite Feld ist <code>buckets<\/code>. Dies ist ein Slice aus der Struktur <code>bucket32<\/code>. Jede Struktur enth\u00e4lt <code>hi<\/code> ein Feld. Dies sind die oberen 32 Bits. Und zwei Slices \u2014 <code>b16his<\/code> und <code>buckets<\/code> aus <code>bucket16<\/code> Strukturen. <\/p>\n<p>\n<\/li>\n<\/ul>\n<p><\/p>\n<p>Hier werden die oberen 16 Bits des zweiten Teils der 64-Bit-Struktur gespeichert. Und hier werden die Bitsets f\u00fcr die unteren 16 Bits jedes Bytes gespeichert. <\/p>\n<p><\/p>\n<p><code>Bucket64<\/code> besteht aus einem Array <code>uint64<\/code>. Die L\u00e4nge wird anhand dieser Konstanten berechnet. In einem <code>bucket16<\/code> kann maximal <code>2^16=65536<\/code> Bits gespeichert werden. Wenn man das durch 8 teilt, sind das 8 Kilobyte. Wenn man das noch einmal durch 8 teilt, sind das 1000 <code>uint64<\/code> Werte. Das hei\u00dft, <code>Bucket16<\/code> \u2013 das ist unsere 8-Kilobyte-Struktur. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Go-Optimierungen in VictoriaMetrics. Alexander Valjalkin\" src=\"\/wp-content\/uploads\/2020\/05\/1cca4e6bfafa3408a3c95e0118e26d80.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Lassen Sie uns betrachten, wie eine der Methoden dieser Struktur zur Hinzuf\u00fcgung eines neuen Wertes implementiert ist. <\/p>\n<p><\/p>\n<p>Alles beginnt mit <code>uint64<\/code> dem Wert. Wir berechnen die oberen 32 Bit, berechnen die unteren 32 Bit. Wir gehen alle <code>buckets<\/code>durch. Wir vergleichen die oberen 32 Bit in jedem Bucket mit dem hinzuzuf\u00fcgenden Wert. Und wenn sie \u00fcbereinstimmen, rufen wir die Funktion <code>add<\/code> in der Struktur b32 <code>buckets<\/code>auf. Und f\u00fcgen die unteren 32 Bit dort hinzu. Und wenn dies zur\u00fcckgegeben wurde, <code>true<\/code>, bedeutet das, dass wir diesen Wert dort hinzugef\u00fcgt haben und wir diesen Wert nicht hatten. Wenn es zur\u00fcckgibt <code>false<\/code>, bedeutet das, dass ein solcher Wert bereits vorhanden war. Dann erh\u00f6hen wir die Anzahl der Elemente in der Struktur. <\/p>\n<p><\/p>\n<p>Wenn wir den ben\u00f6tigten <code>Bucket<\/code> mit dem entsprechenden Hi-Wert nicht gefunden haben, rufen wir die Funktion <code>addAlloc<\/code>, die einen neuen <code>Bucket<\/code>zuweist, indem sie ihn in die Bucket-Struktur hinzuf\u00fcgt.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Go-Optimierungen in VictoriaMetrics. Alexander Valjalkin\" src=\"\/wp-content\/uploads\/2020\/05\/c5d1e5de767f47c7c40e01e12b6d0617.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Dies ist die Implementierung der Funktion <code>b32.add<\/code>. Sie \u00e4hnelt der vorherigen Implementierung. Wir berechnen die oberen 16 Bit und die unteren 16 Bit.<\/p>\n<p><\/p>\n<p>Dann gehen wir alle oberen 16 Bit durch. Wir finden \u00dcbereinstimmungen. Und bei \u00dcbereinstimmung rufen wir die Methode add auf, die wir auf der n\u00e4chsten Seite f\u00fcr <code>bucket16<\/code>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Go-Optimierungen in VictoriaMetrics. Alexander Valjalkin\" src=\"\/wp-content\/uploads\/2020\/05\/e98c4ef316f3a894fd7019c3c85696f7.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>und hier ist die unterste Ebene, die maximal optimiert sein sollte. Wir berechnen f\u00fcr <code>uint64<\/code> die ID den Wert im Slice-Bit sowie <code>bitmask.<\/code>Dies ist die Maske f\u00fcr den angegebenen 64-Bit-Wert, mit der wir das Vorhandensein dieses Bits \u00fcberpr\u00fcfen oder es setzen k\u00f6nnen. Wir pr\u00fcfen, ob dieses Bit gesetzt ist, setzen es und geben das Vorhandensein zur\u00fcck. So haben wir eine Implementierung, die die Schnittoperation von IDs zeitlicher Reihen um das 10-fache im Vergleich zu gew\u00f6hnlichen Maps beschleunigt hat.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Go-Optimierungen in VictoriaMetrics. Alexander Valjalkin\" src=\"\/wp-content\/uploads\/2020\/05\/4dfa5a6142829a3551be22d6c9c3ab92.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>In VictoriaMetrics gibt es neben dieser Optimierung viele andere Optimierungen. Die meisten dieser Optimierungen wurden nicht grundlos hinzugef\u00fcgt, sondern nach der Profilerstellung des Codes in der Produktion.<\/p>\n<p><\/p>\n<p>Das Hauptprinzip der Optimierung lautet \u2013 keine Optimierung hinzuf\u00fcgen, nur weil angenommen wird, dass hier ein Engpass besteht, denn es kann sich herausstellen, dass dort kein Engpass existiert. Optimierung verschlechtert in der Regel die Codequalit\u00e4t. Daher sollte man nur nach der Profilerstellung optimieren und vorzugsweise in der Produktion, damit es sich um reale Daten handelt. Wer interessiert ist, kann sich den Quellcode von VictoriaMetrics ansehen und andere Optimierungen studieren, die dort vorhanden sind.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Go-Optimierungen in VictoriaMetrics. Alexander Valjalkin\" src=\"\/wp-content\/uploads\/2020\/05\/7585c4dc782627bac5b649e6a55e2ffb.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p><em>Ich habe eine Frage zu bitset. Es \u00e4hnelt sehr der Implementierung des C++ Vektors Bool, dem optimierten bitset. Haben Sie die Implementierung von dort \u00fcbernommen?<\/em><\/p>\n<p><\/p>\n<p>Nein, nicht von dort. Bei der Umsetzung dieses Bitsets habe ich mich an das Wissen \u00fcber die Struktur dieser IDs-Zeiten orientiert, die in VictoriaMetrics verwendet werden. Ihre Struktur ist so, dass die oberen 32 Bits gr\u00f6\u00dftenteils konstant sind. Die unteren 32 Bits k\u00f6nnen variieren. Je niedriger das Bit, desto h\u00e4ufiger kann es sich \u00e4ndern. Daher ist diese Implementierung speziell auf diese Datenstruktur optimiert. Die C++-Implementierung ist, soweit ich wei\u00df, f\u00fcr den allgemeinen Fall optimiert. Eine Optimierung f\u00fcr den allgemeinen Fall bedeutet, dass sie nicht die beste L\u00f6sung f\u00fcr den speziellen Fall ist.<\/p>\n<p><\/p>\n<p>Ich empfehle Ihnen auch, den Vortrag von Alexey Milovid zu sehen. Er hat vor etwa einem Monat \u00fcber Optimierungen in ClickHouse f\u00fcr spezielle Anforderungen gesprochen. Er erkl\u00e4rt genau, dass die C++-Implementierung oder eine andere Implementierung im Allgemeinen f\u00fcr eine gute durchschnittliche Leistung optimiert sind. Sie kann schlechter abschneiden als eine spezialisierte Implementierung f\u00fcr bestimmte Kenntnisse, wie bei uns, wo wir wissen, dass die oberen 32 Bits gr\u00f6\u00dftenteils konstant sind.<\/p>\n<p><\/p>\n<p><em>Ich habe eine zweite Frage. Was ist der grundlegende Unterschied zu InfluxDB?<\/em><\/p>\n<p><\/p>\n<p>Es gibt viele grundlegende Unterschiede. Im Hinblick auf Leistung und Speicherverbrauch zeigt InfluxDB in Tests 10-mal h\u00f6heren Speicherverbrauch f\u00fcr hochkar\u00e4tige Zeitreihen, wenn Sie viele davon haben, zum Beispiel Millionen. Von VictoriaMetrics ben\u00f6tigen 1 GB f\u00fcr eine Million aktiver Serien, w\u00e4hrend InfluxDB dabei 10 GB verbraucht. Und das ist ein erheblicher Unterschied. <\/p>\n<p><\/p>\n<p>Der zweite grundlegende Unterschied besteht darin, dass InfluxDB merkw\u00fcrdige Abfragesprachen \u2013 Flux und InfluxQL \u2013 verwendet. Diese sind im Vergleich zu <noindex><a rel=\"nofollow\" href=\"https:\/\/medium.com\/@valyala\/promql-tutorial-for-beginners-9ab455142085\">PromQL<\/a><\/noindex>, das in VictoriaMetrics unterst\u00fctzt wird, nicht sehr benutzerfreundlich f\u00fcr die Arbeit mit Zeitreihen. PromQL ist die Abfragesprache aus Prometheus.<\/p>\n<p><\/p>\n<p>Ein weiteres Unterscheidungsmerkmal ist, dass InfluxDB ein etwas seltsames Datenmodell hat, in dem jede Zeile mehrere Felder mit unterschiedlichen Tag-Sets speichern kann. Diese Zeilen werden weiter in verschiedene Tabellen unterteilt. Diese zus\u00e4tzlichen Komplikationen erschweren die sp\u00e4tere Arbeit mit dieser Datenbank. Es ist schwierig, sie zu pflegen und zu verstehen.<\/p>\n<p><\/p>\n<p>In VictoriaMetrics ist alles viel einfacher. Jede Zeitreihe stellt ein Schl\u00fcssel-Wert-Paar dar. Der Wert ist eine Menge von Punkten \u2013 <code>(timestamp, value)<\/code>, w\u00e4hrend der Schl\u00fcssel ein Satz ist. <code>label=value<\/code>. Es gibt keine Trennung zwischen Feldern und Messwerten. Das erm\u00f6glicht Ihnen, beliebige Daten auszuw\u00e4hlen und diese dann zu kombinieren, zu addieren, zu subtrahieren, zu multiplizieren und zu dividieren, im Gegensatz zu InfluxDB, wo Berechnungen zwischen verschiedenen Reihen meines Wissens nach noch nicht implementiert sind. Selbst wenn sie implementiert w\u00e4ren, w\u00e4re es kompliziert, da man eine Menge Code schreiben m\u00fcsste. <\/p>\n<p><\/p>\n<p><em>Ich habe eine kl\u00e4rende Frage. Habe ich richtig verstanden, dass es ein Problem gab, von dem Sie erz\u00e4hlt haben, dass dieser invertierte Index nicht im Speicher Platz findet, weshalb das Partitionieren stattfindet?<\/em><\/p>\n<p><\/p>\n<p>Zun\u00e4chst habe ich eine naive Implementierung eines inversen Index auf einer Standard-Go-Map gezeigt. Eine solche Implementierung eignet sich nicht f\u00fcr Datenbanken, da dieser inverser Index nicht auf der Festplatte gespeichert wird, w\u00e4hrend eine Datenbank die Daten auf der Festplatte speichern muss, damit diese bei einem Neustart verf\u00fcgbar bleiben. In dieser Implementierung w\u00e4re der inverse Index nach einem Neustart der Anwendung verschwunden, und Sie w\u00fcrden den Zugriff auf alle Daten verlieren, weil Sie sie nicht finden k\u00f6nnen. <\/p>\n<p><\/p>\n<p><em>Hallo! Vielen Dank f\u00fcr den Vortrag! Mein Name ist Pavel. Ich komme von der Firma Wildberries. Ich habe mehrere Fragen an Sie. Die erste Frage. Glauben Sie, dass wenn Sie ein anderes Prinzip beim Entwurf der Architektur Ihrer Anwendung gew\u00e4hlt und die Daten nach Zeit partitioniert h\u00e4tten, es Ihnen vielleicht m\u00f6glich gewesen w\u00e4re, Daten bei der Suche zu kreuzen, basierend nur darauf, dass in einer Partition die Daten f\u00fcr einen bestimmten Zeitraum liegen, d.h. f\u00fcr einen Zeitintervall, und dass Sie sich nicht darum k\u00fcmmern m\u00fcssten, dass Ihre Teile unterschiedlich verteilt sind? Die zweite Frage \u2014 da Sie solchen Algorithmus mit Bitset und allem anderen implementieren, haben Sie vielleicht versucht, Prozessoranweisungen zu verwenden? Vielleicht haben Sie solche Optimierungen ausprobiert?<\/em><\/p>\n<p><\/p>\n<p>Auf die zweite Frage antworte ich sofort. Damit sind wir noch nicht so weit gekommen. Aber wenn es notwendig ist, werden wir das tun. Und die erste, was war die Frage?<\/p>\n<p><\/p>\n<p><em>Sie haben zwei Szenarien diskutiert. Und gesagt, dass Sie das zweite mit einer komplizierteren Implementierung gew\u00e4hlt haben. Und dass Sie das erste, wo die Daten nach Zeit partitioniert sind, nicht bevorzugt haben.<\/em> <\/p>\n<p><\/p>\n<p>Ja. Im ersten Fall w\u00e4re das gesamte Volumen des Index gr\u00f6\u00dfer gewesen, weil wir in jeder Partition Daten-Duplikate f\u00fcr die Zeitreihen speichern m\u00fcssten, die sich durch all diese Partitionen ziehen. Und wenn Sie eine geringe Churn-Rate bei den Zeitreihen haben, d.h. st\u00e4ndig die gleichen Reihen verwendet werden, h\u00e4tten wir im ersten Fall erheblich mehr Speicherplatz im Vergleich zum zweiten Fall verloren.<\/p>\n<p><\/p>\n<p>So ist es \u2013 ja, zeitbasierte Partitionierung ist eine gute Option. Das verwendet auch Prometheus. Aber Prometheus hat einen anderen Nachteil. Beim Zusammenf\u00fchren dieser Datenst\u00fccke ben\u00f6tigt er viel Speicherplatz f\u00fcr die Metainformation \u00fcber alle Labels und Zeitreihen. Daher steigt der Speicherverbrauch beim Zusammenf\u00fchren von gro\u00dfen Datenst\u00fccken erheblich, im Gegensatz zu VictoriaMetrics. Beim Zusammenf\u00fchren verbraucht VictoriaMetrics \u00fcberhaupt keinen Speicher, es werden nur einige Kilobyte verwendet, unabh\u00e4ngig von der Gr\u00f6\u00dfe der zusammenzuf\u00fchrenden Datenst\u00fccke.<\/p>\n<p><\/p>\n<p><em>Der Algorithmus, den Sie verwenden, nutzt Speicher. Dort werden Zeitreihen-Labels markiert, auf denen Werte vorhanden sind. So pr\u00fcfen Sie die Paarweise Existenz in einem Datenarray und im anderen. Und Sie verstehen \u2013 gab es ein Intersect oder nicht. Normalerweise implementieren Datenbanken Cursors, Iteratoren, die ihren aktuellen Zustand speichern und durch sortierte Daten laufen, was Ihnen eine einfache Komplexit\u00e4t dieser Operationen erm\u00f6glicht.<\/em> <\/p>\n<p><\/p>\n<p>Warum verwenden wir keine Cursors f\u00fcr das \u00dcberschneiden von Daten?<\/p>\n<p><\/p>\n<p><em>Ja.<\/em> <\/p>\n<p><\/p>\n<p>In LevelDB oder im mergeset haben wir genau die sortierten Zeilen gespeichert. Wir k\u00f6nnten mit einem Cursor durchgehen und das \u00dcberschneiden finden. Aber warum benutzen wir das nicht? Weil das langsam ist. Denn Cursors implizieren, dass f\u00fcr jede Zeile eine Funktion aufgerufen werden muss. Der Funktionsaufruf dauert 5 Nanosekunden. Und wenn Sie 100.000.000 Zeilen haben, dann verbringen wir allein f\u00fcr den Funktionsaufruf eine halbe Sekunde.<\/p>\n<p><\/p>\n<p><em>Das gibt es, ja. Und ich habe eine letzte Frage. Die Frage mag vielleicht etwas seltsam klingen. Warum kann man beim Eingeben von Daten nicht alle erforderlichen Aggregationen berechnen und in der notwendigen Form speichern? Warum riesige Datenmengen in Systeme wie VictoriaMetrics, ClickHouse usw. speichern, um sp\u00e4ter viel Zeit mit ihnen zu verbringen?<\/em><\/p>\n<p><\/p>\n<p><em>Ich werde ein Beispiel geben, um es verst\u00e4ndlicher zu machen. Angenommen, wie funktioniert ein kleiner Spielzeug-Tachometer? Er zeichnet die Strecke auf, die Sie zur\u00fcckgelegt haben, und addiert sie st\u00e4ndig zu einer Gr\u00f6\u00dfe, zur zweiten \u2013 die Zeit. Und teilt. Und erh\u00e4lt die Durchschnittsgeschwindigkeit. Man kann ungef\u00e4hr das Gleiche machen. Alle notwendigen Fakten zusammenz\u00e4hlen.<\/em><\/p>\n<p><\/p>\n<p>Gut, ich habe die Frage verstanden. Ihr Beispiel ist in der Praxis relevant. Wenn Sie wissen, welche Aggregationen Sie ben\u00f6tigen, ist dies die beste Implementierung. Aber das Problem ist, dass die Leute diese Metriken, einige Daten in ClickHouse speichern, und sie wissen noch nicht, wie sie diese in Zukunft aggregieren oder filtern werden, deshalb m\u00fcssen sie alle Rohdaten speichern. Aber wenn Sie wissen, dass Sie etwas Durchschnittliches berechnen m\u00fcssen, warum sollten Sie es dann nicht berechnen, anstatt dort eine Menge Rohwerte zu speichern? Aber das gilt nur, wenn Sie genau wissen, was Sie brauchen.<\/p>\n<p><\/p>\n<p>\u00dcbrigens unterst\u00fctzen Datenbanken zur Speicherung von Zeitserien die Berechnung von Aggregaten. Zum Beispiel unterst\u00fctzt Prometheus <noindex><a rel=\"nofollow\" href=\"https:\/\/prometheus.io\/docs\/prometheus\/latest\/configuration\/recording_rules\/\">Aufzeichnungsregeln<\/a><\/noindex>. Das hei\u00dft, Sie k\u00f6nnen das tun, wenn Sie wissen, welche Aggregationen Sie ben\u00f6tigen. In VictoriaMetrics gibt es das zurzeit nicht, aber normalerweise wird Prometheus davor gesetzt, wo Sie dies in den Aufzeichnungsregeln machen k\u00f6nnen.<\/p>\n<p><\/p>\n<p>Zum Beispiel musste ich bei meinem vorherigen Job die Anzahl der Ereignisse in einem gleitenden Fenster der letzten Stunde z\u00e4hlen. Das Problem war, dass ich eine benutzerdefinierte Implementierung in Go machen musste, d. h. einen Dienst zur Z\u00e4hlung dieser Sache. Dieser Dienst war letztendlich nicht trivial, weil es kompliziert zu berechnen ist. Die Implementierung kann einfach sein, wenn Sie einige Aggregate in festgelegten Zeitintervallen z\u00e4hlen m\u00fcssen. Wenn Sie jedoch Ereignisse in einem gleitenden Fenster z\u00e4hlen m\u00f6chten, ist das nicht so einfach, wie es scheint. Ich denke, das ist bis jetzt nicht in ClickHouse oder in Zeitseriendatenbanken implementiert, weil es in der Implementierung schwierig ist.<\/p>\n<p><\/p>\n<p><em>Und noch eine Frage. Wir haben gerade \u00fcber das Mittelma\u00df gesprochen, und ich erinnere mich daran, dass es einmal so etwas wie Graphite mit dem Backend Carbon gab. Und es konnte alte Daten verdichten, d. h. eine Punkt pro Minute, einen Punkt pro Stunde usw. beibehalten. Im Grunde ist das recht praktisch, wenn wir Rohdaten, sagen wir, f\u00fcr einen Monat ben\u00f6tigen, w\u00e4hrend alles andere verdichtet werden kann. Aber Prometheus und VictoriaMetrics unterst\u00fctzen diese Funktionalit\u00e4t nicht. Ist geplant, das zu unterst\u00fctzen? Wenn nicht, warum?<\/em><\/p>\n<p><\/p>\n<p>Danke f\u00fcr die Frage. Unsere Nutzer stellen sie gelegentlich. Sie fragen, wann wir die Unterst\u00fctzung f\u00fcr Downsampling hinzuf\u00fcgen. Hier gibt es mehrere Probleme. Erstens versteht jeder Nutzer unter <code>Downsampling<\/code> etwas anderes: Manche w\u00fcnschen sich einen beliebigen Punkt im angegebenen Intervall, andere m\u00f6chten die maximalen, minimalen oder durchschnittlichen Werte. Wenn viele Systeme Daten in Ihre Datenbank schreiben, kann man sie nicht \u00fcber einen Kamm scheren. Es kann sein, dass f\u00fcr jedes System unterschiedliche Downsampling-Verfahren erforderlich sind. Und das ist kompliziert umzusetzen.<\/p>\n<p><\/p>\n<p>Und zweitens ist es so, dass VictoriaMetrics, genau wie ClickHouse, f\u00fcr die Verarbeitung gro\u00dfer Mengen Rohdaten optimiert ist. Daher kann es eine Milliarde Zeilen in weniger als einer Sekunde verarbeiten, wenn Sie viele Kerne in Ihrem System haben. Das Scannen von Zeitreihenpunkten in VictoriaMetrics betr\u00e4gt 50.000.000 Punkte pro Sekunde pro Kern. Diese Leistung skaliert mit der Anzahl der vorhandenen Kerne. Das hei\u00dft, wenn Sie zum Beispiel 20 Kerne haben, k\u00f6nnen Sie eine Milliarde Punkte pro Sekunde scannen. Diese Eigenschaft von VictoriaMetrics und ClickHouse verringert die Notwendigkeit f\u00fcr Downsampling.<\/p>\n<p><\/p>\n<p>Eine weitere Eigenschaft ist, dass VictoriaMetrics diese Daten effizient komprimiert. Die Kompression liegt im Durchschnitt im Produktivbetrieb zwischen 0,4 und 0,8 Byte pro Punkt. Jeder Punkt besteht aus einem Zeitstempel + Wert. Und er wird im Durchschnitt auf weniger als ein Byte komprimiert. <\/p>\n<p><\/p>\n<p><em>Sergey. Ich habe eine Frage. Wie gro\u00df ist das minimale Zeitquantum f\u00fcr die Speicherung?<\/em><\/p>\n<p><\/p>\n<p>Eine Millisekunde. K\u00fcrzlich hatten wir ein Gespr\u00e4ch mit anderen Entwicklern von Zeitreihendatenbanken. Bei ihnen liegt das minimale Zeitquantum bei einer Sekunde. Auch in Graphite betr\u00e4gt es beispielsweise eine Sekunde. In OpenTSDB ebenfalls eine Sekunde. In InfluxDB gibt es nanosekundengenaue Genauigkeit. In VictoriaMetrics betr\u00e4gt es eine Millisekunde, weil es in Prometheus eine Millisekunde betr\u00e4gt. VictoriaMetrics wurde urspr\u00fcnglich als Remote-Speicher f\u00fcr Prometheus entwickelt. Aber jetzt kann es auch Daten aus anderen Systemen speichern. <\/p>\n<p><\/p>\n<p>Die Person, mit der ich gesprochen habe, sagt, dass sie eine sek\u00fcndliche Genauigkeit haben \u2013 das gen\u00fcgt ihnen, da dies von der Art der Daten abh\u00e4ngt, die in die Zeitreihe gespeichert werden. Wenn es sich um DevOps-Daten oder Daten von Infrastrukturen handelt, bei denen Sie sie alle 30 Sekunden oder einmal pro Minute sammeln, dann reicht eine sek\u00fcndliche Genauigkeit aus, weniger ist nicht n\u00f6tig. Wenn Sie jedoch diese Daten von Hochfrequenzhandelsystemen sammeln, ben\u00f6tigen Sie nanosekundengenaue Genauigkeit.<\/p>\n<p><\/p>\n<p>Millisekunden-Genauigkeit in VictoriaMetrics eignet sich sowohl f\u00fcr DevOps-F\u00e4lle als auch f\u00fcr die meisten der F\u00e4lle, die ich zu Beginn des Vortrags erw\u00e4hnt habe. Das Einzige, wof\u00fcr sie m\u00f6glicherweise nicht geeignet ist, sind Hochfrequenzhandelssysteme.<\/p>\n<p><\/p>\n<p><em>Danke! Und eine weitere Frage. Wie ist die Kompatibilit\u00e4t in PromQL?<\/em><\/p>\n<p><\/p>\n<p>Vollst\u00e4ndige Abw\u00e4rtskompatibilit\u00e4t. VictoriaMetrics unterst\u00fctzt PromQL vollst\u00e4ndig. Dar\u00fcber hinaus f\u00fcgt es zus\u00e4tzliche erweiterte Funktionen zu PromQL hinzu, die <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/VictoriaMetrics\/VictoriaMetrics\/wiki\/MetricsQL\">MetricsQL<\/a><\/noindex>hei\u00dfen. Zu dieser erweiterten Funktionalit\u00e4t gibt es einen Vortrag auf YouTube. Ich habe beim Monitoring Meetup im Fr\u00fchling in St. Petersburg dar\u00fcber gesprochen.<\/p>\n<p><\/p>\n<p>Telegram-Kanal <noindex><a rel=\"nofollow\" href=\"https:\/\/t.me\/VictoriaMetrics_ru1\">VictoriaMetrics<\/a><\/noindex>.<\/p>\n<p class=\"for_users_only_msg\">Nur registrierte Benutzer k\u00f6nnen an der Umfrage teilnehmen. <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/auth\/login\/\">Bitte einloggen<\/a><\/noindex>.<\/p>\n<h2 class=\"default-block__polling-title\">Was hindert Sie daran, VictoriaMetrics als langfristigen Speicher f\u00fcr Prometheus zu verwenden? (Schreiben Sie in die Kommentare, ich f\u00fcge es zur Umfrage hinzu))<\/h2>\n<ul class=\"poll-result\">\n<li class=\"poll-result__item\">\n<p>                <strong class=\"poll-result__data-percent  poll-result__data-percent_winner\">71,4%<\/strong>Nutze Prometheus5 nicht<\/p>\n<\/li>\n<li class=\"poll-result__item\">\n<p>                <strong class=\"poll-result__data-percent\">28,6%<\/strong>Wusste nichts \u00fcber VictoriaMetrics2<\/p>\n<\/li>\n<\/ul>\n<p>    7 Benutzer haben abgestimmt. 12 Benutzer haben sich enthalten.<br \/>\n<br \/>Quelle: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/post\/500844\/\">habr.com<\/a> <\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u041f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u044e \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u0438\u0442\u044c\u0441\u044f \u0441 \u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u043a\u043e\u0439 \u0434\u043e\u043a\u043b\u0430\u0434\u0430 \u043a\u043e\u043d\u0446\u0430 2019 \u0433\u043e\u0434\u0430 \u0410\u043b\u0435\u043a\u0441\u0430\u043d\u0434\u0440\u0430 \u0412\u0430\u043b\u044f\u043b\u043a\u0438\u043d\u0430 &quot;Go optimizations in VictoriaMetrics&quot; VictoriaMetrics \u2014 \u0431\u044b\u0441\u0442\u0440\u0430\u044f \u0438 \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u0443\u0435\u043c\u0430\u044f \u0421\u0423\u0411\u0414 \u0434\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u0432 \u0444\u043e\u0440\u043c\u0435 \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0433\u043e \u0440\u044f\u0434\u0430 (\u0437\u0430\u043f\u0438\u0441\u044c \u043e\u0431\u0440\u0430\u0437\u0443\u0435\u0442 \u0432\u0440\u0435\u043c\u044f \u0438 \u043d\u0430\u0431\u043e\u0440 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0445 \u044d\u0442\u043e\u043c\u0443 \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0445 \u0447\u0435\u0440\u0435\u0437 \u043f\u0435\u0440\u0438\u043e\u0434\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u043e\u043f\u0440\u043e\u0441 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0434\u0430\u0442\u0447\u0438\u043a\u043e\u0432 \u0438\u043b\u0438 \u0441\u0431\u043e\u0440 \u043c\u0435\u0442\u0440\u0438\u043a). \u0412\u043e\u0442 \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u0432\u0438\u0434\u0435\u043e \u044d\u0442\u043e\u0433\u043e \u0434\u043e\u043a\u043b\u0430\u0434\u0430 \u2014 [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":80734,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-80733","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.1.1 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u041f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u044e \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u0438\u0442\u044c\u0441\u044f \u0441 \u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u043a\u043e\u0439 \u0434\u043e\u043a\u043b\u0430\u0434\u0430 \u043a\u043e\u043d\u0446\u0430 2019 \u0433\u043e\u0434\u0430 \u0410\u043b\u0435\u043a\u0441\u0430\u043d\u0434\u0440\u0430 \u0412\u0430\u043b\u044f\u043b\u043a\u0438\u043d\u0430 &quot;Go optimizations in VictoriaMetrics&quot;\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/de\/blog\/administrirovanie\/go-optimizations-in-victoriametrics-aleksandr-valyalkin\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.1.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"de_DE\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47Go optimizations in VictoriaMetrics. \u0410\u043b\u0435\u043a\u0441\u0430\u043d\u0434\u0440 \u0412\u0430\u043b\u044f\u043b\u043a\u0438\u043d | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u041f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u044e \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u0438\u0442\u044c\u0441\u044f \u0441 \u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u043a\u043e\u0439 \u0434\u043e\u043a\u043b\u0430\u0434\u0430 \u043a\u043e\u043d\u0446\u0430 2019 \u0433\u043e\u0434\u0430 \u0410\u043b\u0435\u043a\u0441\u0430\u043d\u0434\u0440\u0430 \u0412\u0430\u043b\u044f\u043b\u043a\u0438\u043d\u0430 &quot;Go optimizations in VictoriaMetrics&quot;\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/de\/blog\/administrirovanie\/go-optimizations-in-victoriametrics-aleksandr-valyalkin\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2020-05-08T11:42:31+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2020-05-08T11:42:31+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47Go-Optimierungen in VictoriaMetrics. Alexander Valialkin | ProHoster","description":"Ich lade Sie ein, sich mit der Transkription des Vortrags von Alexander Valjalkin aus dem Ende des Jahres 2019 \"Go-Optimierungen in VictoriaMetrics\" vertraut zu machen.","canonical_url":"https:\/\/prohoster.info\/de\/blog\/administrirovanie\/go-optimizations-in-victoriametrics-aleksandr-valyalkin","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"de_DE","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47Go optimizations in VictoriaMetrics. \u0410\u043b\u0435\u043a\u0441\u0430\u043d\u0434\u0440 \u0412\u0430\u043b\u044f\u043b\u043a\u0438\u043d | ProHoster","og:description":"\u041f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u044e \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u0438\u0442\u044c\u0441\u044f \u0441 \u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u043a\u043e\u0439 \u0434\u043e\u043a\u043b\u0430\u0434\u0430 \u043a\u043e\u043d\u0446\u0430 2019 \u0433\u043e\u0434\u0430 \u0410\u043b\u0435\u043a\u0441\u0430\u043d\u0434\u0440\u0430 \u0412\u0430\u043b\u044f\u043b\u043a\u0438\u043d\u0430 &quot;Go optimizations in VictoriaMetrics&quot;","og:url":"https:\/\/prohoster.info\/de\/blog\/administrirovanie\/go-optimizations-in-victoriametrics-aleksandr-valyalkin","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2020-05-08T11:42:31+00:00","article:modified_time":"2020-05-08T11:42:31+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"80733","title":null,"description":null,"keywords":null,"keyphrases":null,"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":null,"breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-02-28 16:12:22","updated":"2022-09-29 05:16:51","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/posts\/80733","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/comments?post=80733"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/posts\/80733\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/media\/80734"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/media?parent=80733"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/categories?post=80733"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/tags?post=80733"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}