Sicherheit und Geschwindigkeit

Seit einiger Zeit gibt es auf 4cheaters.de jetzt schon die Forums-Funktionalität. Anfangs habe ich ja eigentlich gar nicht wirklich damit gerechnet, daß hier viel passieren wird, aber inzwischen ist da richtig was los. Mittlerweile sind weit über 12.000 Beiträge geschrieben worden.

Zeit für mich, das ganze mal wieder etwas genauer unter die Lupe zu nehmen. Wenn auch zugegebenermaßen etwas unfreiwillig, weil ein Störenfried am letzten Wochenende gewütet hat und in wenigen Minuten 90 mal den gleichen Beitrag geposted hat. Damit ist einerseits natürlich die Webseite übergelaufen und andererseits haben die Leute auf meiner Seite die Möglichkeit, sich per E-Mail über neue Beiträge in einem Thread informieren zu lassen. Im entsprechenden Board zu Vampire: The Masquerade – Bloodlines nutzen momentan etwas mehr als 20 Leute diese Funktion. Ergo: knapp 2000 Mails gingen da sinnloserweise raus.

Ergo: Generalüberholung ist angesagt.

Schritt eins war relativ schnell realisiert: Eine maxmiale Zahl von Posts pro Zeitabschnitt pro IP-Adresse. Dazu speichere ich jetzt (endlich) die IP zu jedem Beitrag. Hätte ich schon früher machen sollen. Mit einer einfachen SQL-Abfrage kann man dann ganz einfach erkennen, wie viele andere Posts von dieser IP in den letzten 3 Minuten kamen. Sind es 3 oder mehr, dann muß der betreffende erst mal ein bisschen warten. Jenachdem, ob das reicht, kann ich notfalls auch noch eine weitere Stufe einbauen, z.B. maximal 10 Posts pro Stunde oder so. Ich gehe aber mal davon aus, daß das Störenfrieden relativ schnell den Spaß (?) verdirbt.

Schritt zwei ist auch nicht viel schwieriger: Bisher kam es relativ häufig vor, daß Leute ihre Posts doppelt eingetragen haben. Ich denke, das geschieht einfach durch einen – gewollten oder ungewollten – Doppelklick auf den „Absenden“-Knopf oder durch das drücken des Reload-Buttons. Das wurde bisher nämlich nicht abgefangen. Der Doppelklick wird ab sofort schon mal durch ein kleines Javascript erschwert:

<input type="submit"
value="Absenden"
id="submitbutton"
onclick="window.setTimeout('document.getElementById('submitbutton').disabled = true;', 10);">

Dadurch wird der Knopf nach dem drücken ausgegraut und kann nicht mehr angeklickt werden. Das ist aber noch nicht alles. Im Formular übergebe ich jetzt auch noch ein verstecktes Feld mit einer einmaligen ID:

<input
type="hidden"
name="comment_session"
value="<?PHP echo md5($_SERVER[REMOTE_ADDR] . date("D M j G:i:s T Y")); ?>"/>

Diese wird beim Abspeichern überprüft und damit werden Doppel-Posts effektiv verhindert.

Und jetzt kommt der dritte und momentan letzte Schritt, der allerdings mit der ganzen Störenfried-Thematik nichts zu tun hat:

Wie schon geschrieben, habe ich beim Entwickeln der Forums-Funktion nicht mit einem durchschlagenden Erfolg gerechnet. Der kam aber, und damit wurde alles sehr, sehr langsam. Das Erzeugen der oben verlinkten Seite hat zwischen 20 und 50 Sekunden gedauert – und der Webserver war in dieser Zeit sehr beschäftigt. Das Problem war die Darstellung des Threads in der Baumstruktur.

Der Einfachheit halber habe ich dazu einen rekursiven Ansatz gewählt und diesen auch nicht wirklich optimiert. Es wurden daher ein mal alle Beiträge aus der Datenbank gelesen. Danach wurde die Darstellung gestartet, die auf dem Prinzip basiert, daß jedes mal, wenn ein Beitrag dargestellt wurde, wieder alle anderen Beiträge durchsucht wurden, ob sie in der Hierarchie unter dem gerade dargestellten liegen. Bei 10 Beiträgen ist das nicht weiter wild. Macht 10×10 = 100 Überprüfungen. Wenn jetzt aber wie zum Beispiel bei Counterstrike etwa 600 Beiträge da rumlungern, dann sind wir schon bei 600×600 = 360.000 Vergleichen -> Laaangsaaam 😉

Der neue Ansatz ist wesentlich Resourcen schonender:
Es wird ein mal am Anfang zu jedem Beitrag ein „Hierarchie-String“ erzeugt. Das könnte etwa so aussehen:

0001
0001-0002
0001-0003
0001-0003-0004
0005-0006

Hier hat also das Root-Element die Unter-Elemente 1 und 5. Element 1 wiederum hat die Unter-Elemente 2 und 3, von denen 3 noch das Element 4 enthält. Element 6 liegt in Element 5.

Das Aufbauen eines Hierarchie-Strings ist relativ einfach möglich, indem man jeweils den String des Parent-Elements nimmt entsprechend erweitert. Auch einfach ist es danach, diese Strings zu sortieren – in PHP am geschicktesten mit der Funktion asort(). Und danach muß man nur noch ausgeben. Eigentlich interessiert einen an diesem Punkt die Hierarchie an sich gar nicht. Man muß nur entscheiden, wie weit man seine Elemente einrücken muß – und das kann man aus der Länge des Strings ablesen.

Ergebnis: von 20 Sekunden (im bisher besten Fall, wenn dem Server sonst langweilig war) runter auf 0.2 Sekunden (wenn der Server ganz normal arbeitet). Was so ein Algorithmus ausmachen kann…

Schritt vier wird demnächst folgen: Das Ein- und Ausblenden von Threads durch das Klicken auf kleine „+“ und „-“ Icons, ähnlich wie es z.B. im Explorer mit den Verzeichnissen auch funktioniert. Ich muß mich nur noch entscheiden, welchen Ansatz ich hier nehme. Ich würde ja gerne auf DHTML setzen. Die Art, wie Microsoft das in der MSDN-Seite gelöst hat gefällt mir nämlich super-gut 🙂 – nur macht das so viel Arbeit…