am 12. Juli 2010
PHP (Paolo hat’s programmiert) ist gerade 15 Jahre alt geworden. Es darf jetzt Mofa fahren. Glückwunsch zur Mofa-Prüfbescheinigung, Finger weg von Alkohol, Zigaretten und Mädels. Und nichts tunen!
In 15 Jahren habe ich Rasmus zwei Fragen zu MySQL gestellt. Die erste Frage schickte ich an die php-generals Mailingliste. Ich hatte einen Fehler bei der Benutzung der MySQL Schnittstelle begangen und fand ihn nicht. Rasmus beantwortete meine Mail. Was für ein Gefühl – das erste Mal vergisst man nicht! Jahre später trafen wir uns auf dem ersten PHP-Vikinger in Skien. Ich fragte ihn was er sich von MySQL wünsche. Er sagte mir, daß MySQL dämlich sei. Was für ein Gefühl – auch das zweite Mal vergisst man nicht!
In Skien sprachen wir über Caching. Rasmus arbeitete in einer Firma die bei Lastproblemen einfach ein paar Lastwagen mit Servern bestellte. Es waren etwas andere Dimensionen als ich gewohnt war. Und es waren andere Ansprüche. Der Weg von PHP zum MySQL Query Cache des MySQL Servers war ihm zu weit und zu langsam. Und er fragte sich welche Hardware er für den MySQL Server und dessen MySQL Anfrage-Cache benutzen sollte, wenn ein weiterer Lastwagen von PHP-Webservern beginnt Anfragen an den MySQL Server zu senden. Als MySQL in Sun aufging hätte ich ihm passende Hardware anbieten können. Doch damals skalierte der MySQL Server nicht besonders gut bei vielen Cores und CPUs. Rasmus hätte den müden Versuch einer vertikalen Skalierung per Hardware vermutlich nicht einmal als Lacher in einen seiner Performanzvorträge aufgenommen. 2006 fragte Rasmus nach einem Client-Cache für horizontale Skalierung. Kaum vier Jahre später gibt es ein Cache Plugin für die mysqlnd-Datenbankbibliotek. Rasmus arbeitet inzwischen für eine andere Firma. Ich weiß nicht ob er auch heute noch einen Client-Cache braucht. Ist noch einer da, der ihn will?
(Kasten – Haupteigenschaften, Überschrift “Spaß beiseite – die wichtigsten Fakten” oder ähnlich)
| Kurzbeschreibung |
Semi-transparenter clientseitiger Anfrage-Cache. |
| Unterstützte PHP MySQL Extensions |
ext/mysqli, PDO_MySQL, ext/mysql |
|
| Vorraussetzungen |
PHP 5.3.3-dev oder neuer, Verwendung von mysqlnd |
|
| Lizenz |
PHP Lizenz |
| Status |
Prototyp, erfüllt MySQL Alpha/Beta QA-Richtlinien |
|
| Programmiersprache |
C, stellt PHP-Schnittstelle bereit |
|
| Invalidierungsstrategie |
Time-to-live (TTL) oder benutzerdefiniert |
|
| Verdrängungsstrategie |
entsprechend Speicherhandler (keine oder benutzerdefiniert) |
|
| Speicherhandler |
Default: Prozessspeicher, APC: Shared Memory, Memory Mapped IO, Memcache, SQLite (Berkeley DB) |
|
| Speicherbare Anfragen (1.0.0-prototyp) |
Gepufferte nicht vorbereitete Anfragen |
|
| Nicht speicherbare Anfragen (1.0.0-prototyp) |
Ungepufferte Anfragen, Prepared Statements |
|
| Speicherbare Anfragen (1.0.1+) |
Wahrscheinlich alle |
|
| Projektseite |
http://forge.mysql.com/wiki/MySQLnd_Query_Cache_Plugin_for_PHP |
|
Mofatuning
Wir sind jetzt 15, wir haben jetzt eine Mofa und nun tunen wir sie auch mit dem “mysqlnd query result cache plugin”, damit wir schneller zur Party kommen!
Der MySQL native driver for PHP (kurz: mysqlnd) ist eine in C geschriebene Datenbankbibliothek. Die Bibliothek ist in PHP 5.3 enthalten. Die C basierten PHP MySQL Erweiterungen können mysqlnd als Alternative MySQL Client Library (AKA libmysql, libmysql client, …) benutzen. Mysqlnd arbeitet also unterhalb der PHP-Anwendungen und unterhalb der PHP MySQL Erweiterungen im PHP selbst.
Vor kurzem wurde die erste Tuninganleitung für mysqlnd veröffentlich. Der Plugin-Leitfaden beschreibt wie die mysqlnd-Bibliothek mittels C-Programmierung um neue Funktionen erweitert werden kann. Das erste vom Haustuner Andrey Hristov stammende Tuningbauteil ist ein Client-Cache Plugin. Die genaue Bezichnung lautet “mysqlnd query result cache plugin for PHP”. In der Szene auch “mysqlnd query cache” oder “QC” genannt.
| Drupal, phpMyFAQ, phpMyAdmin, Oxid, … |
| | |
| ext/mysql, ext/mysqli, ext/PDO_MYSQL |
| Mysqlnd |
| Mysqlnd plugin |
| Load Balancing |
Monitoring |
Performance: Cache |
| | |
| MySQL Server |
Wie jedes andere mysqlnd-Plugin auch ist der QC in C geschrieben. Das entspricht dem was dem Spediteur dem keine Last zu schwer immer gepredigt hat: PHP für den Glue-Code, C für die Geschwindigkeit. Angenehmer Seiteneffekt: als C-Erweiterung der Datenbankbibliothek ist der Cache fast transparent und funktioniert mit allen bestehenden PHP MySQL Programmierschnittstellen und Anwendungen. Das Plugin ist einem clientseitigen transparenten Proxy ähnlich.
Mit 300 über die Bahn
QC in die Mofa reinstecken und sie rennt über 300 Sachen – schneller geht es nicht! Schön wäre es… Es ist unmöglich eine seriöse Aussage über mögliche Geschwindigkeitsgewinne zu treffen. Zu groß und zu zahlreich sind die Variablen. Jeder Anwender ist auf eigene Tests angewiesen. Nur so viel: mit einer kleinen 2-Rechner (1x App-Server, 1x DB-Server, jeweils 2-Core-CPUs) Konfiguration und dem Oxid eShop liegen der MySQL Anfrage-Cache des Servers und der Client-Cache gleichauf. Die meiste Zeit verbringen die CPUs nicht mit Datenbankanfragen und dem Warten auf Ergebnisse vom entfernten und sich langweilenden MySQL-Server sondern mit anderen Aufgaben. Deshalb gibt es kaum Unterschiede zwischen dem serverseitigem und dem clientseitigem Cache in genau dieser Konfiguration. Andere Konfiguration – andere Effekte. Wenn einer mal einen Lastwagen mit Servern…
Wenn ich als 15jähriger mit 300 über die Bahn donnern will, dann gehe ich nicht zu den anderen Kiddies, um meine Mofa zu tunen. Ich leihe mir den Sportwagen aus dem Villenviertel aus. Der Sportwagen wurde für die Rennstrecke konzipiert, die Mofa für den lokalen Nahverkehr. Eine Anwendung die vom ersten Tag an für gezieltes Caching optimiert wurde, braucht den QC nicht. Die Anwendung kann viel gezielter cachen. Sie kann Produkte von Datenbankergebnissen – wie ein HTML-Snippet – zwischenspeichern. Wer den Sportwagen in der Garage hat, braucht die Mofa nicht zu tunen. Wer eine Mofa nutzen muss, wer seine Anwendung nicht im großen Maße verändern will oder kann, wer eine schnelle Problemlösung bei minimalen Kosten will, der sollte sich den QC anschauen.
(Kasten/Exkurs)
Aber das geht doch auch mit PHP!
Die Zwischenspeicherung von Datenbankergebnissen auf dem Client ist ein alter Hut. Datenbankergebnisse sind mit wenigen Zeilen PHP-Code gesichert, hier ein schematisches Beispiel unter Verwendung von ext/mysqli und APC:
$cache_hit = false;
$rows = apc_fetch("slow_table", $cache_hit);
if (!$cache_hit) {
$res = $mysqli->query("SELECT data FROM slow_table");
$rows = $res->fetch_all();
apc_store("slow_table", $rows);
}
Mit dem QC im Halbautomatikmodus ist es etwas einfacher:
$res = $mysqli->query("/*qc=on*/SELECT data FROM slow_table");
$rows = $res->fetch_all();
Mindestens so wichtig wie das Zählen der geänderten Zeilen ist es sich klar zu machen, daß ein PHP basierter Cache zwei Serialisierungen benutzt, während der QC mit einer auskommt. Eine Serialisierung wandelt Daten von einem Format in ein anderes, um sie transportieren zu können. Zur ersten Serialisierung kommt es innerhalb der MySQL Datenbank. MySQL wandelt im Hauptspeicher vorliegende Anfrageergebnisse in eine binäre Zeichenkette, um diesen über das Netzwerk an PHP zu senden. Dort angekommen werden die Netzwerkdaten von der Datenbankbibliothek (hier: mysqlnd) in eine PHP-Variable gewandelt. Diese Serialisierung tritt immer auf. Sie kann nicht eingespart werden.
Alle PHP basierte Zwischenspeicher müssen die PHP-Variable erneut serialisieren, um sie im Zwischenspeicher abzulegen. Im Beispiel wird diese zweite Serialisierung von apc_store()/apc_fetch() vorgenommen. Aus der PHP-Variablen wird eine Zeichenkette, die beim Cache-Miss im Cache abgelegt wird. Beim Cache-Hit erfolgt die Deserialiserung von der Zeichenkette in eine PHP-Variable.
|
PHP basierte Lösung |
mysqlnd query cache plugin (QC) |
| 1. Serialisierung (MySQL) |
MySQL -> binäre Zeichenkette – Netzwerk -> PHP-Variable |
| 2. Serialisierung (Cache) |
PHP-Variable -> Zeichenkette – Cache -> PHP-Variable |
entfällt (siehe Text) |
Der QC benötigt keine zweite Serialisierung. Er speichert die aus der ersten Serialisierung stammenden Netzwerkdaten von MySQL zwischen. Es erfolgt keine Wandelung der vorhandenen Netzwerkdaten in eine andere Form, um die Daten im Cache ablegen zu können. Eine mögliche Fehlerquelle wird ausgeschlossen.
Die drei Betriebsarten des Cache-Plugins
Das Budget ist knapp, eine Villa ist weit und breit nicht zu sehen, das Geld hat kaum ausgereicht um eine vollgetankte Mofa zu kaufen. QC kommt gerade Recht. QC ist so einfach zu bedienen wie es der Tuninganfänger benötigt. Es gibt nur drei Betriebsarten: Vollautomatik, Halbautomatik, Manuell.
Die Vollautomatik speichert blind jede gepufferte, nicht preparte Datenbankanfrage (php.ini – mysqlnd_qc.cache_by_default = 1) für eine vorgegebene Anzahl von Sekunden (mysqlnd_qc.ttl = 30), deren Metadaten in allen Spalten einen Tabellennamen aufweisen (mysqlnd_qc.cache_no_table = 0). Eine gepufferte, nicht vorbereitete Datenbankanfrage von folgenden PHP MySQL Funktionsaufrufen generiert:
- mysqli_query()
- mysqli_real_query() + mysqli_store_result()
- PDO::query(), PDO::exec(), PDO::prepare() wenn die Voreinstellung PDO::ATTR_EMULATE_PREPARES = 1 verwendet wird
- mysql_query()
In der Standardeinstellung (mysqlnd_qc.cache_no_table = 0) werden nur diejenigen Anfragen zwischengespeichert, bei denen alle Ergebnisspalten in ihren Metadaten einen Tabellennamen zeigen. Damit soll verhindert werden, daß versehentlich Inhalte gespeichert werden, die nicht veraltern dürfen: SELECT SLEEP(1), SELECT CURTIME(), …
Zwischengespeicherte Anfragen werden für einen bestimmten Zeitraum aus dem Zwischenspeicher beantwortet. Die Invalidierungsstrategie ist Time-to-live (TTL). Beim Einsatz von TTL kann es zur Auslieferung von veralteten Daten kommen. Zwischengespeicherte Daten werden nicht automatisch invalidiert, wenn sich dem Anfrageergebnis zugrundeliegende Daten ändern. Diese Beschränkung kann im manuellen Modus aufgehoben werden (siehe unten).
Die Halbautomatik (mysqlnd_qc.cache_by_default = 1) ist der voreingestellte Standardbetriebsmodus des QC. Der eilige Mofa-Rennfahrer aktiviert den Turbo per Knopfdruck auf den Feldwegen, die nicht von den Rennkommissaren überwacht werden. Nur die Anwendung und der der Mofa-Rennfahrer kennen diese Feldwege. Nur sie verfügen über das Wissen, wann eine Anfrage zwischengespeichert werden darf und wann nicht, beispielsweise, weil sie einen Lagerbestand anzeigt.
Der QC bekommt seine Anweisungen per SQL-Hint. SQL-Hints sind SQL-Kommentare, die Aktionen auslösen. Für den Einsatz von SQL-Hints sind keine API-Änderungen an den bestehenden PHP MySQL Schnittstellen ext/mysqli, PDO_MySQL und ext/mysql notwendig. Es wird nur der Anfragetext verändert. Eine zu cachende Anfrage wird mit “/*qc=on*/” gekennzeichnet. Der SQL-Hint muss am Anfang der Anfrage stehen. Es darf ein optionaler, zweiter SQL-Hint “/*qc_ttl=n*/” folgenden, der die voreingestellte Standardlebenszeit (mysqlnd_qc.ttl) überstimmt.
$res = $mysqli->query("/*qc=on*/SELECT data FROM slow_table");
$res = $mysqli->query("/*qc=on*//*qc_ttl=3600*/SELECT news FROM hourly_update");
Das Tuningteil tunen: mehr als TTL
Mann kennt seine Pappenheimer. Auf dem Feldweg wird nie kontrolliert, weil es fast schon ein Privatweg ist. Hier darf die Mofa bei gutem Wetter alles geben: kachel Mann! Damit dies automatisch passiert, gibt es den manuellen Betriebsmodus bei dem der Anwender eigene Speicherhandler erstellt.
Die Speicherhandler des QC sind dafür zuständig eine zu cachende Anfrage zu erkennen. Die eingebauten Speicherhandler für Memcache, APC, Hauptspeicher und SQLite werden über SQL-Hints gesteuert. Benutzerdefinierte Handler können jede andere Form der Steuerung implementieren. Speicherhandler sind weiterhin für die Umsetzung einer Invalidierungs- und Verdrängungsstrategie zuständig. Die eingebauten Speicherhandler verwenden TTL als Invalidierungsstrategie. Benutzerdefinierte Handler können komplexere Invalidierungen umsetzen, beispielsweise um die Auslieferung von veralteten Daten vermeiden.
Die ersten Schritte mit eigenen Handlern lassen sich am Besten mit Ableitungen vom eingebauten Default-Handler (Speicherung der Daten im Prozessspeicher) machen. Der Default-Handler ist der einzige, welcher als Klasse in PHP zur Verfügung steht. Wer gleich alles tunen will, der registiert Callback-Funktionen oder implementiert das öffentliche Handlerinterface mysqlnd_qc_handler. Mehr dazu, irgendwann, in einem Folgeartikel.
class mofatuning extends mysqlnd_qc_handler_default {
public function is_select($query) {
if ($this->have_sun() && stristr($query, "feldweg")) {
printf("... activating turbo for 60s!");
return 60;
}
return parent::is_select($query);
}
private function have_sun() {
return true;
}
}
Die Methode is_select(string $query) ist dafür verantworlich zu prüfen, ob der Cache aktiviert werden soll und wenn ja welche TTL benutzt werden soll. Im Beispiel prüft is_select() ob die Sonne scheint und die Mofa auf einem Feldweg fährt. Ist dies der Fall, wird der Turbo für 60 Sekunden gezündet, genauer: die Datenbankanfrage wird für 60 Sekunden zwischengespeichert. Falls die Bedingungen nicht zutreffen, wird das eingebaute Standardverhalten verwendet. Speicherhandler, die das komplette Handlerinterface implementieren, können dem Rückgabewert von is_select() eine andere Bedeutung zukommen lassen als die einer Lebenszeit (TTL).
PECL wir kommen!
Wem der vorgestellte Prototyp zusagt, der ist herzlich aufgefordert dabei zu helfen das Plugin für PECL aufzubereiten. PECL wäre eine ideale Schraubergarage! Hilfe kann in viellerlei Form erbracht werden: mittels Erfahrungsberichten und Fragen auf der Mailingliste, durch eigenes Blogging oder noch besser durch das Schreiben von Dokumentation oder gar der aktiven Mithilfe an der Programmierung. Es gibt noch viel zu entdecken! Packen wir das Thema Doku an – vielleicht schon vor dem nächsten Blogartikel.
Das Potential der mysqlnd C plugin API
Poweranwender sollten bei all den albernen Mofavergleichen nicht übersehen, daß dies das erste öffentliche mysqlnd Plugin ist. Es ist nur eine Möglichkeit die mysqlnd C plugin API zu nutzen. Die API ist ein Bestandteil von mysqlnd und steht jedem PHP-Anwender, der in der Lage ist eigene C-Extensions zu entwicklen, zur Verfügung. Die notwendige C API Dokumentation für Eigenentwicklungen wurde auf der IPC-Spring vorgestellt.