Parallele Datenbanksysteme
Parallele Datenbanksysteme
Prof. Dr. Erhard Rahm
Die Zukunft kommerzieller Datenbanksysteme liegt in der Parallelverarbeitung. Parallele Datenbanksysteme nutzen die Verarbeitungskapazität zahlreicher Prozessoren - im Rahmen von Multiprozessoren oder Parallelrechnern - zur Leistungssteigerung. Hauptziel ist die Verkürzung der Bearbeitungszeit von Transaktionen und Anfragen (Queries), deren sequentielle Bearbeitung inakzeptable Antwortzeiten verursachen würde.
Eine treibende Kraft für die zunehmende Notwendigkeit von Parallelen DBS sind die ständig wachsenden Datenvolumina, auf denen DB-Operationen und Dienstprogramme abzuwickeln sind. Die größten, kommerziell genutzten relationalen Datenbanken umfassen bereits heute mehrere Terabyte (TB), wobei einzelne Relationen (Tabellen) über 100 Gigabyte (GB) belegen. Die sequentielle Verarbeitung von DB-Operationen ist demgegenüber sehr langsam, trotz ständig schneller werdender Hardware. So ist das sequentielle Einlesen der Daten von Magnetplatten typischerweise auf etwa 5 MB/s beschränkt. Auch die Ausführungszeiten relationaler Operatoren auf hauptspeicherresidenten Daten ist relativ langsam. So sind für die einfachste Operation, die sequentielle Suche im Rahmen eines Relationen-Scans, etwa 1000 Instruktionen pro Satz anzusetzen. Damit ist die sequentielle Suche auf einem Prozessor von 100 MIPS auf 10 MB/s beschränkt (Satzlänge: 100 B). Komplexere Operationen wie Sortieren oder Join-Bildung sind typischerweise um mindestens eine Größenordnung langsamer (< 1 MB/s). Die sequentielle Verarbeitung einer relationalen DB-Operation auf 1 TB würde somit Bearbeitungszeiten von mehreren Tagen erfordern - und dies auch nur, wenn keine Behinderungen mit anderen Transaktionen auftreten (Einbenutzerbetrieb). Offensichtlich sind solche Bearbeitungszeiten in den allermeisten Fällen nicht tolerierbar.
Eine Verschärfung der Problematik ergibt sich für neuere DB-Anwendungen, für die zunehmend objekt-orientierte Datenbanksysteme verwendet werden. Multimedia-Anwendungen verlangen so den Zugriff auf sehr große Datenmengen mit sehr restriktiven Antwortzeitvorgaben, z.B. um digitalisierte Filme in hoher Qualität abzuspielen oder um Videoaufnahmen in Echtzeit digital zu speichern. Ingenieuranwendungen, z.B. bei CAD (Computer-Aided Design) oder CASE (Computer-Aided Software Engineering), verursachen umfangreiche Operationen auf komplex strukturierten Objekten mit einem Berechnungsaufwand, der den einfacher relationaler Operatoren um ein Vielfaches übersteigt. In diesen Bereichen ist der Einsatz von Intra-Transaktionsparallelität daher noch dringlicher, um für den Dialogbetrieb ausreichend kurze Bearbeitungszeiten zu ermöglichen.
Neben der schnellen Bearbeitung komplexer DB-Anfragen müssen Parallele DBS in der Lage sein, für OLTP-Anwendungen durch Nutzung mehrerer Prozessoren hohe Transaktionsraten zu erzielen. OLTP (Online Transaction Processing) ist der kommerziell wichtigste Einsatzbereich heutiger DBS, etwa bei Reservierungssystemen oder Bankanwendungen [Ra93]. Typischerweise sind die Transaktionen hierbei sehr einfach, jedoch in großer Häufigkeit auszuführen. Die Durchsatzforderungen verlangen zwingend die gleichzeitige Bearbeitung zahlreicher unabhängiger Transaktionen, also einen Mehrbenutzerbetrieb bzw. den Einsatz von Inter-Transaktionsparallelität. Eine besondere Herausforderung an Parallele DBS, die von derzeitigen Implementierungen noch nicht zufriedenstellend gelöst ist, besteht nun darin, zugleich Inter- und Intra-Transaktionsparallelität effizient zu unterstützen. Neben der OLTP-Last sollen also auf der gleichen Datenbank zeitgleich eine oder mehrere komplexe Anfragen bearbeitet werden, wobei für letztere durch Nutzung von Intra-Transaktionsparallelität eine kurze Antwortzeit erreicht werden soll. Parallele Datenbanksysteme müssen ferner eine hohe Skalierbarkeit, einer hohe Verfügbarkeit (24-Stunden-Betrieb) sowie eine gute Kosteneffektivität aufweisen.
Im folgenden diskutieren wir die Architektur Paralleler DBS und unterscheiden mehrere Arten der parallelen DB-Verarbeitung. Anschließend betrachten wir, welche grundlegenden Faktoren die Leistungs ml;higkeit Paralleler DBS beschränken. Dabei gehen wir insbesondere auf Probleme mit gemischten Arbeitslasten ein, für die Inter- sowie Intra-Transaktionsparallelität zu unterstützen ind.
Architektur von Parallelen DBS
Untersuchungen zur Parallelisierung der DB-Verarbeitung begannen in den siebziger Jahren vor allem im Kontext von Datenbank-Maschinen. Die Mehrzahl der Vorschläge versuchte durch Nutzung von Spezial-Hardware bestimmte Operationen zu beschleunigen. Im Rückblick ist festzustellen, daß diese Ansätze weitgehend gescheitert sind [DG92]. Der Grund liegt vor allem darin, daß hardware-basierte Ansätze zur DB-Verarbeitung naturgemäß eine nur eingeschränkte Funktionalität unterstützen, so daß nur bestimmte Operationen beschleunigt werden. Vor allem ist jedoch der Aufwand und die Entwicklungszeit so hoch, so daß durch die starken Leistungssteigerungen allgemeiner Prozessoren auch mit einfacheren und flexibleren Software-Lösungen ähnliche Leistungsmerkmale erreicht werden.
Parallele DBS wie Tandems NonStop SQL und Oracle Parallel Server beweisen jedoch, daß software-basierte Lösungen zur parallelen DB-Verarbeitung ein großer kommerzieller Erfolg sind. Sie nutzen konventionelle Mehr-/Parallelrechner -Architekturen mit Standardprozessoren und -peripherie, wobei zur Zeit Installationen mit über 200 Prozessoren bestehen. Insbesondere bei Einsatz von Mikroprozessoren wird damit eine hohe Kosteneffektivität möglich. Zudem kann die rasante Leistungssteigerung bei Mikroprozessoren unmittelbar genutzt werden.
Eine weitere Anforderung an die Hardware-Architektur für Parallele DBS ist die lokale Verteilung der Prozessoren, z.B. im Rahmen eines Clusters in einem Maschinenraum. Denn dies gestattet v.a. den Einsatz eines skalierbaren Hochgeschwindigkeits-Netzwerkes, dessen Übertragungskapazität proportional zur Prozessoranzahl gesteigert werden kann. Eine sehr schnelle Inter-Prozessor-Kommunikation ist für die effiziente Zerlegung einer Transaktion in zahlreiche Teiltransaktionen sowie für die Übertragung großer Datenmengen entscheidend. Weiterhin kann bei lokaler Verteilung am ehesten eine dynamische Lastbalancierung erreicht werden, wobei der aktuelle Systemzustand bei der Lastverteilung berücksichtigt wird. Dies betrifft sowohl die initiale Verteilung eintreffender Transaktionen und Anfragen unter den Verarbeitungsrechnern (Transaktions-Routing) als auch die Rechnerzuordnung von Teilaufträgen während der parallelisierten Abarbeitung.
Diese Anforderungen werden von drei allgemeinen Architekturen erfüllt, die zur Realisierung von Parallelen DBS in Betracht kommen: Shared Everything, Shared-Nothing sowie Shared-Disk (Abb. 1). Die Ansätze können wie folgt charakterisiert werden:
-
Shared-Everything (Multiprozessor-DBS, Abb. 1a)
Die DB-Verarbeitung erfolgt auf einem Multiprozessor. Hierbei liegt eine enge Kopplung vor, bei der sich alle Prozessoren einen gemeinsamen Hauptspeicher teilen, über den eine effiziente Kooperation möglich ist (Verwendung gemeinsamer Datenstrukturen). Die Lastbalancierung ist in der Regel einfach (durch das Betriebssystem) möglich, indem gemeinsame Auftragswarteschlangen im Hauptspeicher gehalten werden, auf die alle Prozessoren zugreifen können.
Allerdings bestehen Verfügbarkeitsprobleme, da der gemeinsame Hauptspeicher nur eine geringe Fehlerisolation bietet und Software wie das Betriebssystem oder das DBS nur in einer Kopie vorliegt. Auch die Erweiterbarkeit ist i.a. stark begrenzt, da mit wachsender Prozessoranzahl der Hauptspeicher bzw. die für den Zugriff auf gemeinsame Hauptspeicherinhalte erforderliche Synchronisierung leicht zum Engpaß werden.
Die Bezeichnung "Shared Everything" resultiert daher, daß neben dem Hauptspeicher auch Terminals sowie Externspeicher von allen Prozessoren erreichbar sind. -
Shared-Nothing (Abb. 1b)
Die DB-Verarbeitung erfolgt durch mehrere autonome Rechner oder Prozessor-Elemente (PE), die jeweils über eigene Hauptspeicher sowie eine eigene Instanz des DBS sowie anderer Software-Komponenten verfügen. Es liegt eine lose Kopplung vor, bei der sämtliche Kommunikationsvorgänge durch Nachrichtenaustausch (message passing) realisiert werden. Die Externspeicher sind unter den Rechnern partitioniert, so daß jede DBS-Instanz nur auf Daten der lokalen Partition direkt zugreifen kann. -
Shared-Disk (Database Sharing, Abb. 1c)
Wie bei Shared-Nothing liegt eine Menge lose gekoppelter PE mit je einer DBS-Instanz vor. Es besteht jedoch eine gemeinsame Externspeicherzuordnung, so daß jedes PE (jedes DBS) direkten Zugriff auf die gesamte Datenbank hat. An das Kommunikationsnetzwerk sind hierbei besonders hohe Anforderungen gestellt, da die Plattenzugriffe aller Rechner darüber abzuwickeln sind (zahlreiche Seitentransfers).
Bisherige Implementierungen und Untersuchungen von Intra-Transaktionsparallelität erfolgten vor allem für Shared-Everything- (Multiprozessoren) und für Shared-Nothing-Systeme. Die Nutzung von Multiprozessoren zur Parallelverarbeitung kann dabei als erster Schritt aufgefaßt werden, da er i.a. auf relativ wenige Prozessoren begrenzt ist (meist unter 10). Die Verwendung eines gemeinsamen Hauptspeichers erleichtert dabei die Parallelisierung, da eine effiziente Kommunikation zwischen Prozessen einer Transaktion und eine einfachere Lastbalancierung unter den Prozessoren möglich wird. Kommerzielle DBS nutzen zunehmend Multiprozessoren für Intra-Transaktionsparallelität, z.B. DB2 oder Informix.
Shared-Nothing- und Shared-Disk-Systeme ermöglichen eine größere Anzahl von Prozessoren als Shared-Everything, so daß eine weitergehende Parallelisierung erreichbar wird. Shared-Nothing erfordert, die Datenbank so unter den Prozessorknoten aufzuteilen, daß alle Rechner gut für die parallele DB-Verarbeitung genutzt werden können. Für relationale DBS erfolgt hierzu i.a. eine horizontale (zeilenweise) Partitionierung von Relationen, die durch Spezifikation einer Verteilungsfunktion (i.a. Hash-Funktion oder Wertebereichsunterteilung) auf einem ausgezeichneten Verteilungsattribut (meist dem Primärschlüssel) definiert wird. In einer Bankanwendung könnte so eine Konto-Relation durch eine Verteilungsfunktion auf dem Attribut Kontonummer unter n Rechnern aufgeteilt werden. Anfragen auf der Kontorelation (z.B. Berechnung der Summe aller Kontostände) können dann parallel von allen n Knoten bearbeitet werden. Problematisch bei dieser Vorgehensweise ist jedoch, daß der Parallelitätsgrad von Anfragen sowie die ausführenden Rechner durch die Datenbankverteilung schon statisch festgelegt sind.
Erweiterungen gegenüber zentralisierten DBS, die von Shared-Nothing-DBS zu unterstützen sind, betreffen neben der Definition einer Datenbankverteilung vor allem die Anfrageoptimierung, um zu effizient ausführbaren, parallelen Ablaufplänen zu gelangen. Hierbei sind mehrere Arten der Parallelisierung zu unterstützen, welche im nächsten Kapitel angesprochen werden. Weiterhin ist ein verteiltes Commit-Protokoll zu unterstützen, um bei verteilter Transaktionsausführung die Atomarität von Transaktionen zu gewährleisten. Intra-Transaktionsparallelität wird bereits seit mehreren Jahren in den beiden kommerziellen Shared-Nothing-Systemen Teradata DBC/1024 und Tandem NonStop-SQL unterstützt. Neuere Entwicklungen sind das parallele DB2/6000 und Sybase Navigational Server [Ra94].
In Shared-Disk-Systemen ist aufgrund der gemeinsamen Externspeicherzuordnung keine physische Datenaufteilung unter den Rechnern vorzunehmen. Insbesondere kann somit eine DB-Operation auch von jedem Rechner gleichermaßen abgearbeitet werden, wodurch eine hohe Flexibilität zur dynamischen Lastbalancierung entsteht. Wenn keine Intra-Transaktionsparallelität genutzt werden soll, kann eine Transaktion daher auch stets an einem Rechner bearbeitet werden (keine Notwendigkeit für verteilte Ausführungspläne sowie ein verteiltes Commit-Protokoll). Zur internen Parallelisierung komplexer Anfragen bestehen daneben weit mehr Freiheitsgrade als bei Shared-Nothing, da weder der Parallelitätsgrad noch die Rechner, welche die Teiloperationen ausführen sollen, vorab durch die Datenverteilung festgelegt sind. In Shared-Disk-DBS wird Kommunikation zwischen den Rechnern v.a. zur globalen Synchronisation der DB-Zugriffe notwendig, wozu i.a. rechnerübergreifende Sperrverfahren verwendet werden. Ein weiteres Problem stellt die Behandlung sogenannter Pufferinvalidierungen dar, die sich dadurch ergeben, daß jedes DBS Seiten der gemeinsamen Datenbank in seinem lokalen Hauptspeicherpuffer hält. Die dadurch erforderliche Kohärenzkontrolle läßt sich jedoch gut ins Sperrprotokoll integrieren, da vor jedem Objektzugriff eine Sperre anzufordern ist, so daß die Aktualität einer gepufferten Objektkopie bei der Sperrvergabe geprüft werden kann [Ra94].
Eine Reihe kommerzieller DBS verfolgen den Shared-Disk-Ansatz, jedoch meist nur für Inter-Transaktionsparallelität. Besondere Bedeutung hat Oracles "Parallel Server"erlangt, da er für zahlreiche Plattformen verfügbar ist bzw. verfügbar gemacht wird. Seit Version 7.1 wird dabei auch eine beschränkte Form von Intra-Transaktionsparallelität unterstützt. Auch IBM setzt mit seinen neuen Architekturen Parallel Sysplex und Parallel Transaction Server, welche von IMS und DB2/MVS genutzt werden, massiv auf den Shared-Disk-Ansatz [Ra94]. Der neue S/390 Parallel Query Server für DB2/MVS bietet dabei eine interne Parallelisierung komplexer Anfragen. Der Zugriff erfolgt hierbei jedoch auf eine asynchron aktualisierte Kopie der Datenbank, auf der keine Änderungen zugelassen sind.
Unterschiedlichen Arten der Parallelverarbeitung
Die Parallelverarbeitung für DB-Anwendungen kann auf unterschiedliche Arten erfolgen, die in [Ra94] klassifiziert wurden. Insbesondere können verschiedene Arten von Verarbeitungsparallelität und E/A-Parallelität unterschieden werden, die beide benötigt werden. Bei der Verarbeitungsparallelität, welche die Nutzung mehrerer Prozessoren verlangt, wurde schon zwischen Inter-Transaktionsparallelität (Mehrbenutzerbetrieb) und Intra-Transaktionsparallelität unterschieden. Besondere Bedeutung in Parallelen DBS kommt dabei der Intra-Transaktionsparallelität zu. In kommerziellen Systemen wird dazu die interne Parallelisierung von SQL-Anfragen unterstützt (Intra-Query-Parallelität). Dies wird durch das relationalen Datenmodell sowie deskriptive und mengenorientierte Anfragesprachen wie SQL ermöglicht, da in einer Anfrage große Datenmengen bearbeitet und umfangreiche Berechnungen vorgenommen werden können, so daß ein hohes Parallelisierungspotential besteht. Ein großer Vorteil dabei ist weiterhin, daß die Parallelisierung vollkommen automatisch im DBS (durch den DBS-Optimierer) und somit transparent für den Programmierer und DB-Benutzer möglich ist. Dies ist ein Hauptgrund für den Erfolg paralleler DB-Verarbeitung und stellt einen wesentlichen Unterschied zur Parallelisierung in anderen Anwendungsbereichen dar, die i.a. eine sehr schwierige Programmierung verlangen.
Eine effektive Intra-Query-Parallelität basiert zumeist auf Datenparallelität, welche eine Partitionierung der Daten erfordert, so daß verschiedene Teilanfragen auf disjunkten Datenpartitionen arbeiten. So können z.B. Suchanfragen auf verschiedenen Datenpartitionen parallel ausgeführt werden. Ein großer Vorteil dabei ist, daß der Parallelitätsgrad proportional zur Datenmenge (Relationengröße) erhöht werden kann. Für komplexere Operationen wie Join-Berechnung und Sortierung existieren zahlreiche parallele Algorithmen, die zum Teil die Eingabedaten dynamisch zwischen den Rechnern umverteilen. Der Einsatz von Funktions- oder Pipeline-Parallelität ist für komplexere Operationen auch möglich, jedoch meist weniger wirkungsvoll als Datenparallelität [DG92, Ra94].
Neben Verarbeitungsparallelität ist für Parallele DBS die Unterstützung von E/A-Parallelität für Externspeicherzugriffe obligatorisch. Denn bei einer Sequentialisierung der Plattenzugriffe würden ansonsten sämtliche Parallelitätsgewinne bei den CPU-bezogenen Verarbeitungsanteilen wieder zunichte gemacht. E/A-Parallelität erfordert, die Daten einer Relation über mehrere Platten zu verteilen. Denn dann kann das Lesen einer ganzen Relation parallel von mehreren Platten erfolgen, so daß eine entsprechende Verbesserung der Zugriffszeit erreicht wird (Unterstützung von Datenparallelität innerhalb einer Anfrage). Daneben können kleinere E/A-Aufträge verschiedener Transaktionen parallel verarbeitet werden, sofern sie unterschiedliche Platten betreffen (Unterstützung von Inter-Transaktionsparallelität). Eine Möglichkeit, E/A-Parallelität zu nutzen, besteht im Einsatz sogenannter Disk-Arrays, welche in der jüngsten Vergangenheit starke Bedeutung erreicht haben [Ra93]. Sie bestehen intern aus mehreren Platten, können jedoch logisch wie eine Platte angesprochen werden, so daß ihr Einsatz prinzipiell keine Änderungen in der nutzenden Software erfordert. Ihr Einsatz zur parallelen DB-Verarbeitung kann jedoch Leistungsprobleme hervorrufen, da die Datenverteilung innerhalb des Disk-Arrays dem DBS nicht bekannt ist. Daher kann die vom DBS vorgenommene Parallelisierung einer Anfrage dazu führen, daß parallele Teiloperationen auf dieselben Platten zugreifen und somit E/A-Engpässe erzeugt werden!
Grenzen der Parallelität
Eine wesentliche Forderung an Parallele DBS ist Skalierbarkeit, insbesondere in Form eines mit der Prozessoranzahl linear zunehmenden Antwortzeit-Speedups (= Antwortzeitverhältnis zwischen sequentieller und paralleler Bearbeitung). Zudem soll, vor allem für OLTP-Anwendungen, der Durchsatz proportional mit der Rechneranzahl gesteigert werden. Abb. 2 zeigt, welche Verlaufsformen für den Antwortzeit-Speedup unterschieden werden können. Idealerweise wird bei n Prozessoren ein Speedup-Wert von n erzielt. Bei einem linearen Antwortzeit-Speedup kann die Antwortzeit auch proportional zur Prozessoranzahl verbessert werden, jedoch i.a. auf einem geringeren Niveau. Typischerweise läßt sich jedoch die Antwortzeit nur bis zu einer bestimmten Prozessoranzahl verkürzen. Eine weitere Erhöhung der Prozessorzahl führt dann ggf. zu einer Reduzierung des Speedups, also einer Zunahme der Antwortzeit.
Die suboptimale Speedup-Entwicklung basiert auf mehreren Ursachen. Zunächst ist der maximal mögliche Speedup inhärent begrenzt durch den Anteil einer Transaktion bzw. Operation, der überhaupt parallelisierbar ist (Amdahls Gesetz). Besteht zum Beispiel die Antwortzeit einer Transaktion nur zu 5% aus nicht-parallelisierbaren (sequentiellen) Verarbeitungsanteilen, so ist der maximal möglich Speedup auf 20 beschränkt, unabhängig davon, wieviele Prozessoren eingesetzt werden. Desweiteren sind es vor allem folgende Faktoren, die den Antwortzeit-Speedup und damit die Skalierbarkeit einer Anwendung beeinträchtigen können [DG92]:
- Startup- und Terminierungskosten
Das Starten und Beenden mehrerer Teiloperationen in verschiedenen Prozessen/ Rechnern verursacht einen Overhead, der mit dem Parallelitätsgrad zunimmt. Da umgekehrt die pro Teiloperation zu verrichtende Nutzarbeit (z.B. Anzahl zu verarbeitender Sätze) sinkt, vermindert sich der relative Gewinn einer Parallelisierung mit wachsendem Parallelitätsgrad. - Interferenz
Die Erhöhung der Prozeßanzahl führt zu verstärkten Wartezeiten auf gemeinsam benutzten Systemressourcen. Vor allem der durch die Parallelisierung eingeführte Kommunikations-Overhead kann sich negativ bemerkbar machen, insbesondere im Mehrbenutzerbetrieb (Inter-Transaktionsparallelität). Neben Wartezeiten auf physischen Ressourcen (CPU, Hauptspeicher, Platten, etc.) kann es auch verstärkt zu Sperrkonflikten zwischen unabhängigen Transaktionen kommen. - Skew (Varianz der Ausführungszeiten)
Die langsamste Teiloperation bestimmt die Bearbeitungszeit einer parallelisierten Operation. Varianzen in den Ausführungszeiten, z.B. aufgrund ungleichmäßiger Daten- oder Lastverteilung oder Sperrkonflikten, führen daher zu Speedup-Einbußen. Das Skew-Problem nimmt i.a. auch mit wachsendem Parallelitätsgrad (Rechneranzahl) zu und beschränkt daher die Skalierbarkeit.
Problematischer ist die Behandlung von Resource-Contention aufgrund des hohen Ressourcenbedarfs komplexer Anfragen, der zu starken Behinderungen gleichzeitig aktiver Transaktionen führen kann. Hier besteht im wesentlichen nur die Möglichkeit, durch geeignete Scheduling-Verfahren die Behinderungen zu kontrollieren (z.B. durch Vergabe von Transaktionsprioritäten). In Parallelen DBS liegt eine weitere Schwierigkeit darin, eine Datenverteilung zu finden, die sowohl eine effektive Parallelverarbeitung für komplexe Anfragen als auch eine möglichst rechnerlokale Bearbeitung von OLTP-Transaktionen zur Begrenzung des Kommunikationsaufwandes ermöglicht. Zur Reduzierung von Resource-Contention stellt sich darüber hinaus die Notwendigkeit dynamischer Parallelisierungsstrategien, welche den Parallelitätsgrad vom aktuellen Systemzustand (CPU-Auslastung, Hauptspeicherverfügbarkeit etc.) abhängig machen. Weiterhin ist eine dynamische Lastbalancierung erforderlich, um eine möglichst gleichmäßige Rechnerauslastung zu erreichen. Dazu sollten Transaktionen bzw. einzelne Teilanfragen möglichst Rechnern mit geringer Auslastung zugewiesen werden. Für solche Kontrollaufgaben bestehen - in Abhängigkeit der jeweiligen Architektur (Shared-Nothing, Shared-Disk oder Shared-Everything) - zahlreiche Verfahrensweisen, die Gegenstand der Forschung sind. Diese Fragestellungen werden u.a. auch an der Universität Leipzig unter Leitung des Autors im Rahmen eines Forschungsprojektes untersucht.
Literatur
Rahm, E.: Hochleistungs-Transaktionssysteme. Vieweg-Verlag, 1993
Last Modified: 10:59am MEZ, June 04, 1996