Splunk: Suchen verstehen mit dem Job Inspector – Episode II: Arbeitsteilung
Jacob Brügmann & Martin Müller / 15.08.22 / Digitale Transformation
Im März 2022 schrieben wir eine https://www.consist.de/de/unternehmen/blog/artikel/Splunk-Suchen-verstehen-mit-dem-Job-Inspector/, die wir allen ohne Vorkenntnisse wärmstens ans Herz legen. In Episode II bauen wir darauf auf und beleuchten weitere Aspekte der Suchausführung.
Arbeitsteilung zwischen Search Head und Indexern verstehen
Splunk-Installationen werden verteilt aufgebaut, um entsprechend des Datenvolumens und der Suchlast horizontal skalieren zu können. Aus Sicht einer einzelnen Suche sind dabei der eigene Search Head (SH) sowie alle per Distributed Search verbundenen Indexer (IDX) relevant. Ein möglicherweise vorhandenes Clustering auf Search-Head- oder Indexer-Ebene kann hier ignoriert werden, da dies auf die Ausführung einer einzelnen Suche keinen Einfluss hat.
Reduziert auf diese Kernkomponenten sieht eine Architektur mit verteilter Suche wie folgt aus:
Der Search Head stellt die Benutzeroberfläche bereit und speichert die zur Suchzeit angewendeten Konfigurationen (z. B. Zugriffsrechte oder Feldextraktionen), die Indexer speichern jeweils einen möglichst gleich verteilten Bruchteil der zu durchsuchenden Daten. Das verteilte Durchsuchen und darauffolgende Zusammenführen zu einem Ergebnis erfolgt dabei analog zu MapReduce.
Wird eine Suche ausgeführt, verteilen sich die Aufgaben in etwa wie folgt:
- SH: Vorarbeiten durchführen (z. B. Nutzerrechte prüfen) und Suche den IDX übergeben
- IDX: Map-Schritt durchführen, d. h. die benötigten Events finden, aufbereiten (Feldextraktionen, Lookups etc.) und Zwischenergebnisse berechnen
- SH: Reduce-Schritt durchführen, d. h. die Zwischenergebnisse aller Indexer zusammenbringen und ggf. weitere Berechnungsschritte ausführen (z. B. Umformatieren)
Hieraus ergibt sich direkt die beste Stellschraube, wie Suchen durch „Hardware-auf-das-Problem-werfen“ beschleunigt werden können: Verdopple ich die Anzahl meiner Indexer, muss jeder Indexer nur noch die Hälfte seiner vorherigen Datenmenge durchsuchen und wird entsprechend nur etwa die Hälfte der Zeit brauchen.
Ohne mehr Hardware wird eine Suche schneller, wenn ein möglichst großer Teil der Arbeit im Map-Schritt auf den Indexern durchgeführt wird. Auf dieser Ebene existieren horizontal skaliert viele Maschinen, der Search Head steht alleine da. (Notiz zum Search-Head-Cluster: Horizontale Skalierung per SHC kann für viele gleichzeitige Nutzer und Suchen mehr Kapazität bereitstellen, jede einzelne Suche läuft aber weiterhin auf genau einem SH – hilft also nicht.)
Wie finde ich heraus, welcher Teil einer Suche auf den Indexern bzw. dem Search Head ausgeführt wird? Wo wird zwischen Map und Reduce geschnitten? Dies ist die Kurve zurück zum Job Inspector!
Arbeitsteilung zwischen Search Head und Indexern bestimmen
Das kleinste Beispiel hierfür ist folgende Suche:
index=main | stats count
Manuell lässt sich der Schnitt zwischen Map und Reduce schlussfolgern: Jeder Indexer durchsucht seinen Teil des Indexes main, zählt Events und liefert diese Zwischensumme zum Search Head. Dort muss nur noch die Summe über alle Indexer gebildet werden.
Definitive Aussagen hierzu trifft – wie immer – der Job Inspector, konkret im Abschnitt Search Job Properties unter phase0 (Indexer) und phase1 (Search Head). Wenn die Splunk-Installation nur aus einer Maschine als kombinierter Search Head und Indexer besteht, sind diese Felder trotzdem vorhanden.
In phase0 stehen die erwarteten Dinge am Anfang und am Ende: Die Indexer durchsuchen den Index main und berechnen mit prestats ihr jeweiliges Zwischenergebnis. Die beiden dazwischen generierten Befehle addinfo und fields können in der Regel überlesen werden.
In phase1 werden die voraggregierten Ergebnisse des prestats genommen und vom stats zum Endergebnis aufsummiert.
Zur Gegenprobe kann die zwischen Indexern und Search Head übertragene Datenmenge herangezogen werden, die im oberen Abschnitt Execution Costs unter dispatch.stream.remote ganz rechts in byte dargestellt wird:
Die Suche betrachtete etwa 11 Millionen Events, es wurden in Summe aber nur 47,5 MB ausgetauscht. Wird durch groben Unfug in der Suche provoziert, dass alle Events von den Indexern zum Search Head geliefert werden, käme bei diesem Beispieldatensatz 3,8 GB Transfervolumen zustande – und entsprechend deutlich mehr Laufzeit.
In der Realität sind Suchen natürlich komplexer, die Arbeitsteilung ist nicht immer so einfach erkennbar. Nehmen wir an, wir wollten für Zugriffe auf unser Splunk prüfen, welche Client-Hosts vorkommen:
index=_internal sourcetype=splunkd_ui_access
| lookup dnslookup clientip OUTPUT clienthost
| fillnull value="unknown"
| stats count by clienthost
| sort - count
Logisch wäre hier zunächst folgender Ablauf:
- Index durchsuchen? Machen die Indexer.
- DNS-Lookup ausführen? Keine Beziehung zwischen Events, machen die Indexer.
- Fehlende Werte auffüllen? Keine Beziehung zwischen Events, machen die Indexer.
- Statistik berechnen? Wie oben, Indexer berechnen Zwischenergebnisse und liefern an den Search Head zum Aufsummieren.
- Sortieren? Macht der Search Head, nur hier sind die Summen bekannt.
Richtig?
Falsch. Definitive Aussagen trifft der Job Inspector:
phase0: litsearch (index=_internal sourcetype=splunkd_ui_access) | lookup dnslookup clientip OUTPUT clienthost | fields [...]
phase1: fillnull value="unknown" | addinfo [...] | stats count by clienthost | presort 10000 -auto(count) | sort - count
Warum konnte fillnull nicht wie erwartet auf den Indexern ausgeführt werden? Ohne angegebene Felder, die aufzufüllen sind, werden alle Felder aufgefüllt… über alle Events hinweg. Da ein Event 1 auf Indexer A nicht weiß, welche Felder in Event 2 auf Indexer B vorhanden sein könnten, kann diese Variante von fillnull nur zentral auf dem Search Head ausgeführt werden.
Abhilfe schafft hier, das zu füllende Feld clienthost explizit anzugeben. Dann weiß jeder Indexer Bescheid und kann für sich den Befehl ausführen.
Im Allgemeinen ist dies der Unterschied zwischen distributable streaming und centralized streaming: Beide Befehlsklassen können Schritt für Schritt durch die Events laufen (streaming), unterscheiden sich aber im dabei benötigten Wissen. Distributable benötigt kein Wissen aus anderen Events, jedes kann für sich genommen verteilt verarbeitet werden. Eine Beschreibung und ausführliche Liste mit Ausnahmen findet sich in der Splunk-Dokumentation.
Zurück zum letzten Beispiel: Wirklich schnell lief die Suche auch dann nicht, wenn der Fauxpas im fillnull korrigiert ist. Im Job Inspector verrät die Tabelle der Ausführungskosten, wo die Zeit geblieben ist:
Wir führen 348.178 DNS-Lookups aus! Das dauert 372 Sekunden verteilt über mehrere Indexer und nimmt damit den größten Teil der Gesamtlaufzeit ein.
Entgegen der ersten Intuition “möglichst viel auf die Indexer verteilen” ist dies in diesem Beispiel ungeschickt. Wir können zwar um den Faktor Indexer-Anzahl schneller werden, müssen dafür aber jedes Event einzeln verarbeiten. Kommt eine Client-IP tausend Mal vor, schlagen wir sie auch tausend Mal nach. Trotz DNS-Caching wird das Zeit kosten.
Das stats dedupliziert die Clients ohnehin, daher können wir den DNS-Lookup wie folgt verschieben:
index=_internal sourcetype=splunkd_ui_access
| fillnull value="unknown" clientip
| stats count by clientip
| lookup dnslookup clientip OUTPUT clienthost
| fillnull value="unknown"
| stats sum(count) as count by clienthost
| sort - count
Die Indexer arbeiten bis inklusive erstes stats, der Search Head ab inklusive erstes stats – ein Blick in den Job Inspector bestätigt dies. Die DNS-Lookups passieren jetzt zwar sequentiell auf einer Maschine, dafür wird jede IP nur einmal nachgeschlagen. In unserer Umgebung lief die Suche dadurch mehr als doppelt so schnell und hat nur noch rund 500 DNS-Anfragen gestellt. Die DNS-Admins freut’s!
Falls sich jemand fragt, ob das zweite fillnull ohne Beschränkung auf clientip doof ist: Jein. An dieser Stelle läuft die Suche bereits zentral auf dem Search Head und die Menge aller Felder ist bekannt. Entsprechend richtet das Weglassen hier keinen Schaden an.
Parallel Reduce
Weil zwei MapReduce-Phasen nicht kompliziert genug sind, kann es bei hinreichend großen und entsprechend konfigurierten Splunk-Installationen eine dritte Phase zwischen Map (Indexer) und Reduce (Search Head) geben, das Intermediate Reduce. Für die letzte Suche sehen die drei Phasen dann so aus:
Am Ende von phase0 liefern die Indexer nun je einen Bruchteil an zufällig ausgewählte Indexer (hier 4), die das Berechnen der nach clientip gruppierten Summen in der neuen phase1 übernehmen. Die beiden Befehle rdout und rdin entsprechen dabei der internen Arbeitsteilung des implizit eingefügten Befehls redistribute. Falls clientip sehr viele unterschiedliche Werte hat, beschleunigt dies den Reduce-Schritt deutlich. Der Rest der Berechnung ab lookup läuft wie gewohnt auf dem Search Head.
Wie Intermediate Reduce konfiguriert und der größte Nutzen daraus gezogen wird, ist ein Artikel für sich.
Weitere Informationen
Episode I zum Job Inspector: https://www.consist.de/de/unternehmen/blog/artikel/Splunk-Suchen-verstehen-mit-dem-Job-Inspector/
Splunk-Dokumentation zur verteilten Suche: https://docs.splunk.com/Documentation/Splunk/9.0.0/DistSearch/Whatisdistributedsearch
MapReduce-Algorithmus: https://de.wikipedia.org/wiki/MapReduce
Dokumentation der Suchbefehl-Typen: https://docs.splunk.com/Documentation/Splunk/9.0.0/Search/Typesofcommands
Referenz der Suchbefehl-Typen: https://docs.splunk.com/Documentation/Splunk/9.0.0/SearchReference/Commandsbytype
Parallel Reduce: https://docs.splunk.com/Documentation/Splunk/9.0.0/DistSearch/Parallelreduceoverview
Splunk-Dokumentation des Job Inspector: https://docs.splunk.com/Documentation/Splunk/9.0.0/Search/ViewsearchjobpropertieswiththeJobInspector
Vortrag auf der .conf20 “Clara-fication: Job Inspector” von Clara Merriman und Martin Müller
Folien: https://conf.splunk.com/files/2020/slides/TRU1143C.pdf
Aufnahme: https://conf.splunk.com/files/2020/recordings/TRU1143C.mp4
Splunk-Blog mit weiteren Beispielen: https://www.splunk.com/en_us/blog/tips-and-tricks/splunk-clara-fication-job-inspector.html