Deutsch English
Blog
Home
Über tdbengine
Newsletter
Download
Helpware
Chat
Dokumentation
Installation
Konfiguration
Erste Schritte
Variablentypen
Laufzeitschalter
Textdateien
Systemfunktionen
Tabellenfunktionen
Indexfunktionen
Volltext-Indizierung
Memos und Blobs
Semaphoren-Konzept
Fehlermeldungen
Tipps für PHP Programmierer
Locking daemon
Einführungskurs
Befehlsreferenz
HOWTO - Wie kann ich...?
Projekte
Links
Benchmarks
Bug Reporting
Supportanfrage
 
Home    Überblick    Suche    Impressum    Kontakt    Mitglieder
Das Semaphorenkonzept der tdbengine

Viele tdbengines gleichzeitig
Theoretisch können beliebig viele Instanzen der tdbengine gleichzeitig arbeiten. Der Speicherbedarf liegt pro Instanz bei etwa 580 KByte (plus aktuelle Variablen), so dass bei einem freien Speicher von nur 64 MByte mehr als 100 tdbengines gleichzeitig arbeiten können. Das sollte selbst auf gut gesuchten Internet-Seiten locker ausreichen.

Wenn die durchschnittliche Laufzeit inclusive Programmladen/Initialiserung/Aufräumen (unter Last, also wenn bereits viele andere Programme arbeiten) einer Instanz bei 0.1 Sekunde liegt, so lassen sich unter der oben genannten Annahme etwa 3.600.000 Aufträge pro Stunde realisieren, ein Wert, von dem Site-Betreiber nur träumen können.

Freilich gibt es Programme, die länger als 0.1 Sekunde für ihre Arbeit benötigen. Immer, wenn es in den Sekundenbereich geht, sollte man immer über Optimierungsschritte nachdenken. Aber auch bei einer durchschnittlichen Laufzeit von 1 Sekunde wären hier immer noch 360.000 Aufrufe pro Stunde möglich, wenn, ja wenn die Prozesse wirklich unabhängig voneinander arbeiten könnten.

Synchronisation ist notwendig...
Leider ist dem nicht so. Denn wenn die Programme irgendwelche Daten schreiben, muss dafür gesorgt werden, dass sie sich dabei nicht in die Quere kommen. Ohne an dieser Stelle wirklich in Detail zu gehen, sei hier nur ein Beispiel angeführt: Der schreibende Zugriff auf den Index einer Tabelle hat mitunter zur Folge, dass der komplette BTree (die tdbengine speichert Indizes und manchmal auch Tabellen in solchen Strukturen ab) umgebaut wird. Greift wärend der Umbauphase ein anderer Prozess auf diesen Index zu, so erhält er nichts als Datenchaos. Transaktionen (also die Übergänge zwischen konsistenten Zuständen der Daten) müssen gesichert werden. In der Prozesstechnik spricht man bei solchen Übergängen auch von "kritischen Zuständen", den ein Prozess isoliert durchziehen muss, und wärend dessen er nicht von anderen Prozessen gestört werden darf.

Prozesse, die einen gemeinsamen Datenbestand bearbeiten, müssen demnach synchronisiert werden. Die Synchronisation der Prozesse erfolgt bei der tdbengine mit sogeannten Semaphoren. Einen Semaphor kann man sich als Wächter vorstellen, der am Anfang eines kritischen Weges steht und darauf achtet, dass immer nur soviele Prozesse diesen Weg betreten, wie dieser Weg verkraften kann. Handelt es sich dabei nur um einen einzigen Prozess (das ist der häufigste Fall), der auf den Weg geschickt werden darf, so spricht man auch von einem "binären" Semaphor (go - wait). Derzeit unterstützt die tdbengine nur binäre Semaphoren.

Anfangs geht die tdbengine immer ganz auf "Nummer Sicher": Für alle Programme ist ein einziger Semaphor zuständig, und dieser lässt nur immer einen Prozess passieren. Alle anderen müssen warten. Und jetzt schaut die obige Rechnung schon anders aus. Nehmen wir wieder eine durchnittliche Laufzeit von 0.1 Sekunde. Jetzt schaffen wir maximal 36.000 Auträge pro Stunde. Die Möglichkeit der parallelen Bearbeitung bring uns keinen höheren Datendurchsatz, sondern liefert und nur ein Auffangbecken für maximal 100 Aufträge, so dass Spitzen bis zu 1.000 Anforderungen pro Sekunde abgefangen werden können, die dann einfach in die Warteschleife eingereiht werden.

...und schluckt Performance
Angenommen, wir haben nun ein Programm, das für seine Arbeit eine recht lange Zeit benötigt - sagen wir einmal 30 Sekunden - weil beispielsweise ein großer Datenbestand exportiert wird. Während dieser Zeit werden alle anderen Prozesse auf die Warteliste gesetzt, obwohl sie unter Umständen mit diesem Datenbestand garnichts zu tun haben. Es hagelt "cgi overruns", und die Web-Besucher wenden sich anderen Seiten zu. So darf es nicht sein!

Und so ist es auch nicht. Die anfänglich rigorose Sicherheitsstrategie der tdbengine beruht darauf, dass sie keine Ahnung davon hat, was die einzelnen Programme machen, auf welche Datenbestände sie zugreifen und ob dabei überhaupt irgendwelche kritischen Zustände auftreten können. Die tdbengine weiss es nicht, aber Sie wissen es. Und deshalb können Sie auch neue Semaphoren kreieren und damit den Datenfluss einerseits sicher und anderseits performant zu machen. Und genau darauf kommt es an.

So machen wir es richtig
Für den Eingriff gibt es zwei Möglichkeiten: Einträge in die Konfigurationsdatei "tdbengine.ini" und spezielle Semaphorenfunktionen im Programm. Wir wollen uns zunächst den Möglichkeiten der Konfigurationsdatei zuwenden.

Semaphoren in der Konfigurationdatei
Seit der Version 6.2.7 der tdbengine gibt es die Möglichkeit, lokale Konfigurationsdateien anstelle der globalen zu verwenden. Dieses Feature wollen wir gleich ausnutzen und in dem Verzeichnis, in dem sich die kompilierten EASY-Module befinden, eine Datei "tdbengine.ini" einrichten, die zunächst einmal folgende Einträge enthält:

[globals]
logcgi=1
log=./log/cgi.log
semadir=./sema
stopcgi=0
location=http://www.meinserver.de/globale_aktualisierung.html
timeout=10000
overrun=http://www.meinserver.de/zu_viel_los_hier.html

Jetzt müssen Sie noch die Verzeichnisse "log" und "sema" unterhalb des prg-Verzeichnisses anlegen und dem Ausführer der tdbengine (also meist dem anonymen http-Klienten) die dort die Rechte zum Anlegen und Verändern von Dateien geben.

Dann sollten Sie eine hübsche HTML-Datei "zu_viel_los_hier.html" anlegen, die mit freundlichen Worten darauf hinweist, dass sich gerade tausende von Menschen auf dieser Seite tummeln und das System deshalb überlastet ist. Dadurch wird die garstige "cgi-overrun"-Meldung überschrieben. Diese Seite sollte über den angegebenen URL genauso erreichbar sein wie die Seite "globale_aktualisierung.html", in der Sie darauf hinweisen, dass der Datenbestand gerade überarbeitet wird und die dynamischen Inhalte in Kürze wieder zur Verfügung stehen werden.

Schauen wir uns an dieser Stelle die Einträge in der Konfigurationsdatei an:

Mit logcgi=1 wird die tdbengine dazu angehalten, alle Aktivitäten in einem Logfile zu protokollieren. Damit erhalten wir einen genauen Überblick Den Pfad zum Logfile stellen wir mit log=./log/cgi.log ein.

Mit semadir geben wir das Verzeichnis an, in dem die Semaphoren angelegt werden. Diese werden nämlich von der tdbengine als (leere) Dateien betrachtet, die mit Systemaufrufen komplette gesperrt und wieder freigegeben werden. Das hat den großen Vorteil, dass die Sperren auch dann aufgehoben werden, wenn ein tdbengine-Prozess nicht sauber terminiert, was zwar niemals vorkommen sollte, was aber nicht mit absoluter Sicherheit ausgeschlossen werden kann.

Unter timeout wird die Zeit (in Millisekunden) angegeben, die ein Prozess wartet, bevor die Meldung unter overrun ausgegeben wird. Wir stellen sie hier auf 10 Sekunden ein, was für Webanwendungen erst einmal einen guten Wert darstellt (nach 10 Sekunden will der ungeduldige Anwender spätestens wissen, was los ist).

Jetzt richten wir für alle Programme eine eigene Abteilung in der Konfigurationsdatei ein:

[programmname]
sema=xxx

Wenn ein Programm wirklich nur lesend auf einen Datenbestand zugreift und kein anderes Programm auf den gleichen Datenbestand schreibend zugreift, so können wir für dieses Programm den Wert "nosema" für "xxx" eintragen. In diesem Fall wird der Wächter nach Hause geschickt, und beliebig viele Instanzen der tdbengine können das Programm gleichzeitig ausführen.

Alle anderen Programme die entweder lesend oder schreibend oder beides auf einen gemeinsamen Datenbestand zugreifen, erhalten alle einen gemeinsamen Semaphoren.

Dazu ein Beispiel: Angenommen wir programmiern ein Gästebuch mit den drei Einzelprogrammen:

gaestebuch_lesen.prg
gaestebuch_schreiben.prg
gaestebuch_verwalten.prg

Alle diese Programme greifen auf Gästebuch-Datenbestand zu: Tabellen, Konfigurationsdateien usw., und zwar lesend (alle drei) sowie schreibend (die letzten zwei). Damit sich diese nicht in Quere kommen, richten wir für alle drei einen Semaphor ein:

# Gästebuch-Abteilung

[gaestebuch_lesen]
sema=gaestebuch.sema

[gaestebuch_schreiben]
sema=gaestebuch.sema

[gaestebuch_verwalten]
sema=gaestebuch.sema

Hinweis: Mit dem #-Zeichen anfangende Zeilen werden als Kommentar interpretiert.

Jetzt ist dafür gesorgt, dass diese Programme nicht parallel ausgeführt werden, und damit wird wirksam ein Datendurcheinander vermieden.

Semaphoren im Programm
Zusätzlich zu den hier gezeigten Möglichkeiten gibt es noch zwei EASY-Funktionen, mit denen die Semaphoren noch wesentlich feiner bearbeitet werden können:

WaitSema(Semaphor,TimeOut)
EndSema

Zum richtigen Einsatz dieser Funktionen ist eine Information ganz wesentlich: Jedes Programm kann zu einem Zeitpunkt maximal 10 Semaphoren beschäftigen.

Oftmals ist es beispielsweise so, dass die eigentlichen Datenbankzugriffe nur sehr wenig Zeit beanspruchen, die "künsterlische Aufbereitung" hingegen sehr viel. In Bruchteilen einer Sekunde ist ein Datensatz gefunden und gelesen. Jetzt kann eigentlich die Sperre aufgehoben werden, selbst wenn auf den Datensatz mittels GetField() oder Subst() weiterhin zugegriffen wird.

Ganz prinzipiell kann gesagt werden, dass nach dem letzten lesenden oder scheibenden Zugriff dem Wächter mitgeteilt werden kann: "Junge, ich bin durch. Du kannst jetzt den Nächsten 'reinlassen". Dazu dient die Funktion EndSema().

Hinweis: Etwas Ähnliches macht die tdbengine ganz automatisch, indem sie die CGI-Ausgaben zunächst puffert, dann den Semaphor freigibt, und jetzt erst die Daten zum Webserver und damit zum Klienten überträgt. Diese Datenübertragung ist mit Sicherheit nicht kritisch.

Der Einsatz der Funktion WaitSema() ist schon wesentlich heikler, denn er muß unbedingt vor dem Öffnen der ersten Tabelle erfolgen. Denn bereits beim Öffnen werden viele relavante Daten gelesen (im Gegensatz zum Schließen, wo keine Informationen mehr geschrieben werden).

Also nicht:

db:=OpenDB(...);
IF WaitSema('mysema',1000) THEN
  WriteRec(db,...)
ELSE
  ErrorMessage('Tabelle ist derzeit gesperrt')
END

Sondern:

IF WaitSema('mysema',5000) THEN
  db:=OpenDB(...);
  ...
  WriteRec(db,...);
  ...
  EndSema
ELSE
  ErrorMessage('Tabelle ist derzeit gesperrt')
END

Lange Aktualiserungsläufe
Zuletzt noch ein Beispiel aus der Praxis. Gerade im Internet gibt es viele Situationen, in denen eine permanente Aktualsierung der Datenbestände weder möglich noch notwendig ist. Denken Sie einmal an die Suchmachinen. Bei den vielen Zugriffen wäre eine Sequentialiserung der Prozesse durch Semaphoren ein Unding. Also lassen solche Programme zunächst einmal überhaupt keine schreibenden Zugriffe zu, sondern sammeln die neu eingegeben URLs erst einmal in einer eigenen Tabelle. Dann, irgendwann in der Nacht, wenn die Last sinkt, wird eine Kopie des bisherigen Datenbestandes angelegt und in diesen werden die über den Tag gesammelten Daten eingefügt. Dann werden diese und eventuell ältere URLs abgeklappert, die zugehörigen Seiten gelesen und die relevanten Informationen in Suchstrukturen aufbereitet. Schließlich wird der ursprünglich Datenbestand durch den gerade aktualiserten ersetzt.

Insgesamt ist es nur eine einzige, recht kurze Sperre, die hier benötigt wird, nämlich beim Ersatz des Datenbestandes durch die aktualisierte Kopie.

Hinweis: Zusätzlich muss natürlich das Programm, das neue URLs in einer Hilfstabelle sammelt, saubere Sperren einrichten, aber die betreffen nicht die Such-Abfragen und spielen zeitlich auch keine Rolle.

Wenn in einem Datenbestand nur selten, dafür aber lange Reorganistionsaufgaben erfordert, so sollten Sie auf Semaphoren gänzlich verzichten (sema=nosema) und während der Aktualisierung die lesenden Programme gänzlich abschalten. Das geht ganz einfach mit den Bordmitteln der tdbengine:

// Aktualiserung des Datenbestandes.
// Fogende Programme greifen lesend auf den Datenbestand zu:
//   programm_1
//   programm_2
//   programm_3

// 1. CGI-Ausführung für diese Programme stoppen
  VAR Konfig : STRING = 'tdbengine.ini'
  SetIdent(Konfig,'programm_1.stopcgi','1')
  SetIdent(Konfig,'programm_2.stopcgi','1')
  SetIdent(Konfig,'programm_3.stopcgi','1')

// 2. Zehn Sekunden warten, BIS alle Programme beendet sind
  Pause(1000)

// 3. Datenaktualsierung durchführen
  db:=OpenDB(...)
  ...
  CloseDB(db)

// 4. CGI-AUsführung für die anderen Programme wieder Zulassen
  SetIdent(Konfig,'programm_1.stopcgi','0')
  SetIdent(Konfig,'programm_2.stopcgi','0')
  SetIdent(Konfig,'programm_3.stopcgi','0')







tdbengine Anwendungen im Web:

Open-Source Web CMS


Open-Source Bug-Tracking


Free wiki hosting

Open-Source Wiki-System

Kostenloses Foren-Hosting

Diät mit tdbengine 8-)

tdbengine chat
irc.tdbengine.org
#tdbengine

   Copyright © 2003-2004 tdb Software Service GmbH
   Alle rechte vorbehalten. / All rights reserved
   Letzte Änderung: 07.05.2004


ranking-charts.de

Programmers Heaven - Where programmers go!