Plone beschleunigt - ein Zope-Zweispänner mit ZEO
Posted by Jesco Freund at Aug. 11, 2007 1:33 p.m.
Plone als CMS ist nicht verkehrt. Nur leider in der Standard-Installation mit nur einem laufenden Prozess etwas langsam, insbesondere da der Interpreter Lock von Python verhindert, dass mehrere Threads des Prozesses zeitgleich auf die ZODB, die zentrale Datenbank der Zope-Instanz, zugreifen. Effektiv arbeitet also immer nur ein Thread, und das saugt! Glücklicherweise gibt es mit ZEO eine nette Möglichkeit, Zope-Instanz und Datenhaltung zu entkoppeln und damit der parallelen Abarbeitung von Requests vorschub zu leisten.
Ich habe in den letzten Tagen insgesamt drei Plone-Instanzen umgebaut. Ganz ohne Downtime lässt sich das nicht hinbekommen, aber die liegt im Bereich von ca. 15 Minuten. Die Instanzen laufen alle auf einem Gentoo Linux Server, mit lighttpd als Reverse Proxy zur Verteilung der Requests davor. Beim Aufsetzen war im Portage Tree noch 2.9.6 die aktuelle Zope-Version der 2.9er-Serie, aber freundlicherweise hat der Maintainer nicht nur eine neue Version eingestellt (2.9.7, wobei eigentlich 2.9.8 aktuell wäre), sondern dankenswerter Weise auch 2.9.6 gleich aus der Tree entfernt. naargh! Also ist auch gleich noch ein Upgrade der Zope-Version nötig geworden, aber auch das lässt sich bewerkstelligen…
Zuerst also mal Portage zu einer vernünftigen Zope-Version erziehen und das System auf den neuesten Stand der Dinge bringen:
sed 's/.*net-zope.*/>=net-zope\/zope-2.10/' -i /etc/portage/package.mask
emerge -DNu world
emerge net-zope/plone-2.5.3
Für das weitere Vorgehen nehmen wir einmal an, die fragliche Plone/Zope-Instanz befinde sich in /home/foo/zope. Nehmen wir weiter an, es handele sich hierbei um eine gewöhnlich Instanz, die einfach mit mkzopeinstance.py erstellt wurde und mit einer Standard-ZODB ausgestattet ist. Ziel des weiteren Vorgehens:
- Entkoppelung der Instanz-Konfiguration von einer bestimmten Zope-Version
- Verlagerung der Datenhaltung in eine ZEO-Instanz, die über TCP/IP angesprochen wird
- Verteilung der Applikation (Plone) auf zwei (erweiterbar auf n) Zope-Instanzen, die von Lighttpd als Loadbalancer angesteuert werden
Um etwaige Kritik gleich vorweg zu nehmen: ZEO auf einem Mehrbenutzersystem ist nicht gerade die Krone der Sicherheit. Im Gegensatz zu anderen gängigen DBMSen gibt es keine Authentifizierung, und der Zugriff über TCP/IP lädt pöhse User natürlich zu Untaten ein. Da ich allerdings sehr genau weiß, wer auf meinem System ein und aus geht, ist das wohl ein vertretbares Risiko. Für eine Shared-Hosting-Umgebung aber sicherlich nicht.
Zuerst einmal muss die alte Instanz aus dem Weg geschafft werden; schließlich soll das neue Konstrukt ebenfalls wieder unter /home/foo/zope residieren:
su -l -s /bin/bash foo
~/zope/bin/zopectl stop
mv zope zope_old
Nun muss die Verzeichnis-Struktur für die neuen Instanzen aufgebaut werden:
mkdir zope zope/client{0,1} zope/zeo
cd zope
ln -s /usr/lib/zope-2.9.7 zope
Nun kann die ZEO-Datenbankinstanz erzeugt werden. Anschließend übernehmen wir die Daten aus der alten Instanz.
python zope/bin/mkzeoinstance.py zeo 8100
sed 's/8100/127\.0\.0\.1:8100/' -i zeo/etc/zeo.conf
cp ~/zope_old/var/Data.fs zeo/var/
cd ~
mkdir backup
tar -cjpf backup/zope-old.tar.bz2 zope_old
rm -r zope_old
~/zope/zeo/bin/zeoctl start
Wenn alles geklappt hat, startet der letzte Befehl ZEO im Daemon-Modus. Mit netstat lässt sich leicht überprüfen, ob ein Python-Prozess jetzt wie vorgesehen an Port 8100 lauscht. Nun müssen noch die beiden Zope-Instanzen erzeugt werden. Um die Pflege etwas zu erleichtern, bedienen wir beide aus einem zentralen Products-Verzeichnis.
cd ~/zope
mkdir products
python zope/bin/mkzopeinstance.py --dir=client0
python zope/bin/mkzopeinstance.py --dir=client1
rm -r client0/Products client1/Products
cd client0
ln -s ../products Products
cd ../client1
ln -s ../products Products
cd /usr/share/zproduct/plone-2.5.3
cp -r * /home/foo/zope/products
cp -r .??* /home/foo/zope/products
Nun muss noch die Zope-Konfiguration der beiden Client-Instanzen angepasst werden. Hier in Auszügen die gegenüber der Original-Konfiguration veränderten Abschnitte für Client0:
<http-server>
# valid keys are "address" and "force-connection-close"
address 127.0.0.1:8080
# force-connection-close on
</http-server>
#<zodb_db main>
# # Main FileStorage database
# <filestorage>
# path $INSTANCE/var/Data.fs
# </filestorage>
# mount-point /
#</zodb_db>
<zodb_db temporary>
# Temporary storage database (for sessions)
#<temporarystorage>
# name temporary storage for sessioning
#</temporarystorage>
mount-point /temp_folder
container-class Products.TemporaryFolder.TemporaryContainer
<zeoclient>
server localhost:8100
storage 1
name zeostorage
var $INSTANCE/var
# ZEO client cache, in bytes
cache-size 20MB
# Uncomment to have a persistent disk cache
#client zeo1
</zeoclient>
</zodb_db>
# Other storage examples
#
# ZEO client storage:
#
<zodb_db main>
mount-point /
# ZODB cache, in number of objects
cache-size 5000
<zeoclient>
server localhost:8100
storage 1
name zeostorage
var $INSTANCE/var
# ZEO client cache, in bytes
cache-size 20MB
# Uncomment to have a persistent disk cache
#client zeo1
</zeoclient>
</zodb_db>
Die Konfiguration für Client1 ist nahezu identisch – kopiere einfach die Client0-Konfiguration und ändere Client-spezifische Angaben:
cp ~/zope/client0/etc/zope.conf ~/zope/client1/etc/zope.conf
sed 's/client0/client1/' -i ~/zope/client1/etc/zope.conf
sed 's/127\.0\.0\.1:8080/127\.0\.0\.1:8090/' -i ~/zope/client1/etc/zope.conf
Zu guter letzt starten wir die beiden Zope-Instanzen, um zu prüfen, ob sie kollisionsfrei hochkommen:
cd ~/zope
client0/bin/zopectl start
client1/bin/zopectl start
Hat bis hierhin alles fehlerfrei funktioniert, gilt es nun nur noch, Lighttpd dazu zu erziehen, eingehende Requests hübsch auf beide Zope-Instanzen zu verteilen. Außerdem gibt es einige URLs wie z. B. das Login- oder Registrierungsformular, für das ich gerne eine SSL-verschlüsselte Verbindung erzwingen möchte. Hier noch der passende Ausschnitt aus der Lighttpd-Konfiguration:
$SERVER["socket"] == "ip.des.servers:80" {
$HTTP["host"] =~ "(^|\.)www.foo.bar$" {
server.errorlog = "/home/foo/logs/error_log"
accesslog.filename = "/home/foo/logs/access_log"
server.document-root = "/home/foo/htdocs"
url.redirect = (
"^/login_form$" => "https://www.foo.bar/login_form",
"^/join_form$" => "https://www.foo.bar/join_form",
"^/manage$" => "https://www.foo.bar/manage",
"^/manage/(.*)$" => "https://www.foo.bar/manage/$1",
"^/manage_main$" => "https://www.foo.bar/manage_main",
"^/manage_main/(.*)$" => "https://www.foo.bar/manage_main/$1"
)
url.rewrite-once = (
"^/login_form$" => "$0",
"^/join_form$" => "$0",
"^/manage$" => "$0",
"^/manage/(.*)$" => "$0",
"^/manage_main$" => "$0",
"^/manage_main/(.*)$" => "$0",
"^/(.*)$" => "/VirtualHostBase/http/www.foo.bar:80/foo/VirtualHostRoot/$1"
)
proxy.balance = "hash"
proxy.server = ( "/VirtualHostBase" => (
( "host" => "127.0.0.1", "port" => 8080 ),
( "host" => "127.0.0.1", "port" => 8090 )))
}
}
$SERVER["socket"] == "ip.des.servers:443" {
ssl.engine = "enable"
ssl.use-sslv2 = "disable"
ssl.pemfile = "/etc/lighttpd/ssl/foo.keycert.pem"
ssl.ca-file = "/etc/lighttpd/ssl/root.pem"
server.document-root = "/var/www/localhost/htdocs"
accesslog.filename = "/var/log/lighttpd/ssl_access_log"
$HTTP["host"] =~ "(^|\.)www.foo.bar$" {
server.errorlog = "/home/foo/logs/ssl_error_log"
accesslog.filename = "/home/foo/logs/ssl_access_log"
url.rewrite-once = ( "^/(.*)$" =>
"/VirtualHostBase/https/www.foo.bar:443/foo/VirtualHostRoot/$1" )
proxy.balance = "hash"
proxy.server = ( "/VirtualHostBase" => (
( "host" => "127.0.0.1", "port" => 8080 ),
( "host" => "127.0.0.1", "port" => 8090 )))
}
}
Das gewählte Balancing-Verfahren („hash“) verteilt eingehende Requests anhand eines über die Request-URI gebildeten Hash-Wertes auf die Zope-Instanzen. Gleiche URIs werden so stets an dieselbe Instanz delegiert, was dazu führt, dass die Zope-eigenen Caching-Mechanismen (RAMCache) optimal genutzt werden. Die Alternative („fair“) verteilt die Requests gleichmäßiger über beide Instanzen. Im Kaltzustand ist diese Einstellung etwas effektiver, aber bei warmgelaufenen Caches ist hash zumindest auf meinem System etwas performanter. Das unterschiedliche Verhalten der beiden Balancing-Modes sollte man aber unbedingt im Hinterkopf behalten, wenn man mit Tools wie ab oder httperf versucht, das Reaktionsverhalten zu benchmarken. Bei hash würden bei diesen Tools nämlich alle Requests an dieselbe Zope-Instanz delegiert; ein Loadbalancing fände also de facto gar nicht statt.
No comments | Defined tags for this entry: Gentoo, Lighttpd
Comments
No comments

Content is subject to the conditions of the