Michael Schröpl: & (DBI::) & (mySQL): Performance-Messung (Teil 1)

Beitrag lesen

Hi Cheatah,

Nun ja... auf Lasttests, WebStress etc. brauche ich Dich vermutlich nicht hinzuweisen ;-) Im Zweifel sollte ein kleines Script, welches zufällige Kombinationen aus beispielhaften Werten bildet und diese als HTTP-Request absetzt, genügend Werte liefern. Für Dich sicher eine leichte Übung.

das Problem ist, daß die bisherige Implementierung so schlecht ist (und deshalb wenig verwendet wird) und daß die neue so viel mehr können wird (beispielsweise das Filtern nach Kriterien, welche das alte System gar nicht kennt), daß ich aus den Server-Logs keine reprä- sentativen Anforderungen für das neue System erzeugen kann. Ich habe keine Ahnung, was die Anwender mit der neuen Maschine tun werden ...

Das klingt fast so, als wäre die Statementgenerierung eher hysterisch gewachsen.

Gar nicht. ;-) Das Ding kann einfach nur viel ... stell Dir eine Self-Archivsuche vor, bei der Du für jedes einzelne Feld (Topic, Verfasser, Titel, Body, Datum) Eingabefelder (oder Dropdownlisten) hättest, also parallel nach allem Möglichen suchen und/oder filtern könntest, und für den Suchbegriff auch noch eines. Das kommt dann ziemlich gut hin.

Und die Vielzahl der Statements liegt im Wesentlichen an dem unten beschriebenen Cache-System, welches die Statements eben (bis zu) versechsfacht. Die Self-Suche muß nichts sortieren, die macht einen full table scan auf einer bereits richtig sortierten Liste - und kann abbrechen, wenn ihr Trefferlimit erreicht ist. Da ist der Code ziemlich trivial.

Aus eigener Erfahrung kann ich Dir empfehlen, noch mal darüber nachzudenken, eine glasklare Fallunterscheidung zu Papier zu bringen, mögliche Eingabetypkombinationen felsenfest zu definieren und daraus n eindeutig differenzierbare Statements zu hinterlegen. Wenn es beginnt unübersichtlich zu werden, hast Du praktisch schon verloren. Glaub mir, ich weiß wovon ich rede :-)

Ich habe meine Fallunterscheidungen - die sind auch ziemlich einfach. Es sind einfach nur relativ viele, wegen der vielen Tabellen - die sich aber spürbar lohnen. (Fünfstellig viele Zwischenergebnisse zu sortieren würde meine Maschine umbringen, und es gibt etliche sehr notwendige Suchbegriffe, die eben gerade so schlecht projezieren.

Ich habe eine Tabelle mit Nachrichten. (Sechs- bis siebenstellig viele, es könnte ein Gigabyte werden, vielleicht sogar mehr.) Sagt Dir ht://dig (htdig, ht/dig, ht-Dig, ...) etwas? Unter Umständen kann es Deine Performancebedenken ausräumen; auch wenn es natürlich völlig anders gehandhabt wird als ein DBMS.

Die Suche selbst mit mySQL-FULLTEXT ist so schnell, daß ich da nichts tunen muß. Nur das Sortieren ist teuer, weil es auf einem Objekt statt finden muß, das keinen Index über das Sortierkriterium mehr haben kann, weil es erst als Zwischenergebnis entsteht.

Hast Du Dir überlegt, redundante oder "überflüssige" Daten mit abzulegen, die Du programmlogisch vorberechnen kannst, um die Datenmenge pre-reduzieren zu können?

Einige der Tabellenfelder sind von meinem Importer berechnet worden

  • teilweise mit abenteuerlicher Kristallkugel-KI ... zur Treffer- ausgabe muß ich nur noch Feldwerte ablesen, nichts mehr umrechnen. Alles, was ich tun muß, ist durch die beschriebenen Phasen zur Mengenbeschränkung durchlaufen.

Der Speicherplatz scheint kein wesentlicher Faktor zu sein; und ein ideales DB-Layout ist nicht so wichtig wie die Performance.

Von Phase zu Phase werden im Wesentlichen fast alle Felder der Aus- gangstabelle durchgereicht, weil ich auch fast alle für die ver- schiedenen Phasen der Filterung brauche - und eine konfigurierbare Menge davon (noch einer der CGI-Parameter) für die Trefferanzeige.

Das einzige Feld, was definitiv nicht gebraucht wird, ist das deut- lich größte mit der Story drin - auf die greife ich nur in Filter- phase 1 zu, und hoffentlich fast immer über den FULLTEXT-Index. (Nämlich immer dann, wenn der Suchbegriff ein Wort ist und keine Phrase - glücklicherweise wird der nachgeschaltete Phrasenfilter, der ja mangels Index ein full table scan sein muß, nur auf die Treffermenge der vorherigen FULLTEXT-Matches angewendet.)

Das Ergebnis dieser Abfrage landet in einer temporären Tabelle; diese Entscheidung muß ich noch mal überdenken, vermute ich - das hängt auch davon ab, daß ich eine möglichst performante JOIN-Form für die nachfolgenden Schritte finde, denn an dieser Stelle wäre das Zeilenprodukt noch irre groß.

Schade. Bei einer geringeren Datenmenge würde es sich vielleicht(!) lohnen, eine ID-Menge herauszuholen und aus dieser ein neues Statement mit IN-Klausel zu generieren.

Wenn die Treffermenge an dieser Stelle schon klein wäre, dann würden alle nachfolgenden Schritte praktisch nichts mehr kosten - vor allem das Sortieren nicht. Wenn sie groß ist, dann ist Deine Optimierung nicht einsetzbar - und genau dann wird das Sortieren teuer ... "ein Teufelskreis".

Jetzt folgt ein JOIN dieses Ergebnisses über eine dritte Spalte gegen eine Entitlement-Tabelle, aus der ich durch ein WHERE mit einer anderen Spalte das Berechtigungsprofil des Anwenders heraus hole - nur die "berechtigten" Treffer "überleben" diesen Filter. (Die Entitlement-Tabelle hat fünfstellig viele Einträge, von denen jeder Benutzer dreistellig viele Zeilen "besitzt" - das ist eine Art "Positivliste".) Das Ergebnis landet in einer weiteren temporären Tabelle. Möglicherweise ist es zu überlegen, die Suchreihenfolge umzukehren. Dieser Schritt scheint in eine relativ konstante Datenmasse zu resultieren, während die vorherige Suche vermutlich erheblich weniger, aber auch erheblich mehr Daten liefern kann.

Das täuscht. Das Entitlement ist keine Positivliste über die Einträge der ersten Tabelle (wäre das so, dann wäre Deine Idee prima), sondern nur eine Positivliste über die Filterspalte dieses Filterschritts (die "Nachrichtenquellen"). Hier wäre nun Deine Idee mit der expliziten IN-Klausel anwendbar - allerdings ist die Wertemenge je nach Kundenvertrag irgendwas zwischen vielleicht 5 und 30 Werten groß ... (dreistellig ist theoretisch mög- lich, aber praktisch sehr unwahrscheinlich.) Hast Du eine Vorstellung, bei welcher Mengengröße "IN" (plus ein vor- heriges SELECT zur Berechnung dieser Menge!) besser wäre als ein JOIN?

Kannst Du vorher abschätzen, wie der Fall gelagert ist, und ggf. die Reihenfolge wählen?

Der Entitlement-Filter ist eine pro Benutzer relativ konstante prozen- tuale Filterung - die Treffermenge wäre riesig, wenn das der erste Schritt wäre. (Diese 5 bis 30 Einträge könnten 10 bis 100% des gesamten Datenvolumens bedeuten.) In vielen Fällen filtert die Entitlement-Prüfung nur marginal - sie ist aber unverzichtbar, weil diese Entitlement-Klassen Geld kosten. (Hm ... bei dieser Gelegenheit müßte ich mal testen, ob signifikant viele Benutzer alle Entitlement-Klassen besitzen und ich bei diesen den Entitlement-Filter einfach überspringen sollte ...)

Als nächstes folgt der Duplikat-Filter - falls er denn gewünscht ist (das ist einer der vielen CGI-Parameter). Realisiert ist der über ein GROUP BY über Spalte 5 und 6 der Trefferzeilen - Hast Du die Ergebnisse mit DISTINCT verglichen? Möglicherweise kannst Du hier zwei Schritte vereinen.

Leider nicht. Die Tabellenträge sind sicherlich in mehreren anderen Feldern außer Nr. 5. und 6 unterschiedlich, sonst hätten sie gar keine Existenzberechtigung in meinem Datenvorrat. ("Triviale" Duplikate würde schon mein Importer erkennen.)

DISTINCT eignet sich nicht als Duplikat-Filter - ich brauche danach immer noch fast alle Felder für die Trefferanzeige, und über alle Spalten habe ich definitiv keine Duplikate. (Das ist so ein Fall, wo ich keinen autoincrement brauche, weil meine Felder einen schönen Primärschlüssel hergeben, der - redundant zu den vier einzelnen Feldern, aus deren Inhalt er sich zusammensetzt - als zusätzliche Spalte existiert.)

Als letzter Schritt werden die Treffer nach der 7. Spalte (timestamp) sortiert Ich weiß nicht, wie MySQL arbeitet; aber Oracle benutzt mit "SELECT ... WHERE a=... AND b=... ORDER BY c" einen Index über a, b und c, ohne per ROWID auf die Tabelle zuzugreifen. Möglicherweise kannst Du Dir das auch bei MySQL zunutze machen.

Meine temporäre Tabelle hat aber keine Indexe ... diese aufzubauen ist etwa so teuer wie das Sortieren ohne Index.

Diese Sortierung ist allerdings m. E. der teuerste Teil der gesamten Verarbeitung - ich habe in hinreichend vielen Fällen noch einige tausend Treffer, will aber nur ein paar dutzend oder hunderte anzeigen. Kannst Du in der WHERE-Klausel abschätzen, in welchem Zeitbereich sich die herauszulesenden Zeilen wohl bewegen mögen?

Der Zeit-Filter hat bereits vor dem Sortieren stattgefunden: Einerseits ggf. explizit durch die WHERE-Klauseln der allerersten Auswahl, andererseits implizit durch die Angabe des Tabellennamens. (Das ist auch der Grund, weshalb ich manche Tabellen nicht durchsu- chen muß, wenn per CGI-Parametern ein "kleines" Zeitintervall ange- geben ist.)

Wenn Du ein wenig großzügig reduzierst, braucht nicht so viel sortiert zu werden. Wenn Du nur ungefähr raten kannst, ist diese Methode davon abhängig, ob "ein paar Datensätze weniger" ein akzeptables Ergebnis darstellen.

An dieser Stelle habe ich leider nicht wirklich eine definierte Aufgabenstellung. ;-\

Ich weiß, daß ich die exakte Trefferzahl nur dann zuverlässig bekomme, wenn ich vorher nicht "auf Verdacht" limitiere. Ich weiß auch, daß ich bei geforderten 20 Treffern mit hoher Wahrscheinlich- keit schon den ersten Filter auf 100 Treffer limitieren dürfte - es wäre aber durchaus möglich, daß ich dann aus Versehen nur 18 Treffer nach Entitlement und Duplikatfilter bekomme. Und die Wahrscheinlichkeit dafür hängt wesentlich von der Menge der Entitlement-Einträge dieses Benutzers ab - und vom zufälligen Auf- treten von Duplikaten ... das ist alles ziemlich wildes Kristall- kugelraten. (Nicht, daß ich an fünf anderen Stellen nicht ohnehin bereits wildes Kristallkugelraten machen würde - das soll sogar in gewisser Weise die Stärke der ganzen Maschine werden ...)

Aber ich habe diese Idee im Hinterkopf - ich werde mal ausprobieren, ob ich im Testbetrieb tatsächlich Treffer dadurch verliere. Bei der vorherigen Maschine (von der diese hier eine Art Klon mit anderem Datenuniversum ist) hatte ich eine solche Logik tatsächlich drin - nur ist es dort so, daß der Benutzer weiß, wonach er sucht, also das Fehlen des erwarteten Treffers merken würde. Die hier beschriebene Suche entspricht eher der Self-Archiv-Suche, was die Daten angeht - wenn ich dort etwas nicht finde, weil die Suchmaschine den Treffer unterdrückt, dann habe ich verloren. Und das kann schlimm sein.

Und es ist wirklich bitter, 5000 Zeilen zu sortieren und danach alle bis auf 20 wegzuwerfen. Naja, das ist eigentlich nicht so tragisch, solange der Sort im Speicher stattfinden kann.

Meine Beobachtungen des Antwortzeitverhaltens weisen deutlich darauf hin, daß schon bei mehreren hundert Treffern das Sortieren alles andere erschlägt und spürbar lange dauert.

(... das Forum verkraftet meine Antwort nicht in einem Stück ...)