Load Balancing für Ruby
vom 28. July 2009Web-Anwendungen werden immer anspruchsvoller und komplexer. Sobald der erste Server in die Knie geht, muss man sich Gedanken über Performance und Lastverteilung machen. Gerade bei Anwendungen, die mit Ruby oder Python geschrieben wurden, stößt man häufig schneller an die Grenzen als mit anderen Skriptsprachen wie PHP oder Perl.
Für Ruby gibt es mehrere Ansätze, wie man die Last auf mehrere Server verteilen kann. Ich möchte hier nun eine der vielen Möglichkeit vorstellen. Für dieses Beispiel verwende ich Lighttpd als Webserver, Thin als Dispatcher, Ramaze als Web-Framework und MySQL zur Datenhaltung.

Fangen wir von vorn an: Wenn ein Request eintritt, wird zuerst entschieden, ob das anzufragende Objekt erst noch generiert werden muss. Wird ein Bild, eine CSS-Datei oder eine andere statische Datei angefragt, kann diese direkt zurückgeliefert werden. Anders sieht es bei Webseiten aus, die zuerst durch Ruby gerendert werden müssen. Hier kommt die Lastverteilung mit ins Spiel. Wir konfigurieren Lighttpd so, dass er die Last auf zwei andere Webserver umleitet. Hier die nötige Konfiguration:
$HTTP["host"] =~ "example\.com" {
server.document-root = "/var/www/"
$HTTP["url"] !~ "^/(css|images|js|libs)" {
proxy.balance = "fair"
proxy.server = ( "/" => (
("host" => "192.168.0.50", "port" => 7000),
("host" => "192.168.0.51", "port" => 7000)
))
}
}
Thin selbst muss natürlich auf den beiden Hosts 192.168.0.50 und 192.168.0.51 gestartet und konfiguriert werden. Das nimmt uns Ramaze ab, in dem wir in der app.rb (der "Startdatei") folgendes hinterlegen:
Ramaze.start( :adapter => :thin, :port => 7000, :file => __FILE__ )
Mit dieser Konfiguration starten wir die Anwendung auf beiden Hosts. Thin kann auch von Hand gestartet werden, der Befehl dazu ist, wer hätte es anders erwartet: "thin". Wenn die Performance in die Knie geht, kann man so einfach eine neue Thin-Instanz auf einem separaten Server starten und einbinden.
Natürlich lässt sich jetzt noch die Datenbank auf mehrere Server verteilen. Hier sollte man allerdings darauf achten, dass der Server mit dem Anwendungscode und der Datenbankserver physikalisch im selben LAN bzw. im selben Rechenzentrum untergebracht sind, denn sonst ist zwar die Last verteilt, aber die Performance geht in den Keller, da mehr Zeit zum Übertragen der Datensätze verbraten wird als das eigentliche berechnen.
Syntax Dokumentation
vom 15. May 2009Nach langer Abstinenz wieder mal ein kleiner Beitrag zum Thema Dokumentation. Neulich schaute ich mir die Syntax zu SQLite an und war begeistert! Eine solch hervorragende Dokumentation habe ich schon lange nicht mehr gesehen.
Normalerweise sehen Syntax-Definitionen so aus:

Seit ich programmiere, wird die Syntax so oder in abgeänderter Form dargestellt. Alles was in eckigen Klammern ist ist optional. Das Pipe-Symbol stellt ein "oder" dar. Die türkisen Hervorhebungen stellen Unterausdrücke dar, die nochmal separat beachtet werden müssen.
In der SQLite Dokumentation wurde der selbe Sachverhalt in einem "Flussdiagramm" bzw. einem Automaten dargestellt:

Welch angenehme Überraschung! Die Syntax lässt sich um einiges leichter lesen und verstehen. Wieso wird diese Darstellungsweise nicht immer verwendet? Weil es früher in ASCII unmöglich war? Oder weil es zu umständlich ist? Ne. Weil es nicht parsbar ist? Kann nicht sein, dafür gibts die BNF. Wenn mir jemand einen triftigen Grund nennen kann, bitte ich dies in den Kommentaren zu tun :)
Auch toll gemacht ist in diesem Zusammenhang ist strfriend.com. Hier werden reguläre Ausdrücke durch eine ähnliche Darstellung visualisiert. Leider unterstützt dieses Tool nur einen Bruchteil vom PCRE Quasi-Standard und man muss sich auf einfache Ausdrücke beschränken.
Und wenn wir schon beim Graphen malen sind: Wer "mal schnell" ein UML-Diagramm zeichnen möchte, aber dafür kein Zeichenwerkzeug bemühen will, kann sich mal yuml.de anschauen. Damit lassen sich über die URL Diagramme generieren!
Funktionale- und Integrationstests mit Selenium
vom 7. December 2008Webseiten testen macht kein Spaß. Vor allem dann nicht, wenn man ein zweiseitiges Formular schon zum 25sten Mal ausgefüllt hat und immer noch ein Bug drin ist. Da ich gerne Dinge so weit es geht automatisiere, habe ich mich vor etwas längerer Zeit (diesen Blog-Post wollte ich schon vor ca. einem Jahr erstellen :) nach Test-Frameworks für Webseiten umgesehen.
Es gibt unzählige, allen voran SimpleTest und PHPUnit. Allerdings haben all diese Frameworks ein Problem: Die Testfälle sind dermaßen aufwendig zu erstellen, dass es bei größeren und komplexeren Seiten einfach zu zeitaufwändig und langweilig ist. Ein Beispiel hierfür aus dem Tutorial von SimpleTest:
function testWeAreTopOfGoogle() {
$this->get('http://google.com/');
$this->setField('q', 'simpletest');
$this->click("I'm Feeling Lucky");
$this->assertTitle('SimpleTest - Unit Testing for PHP');
}
Um diesen Vorgang zu beschleunigen, habe ich mich weiter auf die Suche gemacht und bin dann eines Tages auf Selenium gestoßen. Hiermit lassen sich die Tests viel einfacher erstellen.
Selenium besteht aus mehreren Komponenten. Zum Einen hat man zum Erstellen der Tests ein Firefox-Plugin, mit dem Tests einfach aufgezeichnet werden können:

Man drückt auf "Record" und beginnt mit seinem Testcase. Asserts lassen sich über das Kontextmenü hinzufügen. Will man beispielsweise sicherstellen, dass auf der aktuellen Seite eine Wortkombination erschienen ist, markiert man diese und wählt im Kontextmenü "assertTextPresent". Auch Formulareingaben, Seitensprünge usw. werden aufgezeichnet. Will man kompliziertere Testfälle aufsetzen, kann man die aufgenommenen Schritte nachträglich bearbeiten und erweitern. Reichen die Befehle von Selenium nicht aus, kann immer noch auf JavaScript ausgewichen werden.
Hat man seinen Testfall fertig, kann man diesen auf unterschiedliche Weise abspeichern. Zum Einen kann man den Testfall als simple HTML-Tabelle abspeichern, das ist das Standardformat von Selenium. Eine weitere Möglichkeit ist das Konvertieren in einer der vielen Programmiersprachen (Java, PHP, Ruby, Python, ...), so dass man am Ende ein ähnliches Ergebnis wie beim SimpleTest hat, nur mit viel weniger Tipparbeit. Dieses Script kann dann einfach ausgeführt werden.
Ein weiteres Modul von Selenium ist die Umgebung, die die aufgenommenen Testfälle starten kann. Hier gibt es wiederum mehrere Möglichkeiten. Die Serverkomponente ist für den "Hausgebrauch" relativ umbedeutend, und spielt erst eine entscheidende Rolle, wenn wirklich bei jedem Code-commit alle Testfälle ausgeführt werden sollen. Die einfachere Variante ist der Test-Runner, der komplett in JavaScript geschrieben ist, und somit direkt im Browser läuft. Das hat den Vorteil, dass man nicht an einen bestimmten Browser gebunden ist und somit seine Testfälle auf jedem beliebigen Browser testen kann, der einigermaßen neues JavaScript unterstützt.

Ich habe mir, um den Testvorgang weitgehend zu automatisieren, eine Subdomain angelegt, die - wenn ich sie aufrufe - automatisch alle Tests durchlaufen werden. So kann ich in regelmäßigen Abständen und bei größeren Code-Änderungen immer prüfen, ob noch alles tut so wie es soll.
SetUp und TearDown?
Ein Problem gibt es dennoch mit Selenium: Wenn man beispielsweise ein Formular auf ungültige Eingaben testet, werden diese Testdaten evtl. in die Datenbank geschrieben und bleiben auch nach Beendigung des Testlaufs bestehen. Dies ist natürlich nicht gewollt. Eine SetUp- und eine TearDown-Methode muss her. Leider gibt es so etwas in Selenium nicht, da das Framework logischerweise die eigene Datenbank nicht kennt und auch keinen Zugriff darauf hat.
Um dieses Problem zu umgehen, habe ich mir ein kleines PHP-Skript geschrieben, dass diese Funktionalität "nachrüstet". Abgespeckt sieht es ungefähr so aus:
define('SECRET', '12345xyz');
if (isset($_GET['action'])) {
$key = isset($_GET['secret']) ? $_GET['secret'] : '';
if ($key == SECRET) dispatch($_GET['action'], $_GET);
else die('Wrong secret, sorry.');
}
function dispatch($action, $params) {
$preparator = new SeleniumPreparator();
if (method_exists($preparator, $action)) {
PicoraActiveRecord::connect(CONNECTION_STRING);
output($preparator->$action($params));
} else die("No method $action found.");
}
class SeleniumPreparator {
public function setup() {
// Make temp-Entry visible
$entry = $this->getTestentry();
$entry->display = 1;
$entry->allow_comments = 1;
$entry->save();
return true;
}
public function teardown() {
$entry = $this->getTestentry();
$entry->display = 0;
$entry->allow_comments = 0;
// Clear all comments and annotations
$comments = Comment::findAllByField('Comment', 'entry_id', $entry->id);
foreach ($comments as $c) {
$c->delete();
}
$entry->annotations = '[]';
$entry->save();
return true;
}
public function activateAllComments() {
$entryID = $this->getTestentry()->id;
$comments = Comment::findAllByField('Comment', 'entry_id', $entryID);
foreach ($comments as $c) {
$c->approved = 1;
$c->save();
}
return true;
}
private function getTestentry() {
return Entry::findByField('Entry', 'url_name', 'selenium-testentry');
}
}
Aufgerufen wird das Skript mit test_setup.php?secret=12345xyz&action=setup, um beispielsweise die Setup-Methode aufzurufen, die den Testeintrag sichtbar macht. Diesen Aufruf kann man natürlich auch mit Selenium machen. Mit dieser Technik lassen sich dann auch administrative Aufgaben wie das Aktivieren von Kommentaren durchführen, ohne manuell einzugreifen.
Ich hoffe, dass durch Selenium mehr Web-Developer ihre Webseite testen, denn das hat das Web dringend nötig.
ETag
vom 4. February 2008Ich habe mich heute wieder mal etwas mit Web-Caching beschäftigt und bin dabei auf eine sehr interessante Sache gestoßen: den ETag. Dieser ETag ist ein HTTP-Header der mit HTTP/1.1 eingeführt wurde. Mit ihm kann man feststellen ob sich der Content auf dem Server geändert hat, bevor man ihn herunterläd.
Da es dazu noch keinen deutschen Wikipedia-Artikel gab, hab ich schnell einen angelegt, so muss ich das ganze hier nicht noch einmal erklären :)
Das ganze funktioniert folgendermaßen (irb):
http = Net::HTTP.new('aaron-mueller.de', 80)
# <Net::HTTP aaron-mueller.de:80 open=false>
res = http.send_request('GET', '/rss')
# <Net::HTTPOK 200 OK readbody=true>
etag = res.header['etag']
# ""947e30c936""
res = http.send_request('GET', '/rss', nil, {'If-None-Match' => etag})
# <Net::HTTPNotModified 304 Not Modified readbody=true>
res.code
# "304"
res.body
# nil
Eine HTTP-Verbindung wird aufgebaut und eine GET-Anfrage gesendet. Die Antwort (response) enthält den Header ETag, den wir uns zwischenspeichern. Bei der zweiten Anfrage der gleichen Datei senden wir den Header If-None-Match mit dem zuvor gespeicherten ETag mit. Als Antwort erhalten wir einen 304-Statuscode, allerdings diesmal ohne body.
Moderne Webserver unterstützen das automatische Generieren und Anhängen eines ETags an jede Response. In LigHTTPd haben die Option den Prefix etag.* (Siehe Doku). Wenn der Webserver dies nicht unterstützt oder man nur bestimmte Anfragen mit den ETag verstehen will (oder man aber dynamischen Content hat statt statische Seiten) kann man die Header-Manipulation auch eine Stufe darüber (in der Scriptsprache seiner Wahl) erledigen. Hier ein Beispiel in PHP:
// Generate the ETag from the last Entry in the Database
$res = Entry::findAll('Entry', array(
'order' => 'date DESC, id DESC',
'limit' => 1
));
$etag = '"' . substr(md5($res[0]->date . $res[0]->title), 0, 10) . '"';
header("ETag: $etag");
// Check ETag
if (isset($_SERVER['HTTP_IF_NONE_MATCH']) &&
$_SERVER['HTTP_IF_NONE_MATCH'] == $etag) {
header('HTTP/1.0 304 Not Modified');
die();
}
Wirklich genutzt wird dieses tolle Feature momentan leider nur bei Feed-Readern. Es wird langsam Zeit (HTTP/1.1 ist schon über 10 Jahre alt!) sowas unter den Reload-Button der Webbroser zu legen. Bei einem Klick kann statt einem kompletten Refresh der Seite nur eine Meldung ala "nothing changed" angezeigt werden. Das würde einiges an Traffic sparen.
Kommentar schreiben,
del.icio.us
Bug-freier Code ist kein Traum
vom 24. November 2007Tony Hoare (das ist der der den Quicksort-Algorithmus erfunden hat) hat auf einer Informatikveranstaltung an der TU München sein Konzept zu Werkzeugen für die Erstellung von 100% fehlerfreiem Code vorgestellt. Sein Konzept bezieht sich auf einen "Verifying Compiler", also ein Compiler der nicht nur die Syntax checkt, sondern auch auf Semantik und Fehlerfreiheit testet.
Die Idee ist folgende: Man wandle das Programm in eine mathematische Formel um und versuche diese zu beweisen. Wenn die Formel nicht beweisbar ist, ist der Code fehlerhaft. Damit das funktioniert muss das Programm Assertions beinhalten, um die entsprechende Formel zu bilden. Erfahren habe ich darüber in der Computer Zeitung Nr. 45 vom 5.11.2007. Noch mehr Informationen zu diesem speziellen Compiler gibts hier.
Interessant ist, das die langweilige Beweisführung die wir in "Logik und KI" und "Lineare Algebra" ständig machen, einen (mir endlich) ersichtlichen, praktischen Nutzen hat.
Android Die Handy-Plattform von Google
vom 12. November 2007Seite ein paar Stunden kann man das Android SDK von Google herunterladen (~55MB). Entwickelt wird mit Java6 auf Basis eines Linux-Kernels. Als Entwicklungsumgebung wird Eclipse empfohlen (es existiert schon ein recht brauchbares Plugin).

Im SDK dabei sind gut dokumentierte Beispiele, eine hervorragende Dokumentation sowie tools und Scripts für die Emulation und das Deployment. Das Ganze macht nach der ersten Besichtigung einen sehr guten Eindruck, mehr zur Architektur und dem SDK gibt es später :)
1