Suche mit einer Geschwindigkeit von 1 TB/s

TL;DR: Vor vier Jahren verließ ich Google mit der Idee eines neuen Tools zur Überwachung von Servern. Die Idee bestand darin, normalerweise isolierte Funktionen in einem einzigen Dienst zu kombinieren. Datensammlung und Analyse von Protokollen, Sammlung von Metriken, Alarmierung und Dashboards. Ein Prinzip ist, dass der Dienst wirklich schnell, so dass DevOps-Teams eine einfache, interaktive und angenehme Erfahrung haben. Das erfordert die Verarbeitung von Datensätzen mit mehreren Gigabyte in Bruchteilen von Sekunden, ohne das Budget zu sprengen. Bestehende Tools zur Protokollbearbeitung sind oft langsam und ungeschickt, weshalb wir mit einer hervorragenden Herausforderung konfrontiert waren: ein Tool clever zu entwickeln, das den Benutzern ein neues Erlebnis bietet.

In diesem Artikel beschreiben wir, wie wir bei Scalyr dieses Problem gelöst haben, indem wir altbewährte Methoden angewendet, brute-force Ansätze genutzt und überflüssige Schichten eliminiert sowie komplexe Datenstrukturen vermieden haben. Diese Lektionen können Sie auf Ihre eigenen technischen Herausforderungen anwenden.

Die Kraft der alten Schule

Die Analyse von Protokollen beginnt normalerweise mit der Suche: Alle Nachrichten finden, die einem bestimmten Muster entsprechen. In Scalyr handelt es sich um Dutzende oder Hunderte von Gigabyte an Protokollen von vielen Servern. Moderne Ansätze beinhalten oft den Aufbau einer komplexen Datenstruktur, die für die Suche optimiert ist. Ich habe so etwas natürlich bei Google gesehen, wo sie in solchen Angelegenheiten recht gut sind. Aber wir haben uns für einen viel einfacheren Ansatz entschieden: die lineare Durchsuchung der Protokolle. Und das hat funktioniert – wir bieten eine Benutzeroberfläche mit einer Suchgeschwindigkeit, die um ein Vielfaches schneller ist als die unserer Wettbewerber (siehe die Animation am Ende).

Die entscheidende Erkenntnis war, dass moderne Prozessoren in einfachen, geradlinigen Operationen tatsächlich sehr schnell sind. Dies kann leicht in komplexen, mehrschichtigen Systemen übersehen werden, die von der I/O-Geschwindigkeit und Netzwerkoperationen abhängen – und solche Systeme sind heute weit verbreitet. Daher haben wir ein Design entwickelt, das die Anzahl der Schichten und überflüssigen Ballast minimiert. Mit mehreren Prozessoren und Servern gleichzeitig erreicht die Suchgeschwindigkeit 1 TB pro Sekunde.

Wesentliche Erkenntnisse aus diesem Artikel:

  • Brute-Force-Suche ist ein durchaus gangbarer Ansatz zur Lösung realer, groß angelegter Probleme.
  • Brute-Force ist eine Designtechnik und nicht eine Ausrede für Faulheit. Wie jede Technik ist sie für manche Probleme besser geeignet als für andere und kann schlecht oder gut umgesetzt werden.
  • Brute-Force ist besonders gut geeignet, um stabile Leistung zu erzielen.
  • Eine effiziente Nutzung von Brute-Force erfordert die Optimierung des Codes und den rechtzeitigen Einsatz ausreichender Ressourcen. Sie ist geeignet, wenn Ihre Server unter hoher Belastung stehen, die nicht benutzergeneriert ist, und benutzergenerierte Vorgänge Vorrang haben.
  • Die Leistung hängt vom Design des gesamten Systems und nicht nur vom Algorithmus des inneren Zyklus ab.

(In diesem Artikel wird die Datensuche im Speicher beschrieben. In den meisten Fällen haben die Scalyr-Server die Protokolle bereits zwischengespeichert, wenn ein Benutzer eine Suche durchführt. Im nächsten Artikel werden wir die Suche in nicht zwischengespeicherten Protokollen besprechen. Die gleichen Prinzipien gelten: effizienter Code, Brute-Force-Methodik mit umfangreichen Rechenressourcen).

Die Brute-Force-Methode

Traditionell erfolgt die Suche in großen Datensätzen über einen Keyword-Index. Im Kontext von Server-Logs bedeutet dies, dass jedes einzigartige Wort im Protokoll gesucht wird. Für jedes Wort wird eine Liste aller Vorkommen erstellt. Dadurch können alle Nachrichten mit diesem Wort, wie z.B. 'error', 'firefox' oder 'transaction_16851951', leicht gefunden werden – einfach im Index nachsehen.

Ich habe diesen Ansatz bei Google verwendet, und er hat gut funktioniert. Aber bei Scalyr durchsuchen wir die Logs Byte für Byte.

Warum? Aus einer abstrakten algorithmischen Sicht sind Keyword-Indizes viel effizienter als eine grobe Suche. Wir verkaufen jedoch keine Algorithmen, sondern Leistung. Und Leistung umfasst nicht nur Algorithmen, sondern auch Systemtechnik. Wir müssen alles berücksichtigen: Datenvolumen, Suchtyp, verfügbare Hardware und den programmatischen Kontext. Für unser spezifisches Problem haben wir entschieden, dass eine Option wie 'grep' besser geeignet ist als ein Index.

Indizes sind großartig, aber sie haben ihre Grenzen. Ein einzelnes Wort zu finden, ist einfach. Die Suche nach Nachrichten mit mehreren Wörtern, wie 'googlebot' und '404', ist jedoch viel schwieriger. Die Suche nach einer Phrase wie 'uncaught exception' erfordert einen komplizierteren Index, der nicht nur alle Nachrichten mit diesem Wort erfasst, sondern auch den genauen Standort des Wortes.

Die wahre Herausforderung tritt auf, wenn Sie nach nicht-Wörtern suchen. Angenommen, Sie möchten herausfinden, wie viel Traffic von Bots kommt. Der erste Gedanke ist, in den Logs nach dem Wort 'bot' zu suchen. So finden Sie einige Bots: Googlebot, Bingbot und viele andere. Aber hier ist 'bot' kein Wort, sondern ein Teil davon. Wenn wir 'bot' im Index suchen, finden wir keine Nachrichten mit dem Wort 'Googlebot'. Wenn wir jedes Wort im Index überprüfen und dann den Index nach den gefundenen Keywords durchsuchen, wird die Suche stark verlangsamt. Infolgedessen erlauben einige Log-Analyse-Programme keine Suche nach Wortteilen oder (im besten Fall) unterstützen sie eine spezielle Syntax mit geringerer Leistung. Das wollen wir vermeiden.

Ein weiteres Problem ist die Interpunktion. Möchten Sie alle Anfragen von 50.168.29.7? Что насчёт отладки логов, содержащих [error]? Индексы обычно пропускают пунктуацию.

Letztendlich schätzen Ingenieure leistungsstarke Werkzeuge, und manchmal kann ein Problem nur mit regulären Ausdrücken gelöst werden. Ein Schlüsselwortindex ist dafür eher ungeeignet.

Darüber hinaus sind Indizes komplex. Jede Nachricht muss in mehrere Schlüsselwortlisten aufgenommen werden. Diese Listen sollten immer in einem durchsuchbaren Format gehalten werden. Anfragen mit Phrasen, Wortfragmenten oder regulären Ausdrücken müssen in Operationen mit mehreren Listen übersetzt werden, und die Ergebnisse sollten gescannt und zusammengeführt werden, um eine resultierende Menge zu erhalten. In der Kontext einer groß angelegten Mehrbenutzerdienste schafft diese Komplexität Leistungsprobleme, die bei der Analyse von Algorithmen nicht sichtbar sind.

Schlüsselwortindizes benötigen zudem viel Speicherplatz, und Speicherkapazität ist ein wesentlicher Kostenfaktor im Log-Management-System.

Auf der anderen Seite kann jede Suche viel Rechenleistung erfordern. Unsere Nutzer schätzen die Hochgeschwindigkeitssuche für spezielle Anfragen, aber solche Anfragen kommen relativ selten vor. Für typische Suchanfragen, wie zum Beispiel für das Dashboard, verwenden wir spezielle Techniken (die wir im nächsten Artikel näher erläutern werden). Andere Anfragen sind recht selten, sodass es selten vorkommt, mehr als eine gleichzeitig zu verarbeiten. Das bedeutet jedoch nicht, dass unsere Server nicht beschäftigt sind: Sie sind stark belastet mit dem Empfangen, Analysieren und Komprimieren neuer Nachrichten, der Bewertung von Benachrichtigungen, der Komprimierung alter Daten und so weiter. So verfügen wir über einen erheblichen Pool von Prozessoren, die zur Bearbeitung von Anfragen eingesetzt werden können.

Brute Force funktioniert, wenn Sie ein grobes Problem haben (und viel Kraft).

Brute Force ist am effektivsten bei einfachen Aufgaben mit kleinen internen Schleifen. Oft können Sie die innere Schleife optimieren, um sehr hohe Geschwindigkeiten zu erreichen. Wenn der Code komplex ist, wird es viel schwieriger, ihn zu optimieren.

Ursprünglich hatte unser Code für die Suche eine recht große interne Schleife. Wir speichern Nachrichten auf 4K-Seiten; jede Seite enthält einige Nachrichten (in UTF-8) und Metadaten für jede Nachricht. Die Metadaten sind eine Struktur, die Länge des Wertes, die interne Nachrichten-ID und andere Felder codiert. Die Suchschleife sah folgendermaßen aus:

Suche mit einer Geschwindigkeit von 1 TB/s

Dies ist eine vereinfachte Version im Vergleich zum tatsächlichen Code. Aber auch hier sind mehrere Objektplatzierungen, Datenkopien und Funktionsaufrufe sichtbar. Die JVM optimiert Funktionsaufrufe recht gut und verwaltet flüchtige Objekte, daher funktionierte dieser Code besser als erwartet. Während der Tests hatten die Kunden recht erfolgreich damit gearbeitet. Letztendlich haben wir aber einen neuen Stand erreicht.

(Sie fragen sich vielleicht, warum wir Nachrichten in diesem Format mit 4K-Seiten, Text und Metadaten speichern, anstatt direkt mit Logs zu arbeiten. Es gibt viele Gründe, die darauf hinauslaufen, dass die interne Struktur von Scalyr eher einer verteilten Datenbank als einem Dateisystem ähnelt. Textsuche wird häufig mit Datenbankähnlichen Filtern auf Feldern nach der Protokollanalyse kombiniert. Wir können gleichzeitig in vielen Tausend Logs suchen, und einfache Textdateien eignen sich nicht für unser transaktionales, repliziertes, verteiltes Datenmanagement.)

Ursprünglich schien dieser Code nicht ideal für die Optimierung durch Brute-Force-Methoden geeignet zu sein. „Echte Arbeit“ in String.indexOf() spielerisch nicht einmal im CPU-Profil dominant zu sein. Das heißt, die Optimierung nur dieser Methode würde keinen signifikanten Effekt haben.

Es stellt sich heraus, dass wir Metadaten am Anfang jeder Seite speichern und der Text aller Nachrichten in UTF-8 am anderen Ende verpackt ist. Das haben wir genutzt, um die Schleife zur gleichzeitigen Suche über die gesamte Seite neu zu schreiben:

Suche mit einer Geschwindigkeit von 1 TB/s

Diese Version arbeitet direkt auf der Darstellung raw byte[] und sucht alle Nachrichten gleichzeitig auf der gesamten 4K-Seite.

Es ist viel einfacher, es für die Brute-Force-Methode zu optimieren. Die interne Suchschleife wird gleichzeitig für die gesamte 4K-Seite aufgerufen, anstatt sie einzeln für jede Nachricht auszuführen. Es gibt weder Datenkopien noch Objektzuweisungen. Darüber hinaus werden komplexere Metadatenoperationen nur bei positiven Ergebnissen und nicht für jede Nachricht ausgeführt. So haben wir eine Menge Overhead ausgeschlossen, und die restliche Belastung konzentriert sich in einer kleinen internen Suchschleife, die sich gut für weitere Optimierungen eignet.

Unser effektiver Suchalgorithmus basiert auf einer hervorragenden Idee von Leonid Wolnicki. Er ähnelt dem Boyer-Moore-Algorithmus mit einem Sprung von etwa der Länge der Suchzeichenfolge in jedem Schritt. Der Hauptunterschied besteht darin, dass er zwei Bytes gleichzeitig überprüft, um falsche Übereinstimmungen zu minimieren.

Unsere Implementierung erfordert für jede Suche die Erstellung einer 64K-Suchttabelle, doch das ist unerheblich im Vergleich zu den Gigabytes an Daten, die wir durchforsten. Der innere Zyklus verarbeitet mehrere Gigabytes pro Sekunde auf einem Kern. Praktisch erreichen wir eine stabile Leistung von etwa 1,25 GB pro Sekunde pro Kern, und es gibt Potenzial für Verbesserungen. Einige Overheads außerhalb des inneren Zyklus können reduziert werden, und wir planen, mit dem inneren Zyklus in C anstelle von Java zu experimentieren.

Die Kraft anwenden

Wir haben besprochen, dass die Suche in Logs „grob“ implementiert werden kann, aber wie viel „Kraft“ haben wir tatsächlich? Nicht wenig.

1 Kern: Bei richtiger Nutzung ist ein Kern eines modernen Prozessors allein schon recht leistungsstark.

8 Kerne: Momentan nutzen wir die SSD-Server Amazon hi1.4xlarge und i2.4xlarge, die jeweils über 8 Kerne (16 Threads) verfügen. Wie bereits erwähnt, sind diese Kerne normalerweise mit Hintergrundoperationen beschäftigt. Wenn ein Benutzer eine Suche durchführt, werden die Hintergrundoperationen angehalten, sodass alle 8 Kerne für die Suche zur Verfügung stehen. Die Suche wird normalerweise in Bruchteilen einer Sekunde abgeschlossen, danach werden die Hintergrundarbeiten wieder aufgenommen (ein Steuerprogramm gewährleistet, dass eine Flut von Suchanfragen die wichtigen Hintergrundarbeiten nicht stört).

16 Kerne: Zur Gewährleistung der Zuverlässigkeit organisieren wir die Server in Master-/Slave-Gruppen. Jeder Master hat einen SSD- und einen EBS-Server unter sich. Wenn der Hauptserver ausfällt, übernimmt der SSD-Server sofort dessen Platz. Fast immer arbeiten Master und Slave ordnungsgemäß, sodass jeder Datenblock auf zwei verschiedenen Servern für die Suche verfügbar ist (der Slave-Server EBS hat einen schwachen Prozessor, weshalb wir ihn nicht berücksichtigen). Wir teilen die Aufgabe zwischen ihnen auf, sodass uns insgesamt 16 Kerne zur Verfügung stehen.

viele Kerne: In naher Zukunft werden wir die Daten so auf Server verteilen, dass alle an der Verarbeitung jeder nicht trivialen Anfrage beteiligt sind. Jedes Kern wird aktiv sein. [Hinweis: wir haben den Plan umgesetzt und die Suchgeschwindigkeit auf bis zu 1 TB/s erhöht, siehe Hinweis am Ende des Artikels].

Einfachheit sorgt für Zuverlässigkeit

Ein weiterer Vorteil der Brute-Force-Methode ist die relativ stabile Leistung. Im Allgemeinen ist die Suche nicht allzu empfindlich gegenüber den Details der Aufgabe und des Datensatzes (ich denke, das ist der Grund, warum sie als 'brutal' bezeichnet wird).

Der Schlüsselwortindex liefert manchmal unglaublich schnelle Ergebnisse, in anderen Fällen jedoch nicht. Angenommen, Sie haben 50 GB an Logs, in denen der Begriff 'customer_5987235982' genau dreimal vorkommt. Die Suche nach diesem Begriff zählt direkt aus dem Index drei Standorte und ist sofort abgeschlossen. Aber eine komplexe Suche mit Platzhaltern kann Tausende von Schlüsselwörtern durchsuchen und viel Zeit in Anspruch nehmen.

Auf der anderen Seite wird die Brute-Force-Suche für jede Anfrage mit mehr oder weniger gleichbleibender Geschwindigkeit durchgeführt. Die Suche nach langen Wörtern funktioniert besser, aber selbst die Suche nach einem einzelnen Zeichen erfolgt recht schnell.

Die Einfachheit der Brute-Force-Methode bedeutet, dass ihre Leistung nahe am theoretischen Maximum liegt. Hier gibt es weniger Möglichkeiten für unerwartete Überlastungen der Festplatten, Konflikte bei Sperren, Zeigerjagden und tausend andere Ursachen für Systemausfälle. Ich habe mir gerade die Anfragen angesehen, die letzte Woche von Scalyr-Nutzern auf unserem meistbeschäftigten Server gestellt wurden. Es gab 14.000 Anfragen. Genau acht davon benötigten mehr als eine Sekunde; 99 % wurden innerhalb von 111 Millisekunden bearbeitet (wenn Sie keine Log-Analyse-Tools verwendet haben, glauben Sie mir: das ist schnell).

Eine stabile, zuverlässige Leistung ist wichtig für die Benutzerfreundlichkeit des Dienstes. Wenn er gelegentlich langsamer wird, werden die Nutzer ihn als unzuverlässig empfinden und zögern, ihn zu nutzen.

Die Suche in Logs in Aktion

Hier ist eine kleine Animation, die die Scalyr-Suche in Aktion zeigt. Wir haben ein Demokonto, in das wir jedes Ereignis aus jedem öffentlichen Github-Repository importieren. In dieser Demonstration analysiere ich die Daten der letzten Woche: etwa 600 MB unverarbeiteter Protokolle.

Das Video wurde live, ohne spezielle Vorbereitung, auf meinem Desktop aufgenommen (etwa 5000 Kilometer vom Server entfernt). Die Leistung, die Sie sehen werden, ist zum großen Teil auf die Optimierung des Webclients, sowie auf ein schnelles und zuverlässiges Backend zurückzuführen. Jedes Mal, wenn es eine Pause ohne das ‚Loading‘-Indicator gibt, mache ich eine Pause, damit Sie lesen können, was ich gleich anklicken werde.

Suche mit einer Geschwindigkeit von 1 TB/s

Zusammenfassung

Bei der Verarbeitung großer Datenmengen ist es wichtig, einen guten Algorithmus zu wählen, doch „gut“ bedeutet nicht „aufwendig“. Überlegen Sie, wie Ihr Code in der Praxis funktionieren wird. In der theoretischen Analyse von Algorithmen bleiben einige Faktoren unbeachtet, die in der realen Welt von großer Bedeutung sein können. Einfachere Algorithmen lassen sich leichter optimieren und sind stabiler in Grenzsituationen.

Denken Sie auch an den Kontext, in dem der Code ausgeführt wird. In unserem Fall benötigen wir leistungsstarke Server, um Hintergrundaufgaben zu verwalten. Nutzer initiieren Suchen relativ selten, daher können wir eine ganze Gruppe von Servern kurzfristig nutzen, die für die Durchführung jeder Suche erforderlich ist.

Durch brute-force-Methoden haben wir eine schnelle, zuverlässige und flexible Suche über den Log-Satz implementiert. Wir hoffen, dass diese Ideen für Ihre Projekte nützlich sind.

Korrektur: Die Überschrift und der Text wurden von "Suche mit 20 GB pro Sekunde" auf "Suche mit 1 TB pro Sekunde" geändert, um die Leistungssteigerung der letzten Jahre widerzuspiegeln. Diese Geschwindigkeitssteigerung ist in erster Linie auf die Veränderung des Typs und der Anzahl der EC2-Server zurückzuführen, die wir heute zur Bedienung der wachsenden Kundenbasis hochfahren. In naher Zukunft erwarten wir Änderungen, die eine weitere drastische Steigerung der Effizienz ermöglichen, und wir freuen uns darauf, darüber berichten zu können.

Quelle: habr.com

Kaufen Sie zuverlässiges Hosting für Websites mit DDoS-Schutz, VPS VDS-Server 🔥 Kaufen Sie zuverlässiges Hosting für Websites mit DDoS-Schutz, VPS VDS-Server | ProHoster