Donnerstag, 14. Januar 2010

MySQL vs. Memcached vs. Memcached + XCache ... Benchmark

Aus aktuellen Anlass hat mich Folgendes interessiert:

Auf einem Userprofil sollen 50 Freunde ausgegeben werden. Jeder User der online ist, soll dabei farblich anders dargestellt werden als die, die offline sind.

Die MySQL Query dazu sieht in etwa so aus: SELECT a.user_id, a.hat_freund, b.username, c.user_id AS online FROM friends AS a JOIN userdaten AS b ON a.hat_freund = b.user_id LEFT JOIN last_action AS c ON a.hat_freund = c.user_id AND c.last_action > UNIX_TIMESTAMP() - 1200 WHERE a.user_id = 111 ORDER BY a.seit DESC ...als "online" gelten also User, die in den letzten 20 Minuten eine Aktion durchgeführt haben.

So eine Query kommt zur Zeit zum Einsatz. Wir sind aber bemüht dem Datenbankserver so viel Last wie möglich zu entziehen und Cachingmethoden einzusetzen.


Verglichen wurde nun:

1. Der Istzustand, die reine MySql-Abfrage und die anschliessende Ausgabe der Daten.

2. Die reine MySql-Abfrage, mit anschliessender Serialisierung und Speicherung als Array in Memcached, sowie die Ausgabe der Daten aus der Datenbank. (Bzw. die soeben serialisierten Daten)

3. Das reine Auslesen des Resultsets aus Memcached und die Ausgabe.

4. Das reine Auslesen des Resultsets aus Memcached und die Ausgabe, mit dem Unterschied, dass in der Ausgabeschleife für jeden einzelnen User per XCache der aktuelle Onlinestatus abgefragt wird.

5. Das reine Auslesen des Resultsets aus Memcached und die Ausgabe, mit dem Unterschied, dass in der Ausgabeschleife für jeden einzelnen User per Memcached der aktuelle Onlinestatus abgefragt wird.

(Memcached läuft lokal. Datenbankserver im gleichen Rack.)

Die Ergebnisse:

1. normal aus datenbank ( 0.0036)

2. aus datenbank, werte in array speichern, in den cache packen und dann ausgeben ( 0.0043)

3. daten aus memcached auslesen und ausgeben ( 0.0010)

4. daten aus memcached auslesen und ausgeben. in der schleife jeden onlinestatus via xcache prüfen ( 0.0012)

5. daten aus memcached auslesen und ausgeben. in der schleife jeden onlinestatus via memcached prüfen ( 0.0046)



Auswertung:

Methode 3 ist hier eindeutig die Schnellste. Mit dieser Variante liesse sich allerdings nicht Anzeigen, wer von den ausgegebenen Usern gerade online ist bzw. wären diese Daten schnell veraltet, so dass der Cache höchstens ein paar Minuten Gültigkeit haben sollte.

Methode 4 sieht da schon vielversprechender aus. Speichert man die Useraktivitäten jeweils auch in einer XCache Variablen, so dauert es bei diesen 50 Durchgängen lediglich 0,0002 Sekunden um alle zu überprüfen. Dadurch kann der Teil, der mit Memcached gecached wird solange gültig sein, bis der User einen neuen Freund hinzugefügt oder gelöscht hat.

Methode 5 ist die Langsamste von allen. Auf diese Methode müsste man zurückgreifen, wenn man mehr als einen Webserver verwendet und XCache als Cachingsystem unbrauchbar wird*. Wenn man aber bedenkt, dass die Hauptaufgabe nicht ist, bedeutend schneller zu sein als MySQL, sondern nur, den Datenbankserver zu entlasten, so ist die Geschwindigkeit immernoch mehr als ausreichend.


Fazit:

Solange man noch nicht darüber nachdenkt, mehr als einen Webserver zu verwenden, sollte Methode 4 verwendet werden, bzw. Methode 3, wenn die Onlinezustände der User nicht angezeigt werden sollen.
Methode 4 geht bei 50 Durchläufen noch in Ordnung. Man sollte es mit den Schleifen jedoch nicht übertreiben. Die benötigte Zeit steigt entsprechend der Anzahl der Durchläufe!

Mittwoch, 13. Januar 2010

Xcache vs. Memcached

Xcache 1.3.0 vs. Memcached 1.4.4

php 5.2.12 ... xcache statisch eingebunden.
memcache modul ebenfalls statisch eingebunden in der version 2.2.5

webserver: nginx-0.7.64

system: 2 x intel quadcore. 8GB ram. open suse 10.

getestet wurde die geschwindigkeit des setzens und des einlesens von werten in den jeweiligen cache.

testreihe 1 mit memcached über unix-socket:

1.test:
timer: 100000 mal xcache variable gesetzt ( 0.4659)
timer: 100000 mal memcached variable gesetzt ( 2.5669)
timer: 100000 mal xcache variable eingelesen ( 0.1838)
timer: 100000 mal memcache variable eingelesen ( 2.4150)


2.test:
timer: 100000 mal xcache variable gesetzt ( 0.4774)
timer: 100000 mal memcached variable gesetzt ( 2.5527)
timer: 100000 mal xcache variable eingelesen ( 0.1897)
timer: 100000 mal memcache variable eingelesen ( 2.3908)


3.test:
timer: 100000 mal xcache variable gesetzt ( 0.4690)
timer: 100000 mal memcached variable gesetzt ( 2.8108)
timer: 100000 mal xcache variable eingelesen ( 0.1873)
timer: 100000 mal memcache variable eingelesen ( 2.6441)



testreihe 2 mit memcached über TCP:
(hier wurde ein anderer server benutzt, mit 16GB ram und etwas schnelleren prozessoren. alles andere ist identisch)

1.test:
timer: 100000 mal xcache variable gesetzt ( 0.2311)
timer: 100000 mal memcached variable gesetzt ( 2.5986)
timer: 100000 mal xcache variable eingelesen ( 0.1547)
timer: 100000 mal memcache variable eingelesen ( 2.3529)


2.test:
timer: 100000 mal xcache variable gesetzt ( 0.2354)
timer: 100000 mal memcached variable gesetzt ( 2.6385)
timer: 100000 mal xcache variable eingelesen ( 0.1561)
timer: 100000 mal memcache variable eingelesen ( 2.3573)


3.test:
timer: 100000 mal xcache variable gesetzt ( 0.2376)
timer: 100000 mal memcached variable gesetzt ( 2.6054)
timer: 100000 mal xcache variable eingelesen ( 0.1541)
timer: 100000 mal memcache variable eingelesen ( 2.3435)




auswertung:
das reine setzen und einlesen von einfachen variablen ist mit xcache deutlich schneller. das ist nicht verwunderlich, denn xcache bietet weniger funktionen und ist direkt in php eingebunden, wohingegen memcached als eigenständiger daemon läuft und seperat angesprochen werden muss.

wenn man einfache html-stücke oder variablen cachen möchte und man keinen extra-server zum cachen benutzt, dann sollte xcache die erste wahl sein.

memcached hingegen bietet die möglichkeit ganze objekte zu cachen. also beispielsweise mehrdimensionale arrays, womit man ergebnisse von datenbankabfragen cachen kann.
memcached bietet außerdem die möglichkeit große objekte bzw. strings zu komprimieren.

der entscheidende vorteil jedoch liegt in der fähigkeit memcacheds, sich auf verschiedene server verteilen zu lassen. dies ist insbesondere für größere projekte von vorteil.

fazit: es schadet nichts beide varianten parallel zu nutzen. xcache ist als opcode cacher ohnehin ein must-have. der variablen-cache ist da schon mit bei, also kann man ihn auch ruhig nutzen.
memcached sollte man nutzen, wenn es darum geht, wirklich große mysql resultsets zu cachen.


xcache: http://xcache.lighttpd.net/
memcached: http://www.memcached.org/
memcache (pecl): http://pecl.php.net/package/memcache
nginx: http://www.nginx.net


test-script:

ini_set('error_reporting', E_ALL);
ini_set('display_errors', 'On');
ini_set('display_startup_errors', 'On');

function start_timer($event) {
printf("timer: %s
\n", $event);
list($low, $high) = split(" ", microtime());
$t = $high + $low;
flush();

return $t;
}

function next_timer($start, $event) {
list($low, $high) = split(" ", microtime());
$t = $high + $low;
$used = $t - $start;
printf("timer: %s (%8.4f)
\n", $event, $used);
flush();

return $t;
}

$t = start_timer("");

$x = 0;
while($x < 100000) {
xcache_set('111',"aaahhh ein string!!!",300);
$x = $x+1;
}

$t = next_timer($t, "100000 mal xcache variable gesetzt");

$memcache_obj = memcache_connect('unix:///tmp/memcached.socket', 0);
//$memcache_obj = memcache_connect('127.0.0.1', 11211);
$x = 0;
while($x < 100000) {
//$memcache_obj->set('111',"aaahhh ein string!!!",0,300);
memcache_set($memcache_obj, '111', "aaahhh ein string!!!", 0, 300);
$x = $x+1;
}

$t = next_timer($t, "100000 mal memcached variable gesetzt");

$x = 0;
while($x < 100000) {
$daten = xcache_get('111');
$x = $x+1;
}

$t = next_timer($t, "100000 mal xcache variable eingelesen");

$x = 0;
while($x < 100000) {
$daten = memcache_get($memcache_obj, '111');
$x = $x+1;
}

$t = next_timer($t, "100000 mal memcache variable eingelesen");

?>

Sonntag, 10. Januar 2010

Via PHP Unix-Socket auf Memcached zugreifen. Problembehebung.

Für mich so als Notiz.

// über IP
//$memcache_obj = memcache_connect('127.0.0.1', 11211);
// über socket
$memcache_obj = memcache_connect('unix:///tmp/memcached.socket', 0);

Geht nicht auf Anhieb, da memcached unter dem User "nobody" läuft und keine Berechtigung für /tmp/memcached.socket hat.
Nach dem Starten von Memcached ändert man mit Chmod die Zugriffsrechte der memcached.socket auf 777 und dann gehts.
Sonst gibts Fehlermeldung "Permission denied".


UPDATE:

memcached mit dem flag -a 777 starten, dann hat sich das Problem auch erledigt.
also quasi ./memcached -s /tmp/memc.socket -a 0777

Donnerstag, 17. Dezember 2009

Via phpmyadmin nachträglich einen Auto-Increment primary key hinzufügen

Wer kennt es nicht.

Man muss mit einigen Werten testen und muss eine Tabelle um einen neuen Primary Key bereichern. Wenn man das über phpmyadmin mit der normalen Spalten-Hinzufüge-Funktion macht, klappt dies nicht auf anhieb.

Hier die Lösung: Zuerst die Spalte (bsp. "id") anlegen, ohne gleichzeitig Auto-Increment zu aktivieren oder die Spalte als Index zu definieren. Als wäre es eine ganz normale Spalte.

Danach diese Spalte als INDEX festlegen (noch NICHT Primary Key). Nun die Spalte bearbeiten und "Auto increment" aktivieren.
Jetzt kann man bei Bedarf den Index als Primary Key deklarieren.

Fertig.

Donnerstag, 25. Dezember 2008

Mit AJAX nachgeladene Javascript-Funktionen ausführbar machen

Problem:

ersetzt man einen Teil einer Seite dynamisch per Ajax und enthält dieser Inhalt Javascript-Funktionen, so können diese leider nicht ausgeführt werden.

Dafür gibts aber eine Lösung:

Folgende Funktion muss in den Teil der Seite eingebaut werden, der nicht ersetzt wird:


function evalScript(scripts)
{ try
{ if(scripts != '')
{ var script = "";
scripts = scripts.replace(/]*>([\s\S]*?)<\/script>/gi, function(){
if (scripts !== null) script += arguments[1] + '\n';
return '';});
if(script) (window.execScript) ? window.execScript(script) : window.setTimeout(script, 0);
}
return false;
}
catch(e)
{ alert(e)
}
}


den, per AJAX nachgeladenen Text bzw. HTML - / JavaScript - Code, jagd man einmal durch diese Funktion und ersetzt dann das entsprechende Seitenelement wie normal.

function ersetze_seitenelement() {
if (http_request.readyState == 4) {
if (http_request.status == 200) {
text = http_request.responseText;
evalScript(text);
document.getElementById('seitenelement2').innerHTML=text;
}
}
}

Im nachgeladenen Inhalt jedoch, müssen die Javascript-Funktionen anders deklariert werde, damit das auch alles klappt.
statt

<script type="text/javascript">
function blabla() {
befehl;
}
</script>

muss es so aussehen:

<script type="text/javascript" defer="defer">
self.blabla = function () {
befehl;
};
</script>

es muss also das defer="defer" hinzugefügt werden.
Außerdem muss hinter jede Funktion ein ";" stehen.

Freitag, 10. Oktober 2008

lighttpd mit mod_mem_cache (statische Dateien cachen)

Oftmals sind die Festplatten ein Flaschenhals, gerade bei Bildservern. Nehmen wir an wir hosten dort nun viele normal-große Bilder und zu jedem Bild ein kleines Vorschaubild. Diese eigenen sich hervorragend um im Arbeitsspeicher gecached zu werden, da sie sehr klein sind und in der Regel die meisten Festplattenzugriffe verursachen.

Das third Party Modul "mod_mem_cache" für lighttpd eignet sich bestens um statischen Content zu cachen.

Zu erst einmal den aktuellen lighttpd installieren (jetzt grad 1.4.20):

www.lighttpd.net

laden, entpacken, ins Verzeichnis wechseln

dann den Patch laden: http://blog.quehy.com/tag/mod_mem_cache

wget http://blog.quehy.com/doc/lighttpd-1.4.19.mod_mem_cache.patch
patch -p0 [spitze Klammer auf] lighttpd-1.4.19.mod_mem_cache.patch
sh autogen.sh
(./configure --help)
./configure --without-mysql --without-bzip2 --without-gdbm
make
make install


sollte lighttpd schon installiert sein, muss der webserver vorher gestoppt werden. im entpackten verzeichnis (sofern nicht frisch entpackt) einmal "make clean" ausführen. danach die anderen Schritte wie schon geschrieben.

Auf die Konfiguration von lighttpd geh ich jetzt nicht ein. Infos dazu gibts auf www.lighttpd.net ... es ist im Grunde auch recht simpel.

Nun müssen wir der Konfigurationsdatei aber noch die Parameter fürs Modul mitteilen.

mem-cache.enable = "enable"
mem-cache.max-memory = 1024 #nutze bis zu 1GB Arbeitsspeicher)
mem-cache.max-file-size = 7 #Cache Dateien die maximal 7kb groß sind (nur Vorschaubilder)
mem-cache.expire-time = 480 #gecachte Dateien sind 8 Stunden gültig
mem-cache.filetypes=("image/jpeg") #Nur jpeg Grafiken cachen (Damit PHP Dateien, weiterhin ausgeführz werden)


"mod_mem_cache" eventuell noch bei den "server.modules" eintragen und zack es sollte funktionieren.

Quellen:
www.lighttpd.net
http://blog.quehy.com/tag/mod_mem_cache

Probleme:
kommt es nach autogen.sh zu einer Fehlermeldung wie "configure.in:70: error: possibly undefined macro: AC_DEFINE", dann muss "fam-devel" (bzw. libfam-dev) installiert werden.

Mittwoch, 13. August 2008

PHP Session Handler

Sessions sind ein sehr beliebtes Werkzeug um clientbasierende Informationen über viele Seitenaufrufe hinweg vom Server ohne größeren Aufwand abzurufen.

Hier solls jetzt nicht über Sessions allgemein gehen sondern darum, welche Möglichkeiten es gibt Sessions zu speichern.

Der PHP-Standard ist, dass die Sessions als normale Dateien in einem Ordner abgelegt werden.
das kann /tmp sein oder aber auch /var/www/sessions ...wie es beliebt.

Die Sessiondateien im Hauptspeicher zu behalten sollte effektiver sein, da die Festplatte beim Schreiben und Auslesen von Daten nicht beansprucht wird. Die Festplatte(n) ist(sind) nämlich oft ein Flaschenhals, besonders bei Datenbankservern. Auf vielen Servern laufen Web - und Datenbankserver gleichzeitig... da sollte jede Festplattenentlastung wahrgenommen werden.

Ich möchte nun kurz 2 Möglichkeiten vorstellen und deren Vor - und Nachteile nennen.

Die erste Möglichkeit ist der Session Handler "mm". Dieser ist verfügbar, wenn PHP mit "--with-mm" kompiliert wurde. (http://murksfurtz.blogspot.com/2008/08/schlankes-php-selbst-kompilieren.html)
Um diese Variante zu nutzen muss in der php.ini einfach die Variable "session.save_handler" von "files" auf "mm" geändert werden. PHP bzw. Webserver neustarten und die Sessions befinden sich nun im Arbeitsspeicher. Der Vorteil sollte klar sein => keine Festplattenlast durch Sessionhandling. Zudem ist diese Variante sehr einfach umzusetzen.
Nachteil: beim Webserver oder PHP-Neustart, sind die Sessiondaten verloren. Das heisst, User müssten sich in dem Fall neu einloggen.
Ein weiterer Nachteil: So ein Neustart hat zur Folge, dass die Sessiondaten auf dem Server gelöscht werden, die Cookies mit den Session-IDs auf den Clientrechnern aber noch vorhanden sind. Erstellt der Server nun neue Sessiondateien die zufällig die gleiche ID haben, wie einige noch vorhandene auf Clientrechnern, so kann es passieren, dass diese Zugriff auf andere Accounts bekommen... und das ist nicht so doll :)

Eine andere Möglichkeit bietet das Memcache-Modul. Um dieses zu nutzen, muss erst einmal "Memcached" installiert werden, welches ein Deamon unabhängig von PHP ist. Memcached gibts dort: http://www.danga.com/memcached/
Memcache ist nicht Teil des PHP Sources, man muss es daher nachträglich dynamisch oder statisch einbinden. Die Modulquellen gibts dort: http://pecl.php.net/package/memcache und wie man Extensions nachträglich statisch einbindet steht da: http://murksfurtz.blogspot.com/2008/08/php-modul-nachtrglich-statisch.html
Mit Memcache kann man übrigens, ähnlich wie mit Xcache, auch Variablen in den Speicher schreiben auf die jedes PHP Skript zugreifen kann.

So, wenn nun der Memcached Deamon läuft ändern wir in der php.ini den session.save_handler auf "memcache". Damit aber noch nicht genug. Wir müssen nun noch session.save_path ändern... und zwar sollte das dann so aussehen: session.save_path = "tcp://127.0.0.1:11211?persistent=1&weight=1&timeout=3&retry_interval=4" ...Memcached ist ja wie gesagt ein unabhängiges Programm und nutzt zur Kommunikation das TCP Protokoll. Ein Riesenvorteil der sich daraus ergibt ist, dass man von entfernten Rechnern aus, auf die Memcached-Daten zugreifen kann. Sehr interessant also im Falle von PHP-Loadbalancern, damit auch von verschiedenen Webservern aus, auf die selben Sessiondaten zugegriffen werden kann. Es fehlt allerdings eine Authentifizierungsmöglichkeit, so dass die Sicherheit über die Firewall hergestellt werden muss.

Eine 3. Möglichkeit ist das Speichern von Sessiondaten auf einer Ramdisk. Also einer Partition im Arbeitsspeicher, die wie eine Festplattenpartition behandelt wird. Dazu habe ich bereits was geschrieben, daher gibts an dieser Stelle nur noch einen Link: http://murksfurtz.blogspot.com/2008/08/ramdisk-mit-ramfs.html