Cheatah: & (DBI::) & (mySQL): Performance-Messung

Beitrag lesen

Hi,

Du kennst aber perldoc Benchmark?
nicht im Detail - würde ich die mySQL-Zeiten dabei mit erfassen
können, dann wäre genau jetzt der Zeitpunkt, mich dort einzulesen.

leider nicht; es kann auch nur die Daten auswerten, die es bekommt.

Ja - aber die Information ist zu ungenau, um sie auf mehrere
tausend auf dem Server arbeitende Kunden sinnvoll hochzurechnen.

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.

Dazu kannst Du Deine Statements mit "EXPLAIN SELECT ..." testen;
Das habe ich schon hinter mir.

Ah. Hätte ich mir denken können.

Meine Anwendung ist im Wesentlichen eine Art Suchmaschine.

Verstehe. Ja, ist im Detail nicht ganz leicht vorherzusehen, was da passieren wird.

Und in meinem Fall kommt noch dazu, daß die konkreten SQL-Statements
sich von Anforderung zu Anforderung _heftig_ unterscheiden können -

Das klingt fast so, als wäre die Statementgenerierung eher hysterisch gewachsen. 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 :-)

Vielleicht kann Dir da jemand mit Erfahrung zur Seite stehen.
Was SQL angeht, glaube ich die selbst zu haben - bei mySQL sieht es
nicht so gut aus.

Ja, so meinte ich das auch. Wer deutsch fließend spricht, muss noch lange koi schwäb'sch babbele gönne...

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. Leider habe ich damit keine praktische Erfahrung; aber hier im Haus wird es ganz gerne eingesetzt, wenn große Datenmassen schnell durchsucht werden müssen - wenigstens für eine Vorauswahl der in Frage kommenden Datensätze.

Interessant sind nur die Lesezugriffe - deshalb habe ich praktisch
jeden Index, den Du Dir auch nur theoretisch vorstellen kannst,
verfügbar. (Schreiben darf langsam sein - Lesen muß schnell sein.)

Hast Du Dir überlegt, redundante oder "überflüssige" Daten mit abzulegen, die Du programmlogisch vorberechnen kannst, um die Datenmenge pre-reduzieren zu können? Der Speicherplatz scheint kein wesentlicher Faktor zu sein; und ein ideales DB-Layout ist nicht so wichtig wie die Performance.

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 nach-
folgenden 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 heruaszuholen und aus dieser ein neues Statement mit IN-Klausel zu generieren. Ansonsten kenne ich mich mit temporären Tabellen leider zu wenig aus.

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 drei-stellig 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. Kannst Du vorher abschätzen, wie der Fall gelagert ist, und ggf. die Reihenfolge wählen?

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 Treffer-
zeilen -

Hast Du die Ergebnisse mit DISTINCT verglichen? Möglicherweise kannst Du hier zwei Schritte vereinen.

(Oracle würde eine solche Query ablehnen, glaube ich mich zu erinnern.)

Ja, würde es :-)

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.

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? 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.

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.

Mein Problem ist, daß ich unbedingt den Volltextindex brauche, um die
Treffermenge effizient zu berechnen, diese dann anschließend aber nach
einem völlig anderen Kriterium sortieren muß, um _danach_ erst mit
LIMIT die gewünschte Ausgabemenge zu ermitteln und deshalb für das
Sortieren selbst keinen bereits existierenden Index mehr nutzen kann.

Vielleicht kannst Du das Pferd umgekehrt aufzäumen. Erst sortieren (sortierten Index verwenden - naja, ein Index mit Timestamp als erste Spalte ist natürlich auch nicht das beste...), dann grob limitieren, dann gegen die Suchbegriffe gegenchecken. Ich runzele zwar gerade sehr die Stirn, aber vielleicht bringt Dich ja ein anderer Denkansatz auf einen völlig neuen Weg...

(Geben würde es den nämlich schon ... und es gibt sogar Suchanfragen,
die davon profitieren können, nämlich solche ohne Suchbegriff, die
dann eben nicht "suchen", sondern nur "filtern", über die vielen
anderen CGI-Parameter - es ist halt eine Art "Hybridmaschine".)

Btw, gibt es bei MySQL ein Äquivalent zu Oracles Hints?

Allerdings kann ich an mehreren Stellen abbrechen: Wenn ich genügend
Treffer habe und wenn die CGI-Parameterwerte zur Beschreibung des
gewünschten Zeitintervalls bestimmte dieser Tabellen ausschließen.

Dafür ist allerdings Programmlogik nötig; das bedeutet mehr Roundtrips und damit mehr Zeit, DB-seitige Analysen usw. Eine Auftrennung in mehrere Statements ist i.d.R. nur dann sinnig, wenn _üblicherweise_ nur das erste verwendet wird, jedes weitere aber die Ausnahme bildet.

Ein ähnliches System habe ich mal mit dem Intermedia Text Package verwendet: Die contains()-Funktion wird erst mit dem Suchbegriff gefüttert; wenn das nichts liefert, mit "Suchbegriff%", dann mit "%Suchbegriff%", dann mit "fuzzy(Suchbegriff)". Der letzte Fall dauert ewig (teilweise über 1-2 Sekunden), war aber gegenüber einer leeren Treffermenge der akzeptablere Vorschlag.

Es kommt ziemlich oft vor, daß [...] Ich habe also die "schnellen" Anfragen etwas
gebremst, um bei den "langsamen" Abfragen den Super-GAU zu verhindern.

Klingt durchdacht. Das besagt leider noch nicht, ob es praxistauglich ist - wenn die "schnellen" Anfragen überwiegen, durch dieses Konzept aber langsamer werden, ist es eine Verschlimmbesserung.

Auch innerhalb des beschriebenen Ablaufs steige ich an jeder Stelle
aus, wo ich erkannt habe, daß 0 Treffer vorliegen und nachfolgende
JOINs keinen Sinn mehr machen.

Nun ja, das ist wohl selbstverständlich :-)

Ich habe also buchstäblich keine Ahnung, wie viel SQL-Code tatsächlich
generiert und ausgeführt wird, bevor ich ihn selbst sehe. (Ich habe
allerdings durchaus eine Vorstellung davon, was ein "guter" oder
"schlechter" Suchbegriff ist und wie viele Treffer in welchen Tabellen
etwa zu erwarten sind ... so, wie in diesem Forum hier "HTML" ein
außergewöhnlich schlechter und "fatalsToBrowser" ein außergewöhnlich
guter Suchbegriff wäre, gibt es ähnliches auch in meinem Datenbestand.)

Wenn Du das auflisten oder patternmäßig erkennen kannst, kannst Du individuell darauf reagieren. Ich fange zum Beispiel "www.*" und "*.de" ab, wenn sie in "meiner" Produktsuchmaschine eingegeben werden... seltsamerweise passiert das fast in 50% aller(!) Fälle, bei einem Suchformular _ohne_ Autofokus...

Alles in allem bin ich mit meinem Modell schon ziemlich zufrieden

Das sind schlechte Voraussetzungen für eine Optimierung... Du wirst versuchen, an dem Konzept festzuhalten :-)

(der zu schlagende Vorgänger macht halt auch einen full table scan ... ;-),

*brüll* Ich wette, seine Suchfunktion hat alle Rekorde gebrochen ;-)))

aber es würde mich später ärgern, wenn ich durch einen Fingerfehler
irgendwo einen nennenswerten Faktor liegen lassen würde.

Das Tabellenlayout kann ebenfalls von entscheidender Bedeutung sein; gerade wenn ein Index nur bedingt verwendet werden kann. Leider kann ich Dir da nicht im Detail helfen; ich vermute aber, dass solche Dinge wie "erst schmale Spalten, dann breite Spalten" ebenfalls gelten. Über die Selbstorganisation verschiedener Spaltentypen steht aber auch in der Doku einiges interessantes.

Über eine Messung innerhalb der CGI-Anwendung (und ein entsprechendes
logfile) wäre das extrem einfach gewesen ...

Bau mal ein Logfile, das Dir die Statements inklusive der Größe des Resultsets (und vielleicht solchen Goodies wie EXPLAIN) mitprotokolliert. Sowas ist zwar ziemlich bescheiden auszuwerten, aber dafür beliebig detailliert - spätestens wenn man rücktestet. Ob MySQL ein Stats-Pack hat, weiß ich leider nicht.

Cheatah