am 8. Juli 2009
Bei größeren Projekten kommen die Themen Performance & Caching irgendwann in den Fokus – falls es nicht schon vorher der Fall war. In diesem Artikel möchte ich besonders auf Architekturen eingehen, die aus mehreren Applikations-Servern und einem Datenbank-Server bestehen. Mit den Hits auf die Applikationen steigen die Datenbankanfragen und im schlimmsten Fall wird die Last durch Anfragen von den verschiedenen Applikations-Servern zu hoch.
Um die Applikationen jedoch dauerhaft stabil und performant zu halten, sind Änderungen zwingend notwendig. Die Hardware kann in diesem Fall natürlich aufgerüstet werden, ich denke jedoch für uns als Entwickler ist die Herausforderung das Problem auf der Entwicklungsebene zu lösen. Nahe liegend ist es also die Last durch die Zugriffe auf den Datenbank-Server zu verringern und die Übertäter – lange oder oft genutzte Queries – zu optimieren oder zu cachen . Wie der Titel sagt, gehe ich heute auf das Caching ein. Bei meiner aktuellen Einarbeitung in das Zend Framework, bin ich relativ schnell über Zend_db gestolpert und hatte das Caching im Hinterkopf. Da kam die kleine, schnelle Datenbank SQLite, die von Zend_db unterstützt wird, gerade richtig.
Für alle, die noch keinen Kontakt mit SQLite hatten: Es ist eine auf C basierende Datenbank, die ohne jegliche Konfiguration direkt genutzt werden kann. Eine Datenbank wird bei Erstellung in eine einzige Datei geschrieben und ist somit äußerst platzsparend und backupfreundlich. Seit PHP5 ist die SQLite-Extension standardmäßig integriert. Mit PHP 5.3 ist die Erweiterung SQLite3 erschienen, die zusätzliche Funktionen bietet.
Um nun also das Caching effizient umzusetzen, sollte die SQLite Datenbank aus Performance-Gründen direkt auf dem Applikations-Server platziert werden, wo die Daten benötigt werden.
Dadurch wird erstens der ausgelagerte MySQL Server entlastet und zweitens muss die Anfrage nicht durch das Netzwerk geschickt werden, sondern kann direkt innerhalb der Applikationverarbeitet werden.
Im folgenden Beispiel werden die meist verkauften Artikel (Topseller) aus einer fiktiven Online-Shop-MySQL-Datenbank gezogen und in eine SQLite-Datenbank geschrieben.
Somit ist es möglich, die oft in dem Shop aufgerufenen Topseller abzufragen, ohne bei jedemAufruf eine Verbindung zur MySQL-Datenbank herzustellen.
<?php>
require_once('Zend/Db/Adapter/Pdo/Sqlite.php');
require_once('Zend/Db/Adapter/Mysqli.php');
$config_sqlite = array(
'dbname' => 'sqlite_dev.sqlite',
'sqlite2' => true
);
$config_mysql = array(
'host' => '192.168.0.22',
'username' => 'dev_user',
'password' => '123456789',
'dbname' => 'dev'
);
$sqlite_db = Zend_Db::factory('pdo_sqlite', $config_sqlite);
$mysql_db = Zend_Db::factory('mysqli', $config_mysql);
// MySQL
$query = ("select sum(a.number) as sales_count,
b.description,
c.picture,
c.price,
c.tax
from
art_order as a,
art_description as b,
art_data as c
where
b.id = a.article_id and
b.language = 'de' and
c.id = a.article_id
group by
a.article_id
order by
sales_count
");
$result = $mysql_db->fetchAssoc($query);
// SQLITE
$sqlite_db->query(" DROP TABLE 'topseller'");
$sqlite_db->query(" CREATE TABLE 'topseller' (
'rank' INTEGER PRIMARY KEY,
'picture' TEXT,
'description' TEXT,
'price' REAL,
'tax' REAL
);
");
$insert_data = array();
$rank = 1;
foreach($result as $value){
$insert_data = array(
'rank' => $rank,
'picture' => $value['picture'],
'description' => $value['description'],
'price' => $value['price'],
'tax' => $value['tax']
);
$sqlite_db->insert('topseller', $insert_data);
$rank++;
}
?>
Die Kombination von MySQL und SQLite im Quellcode, dient in diesem Fall der Übersichtlichkeit und Vergleichbarkeit der beiden Methoden über Zend_db.
Im SQLite Konfigurationsarray ($config_sqlite) habe ich den Parameter (‘sqlite2′ => true) absichtlich stehen lassen. Wer PHP 5.3 bereits nutzt, kann diesen einfach entfernen, wodurch automatisch die SQLite3-Extension verwendet wird. Zudem ist es wichtig zu wissen, dass SQLite3 die Datentypen INTEGER, REAL, TEXT & BLOB als Hauptdatentypen unterstützt, jedoch durch ein dynamisches Datentyp System jedem Wert einen eigene Datentyp zuweist.
Um die Topseller innerhalb der SQLite-Datenbank aktuell zu halten, wäre ein Cronjob eine elegante Lösung bzw. die Einbindung in eine Caching-Klasse zur Erweiterung einer schon bestehenden Struktur.
Aus eigener Erfahrung kann ich berichten, dass SQLite bei Abfragen auf eine einzelne Tabelle schneller ist, als eine Abfrage auf eine MySQL-Tabelle. Somit eignet sich SQLite im Besonderen für diese Methode. Für komplexere Abfragen und sehr große Datenmengen, gibt es jedoch geeignetere Methoden. Bei Interesse an der Vielfältigkeit, sowie der Nutzung von Zend_db oder SQLite, werde ich dieses Thema gerne aufgreifen und in Kürze weiter darüber berichten.