HADES Ein hochverfügbares, verteiltes Main-Memory-Datenmanagementsystem Vom Fachbereich Informatik der Technischen Universität Darmstadt genehmigte Dissertation zur Erlangung des akademischen Grades eines Doktor-Ingenieurs (Dr.-Ing.) von Diplom-Informatiker Matthias Meixner aus Fulda Referenten: Prof. Alejandro Buchmann, Ph. D. Prof. Dr.-Ing. Felix Gärtner Tag der Einreichung: 4. Mai 2004 Tag der mündlichen Prüfung: 17. Dezember 2004 Darmstädter Dissertationen D17 Inhaltsverzeichnis 1 Einführung 1 1.1 Publish/Subscribe . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.2 IP-Übernahmemechanismen . . . . . . . . . . . . . . . . . . . . . . . 6 1.3 Ziele und Ergebnisse dieser Arbeit . . . . . . . . . . . . . . . . . . . 8 1.4 Überblick über die Arbeit . . . . . . . . . . . . . . . . . . . . . . . . 10 2 Annahmen über die Systemumgebung 2.1 2.2 2.3 Grundlagen der Fehlertoleranz 11 . . . . . . . . . . . . . . . . . . . . . 11 2.1.1 Fehlermodelle . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 2.1.2 Fehlerabdeckung . . . . . . . . . . . . . . . . . . . . . . . . . 13 2.1.3 Fehlertoleranz, Redundanz und Hochverfügbarkeit . . . . . . 14 2.1.4 Verteilte Systeme und Fehlertoleranz . . . . . . . . . . . . . . 15 Die HADES-Systemumgebung . . . . . . . . . . . . . . . . . . . . . . 15 2.2.1 HADES-Fehlermodell . . . . . . . . . . . . . . . . . . . . . . 15 2.2.2 Hardwareplattform . . . . . . . . . . . . . . . . . . . . . . . . 16 2.2.3 Speicherressourcen . . . . . . . . . . . . . . . . . . . . . . . . 17 2.2.4 Netzwerkressourcen . . . . . . . . . . . . . . . . . . . . . . . 18 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 Zusammenfassung i ii INHALTSVERZEICHNIS 3 Überblick: Aufbau von HADES 21 3.1 Fehlertolerante Kommunikationsschicht . . . . . . . . . . . . . . . . 21 3.2 HADES-API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 3.3 HADES-DB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 3.4 Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 4 Fehlertolerante Kommunikationsschicht 4.1 27 Überblick über die Implementierung . . . . . . . . . . . . . . . . . . 29 4.1.1 Designentscheidungen . . . . . . . . . . . . . . . . . . . . . . 29 4.1.2 Aufbau der Kommunikationsschicht . . . . . . . . . . . . . . 30 4.2 Client-Programmierschnittstelle . . . . . . . . . . . . . . . . . . . . . 32 4.3 Verwaltung der Rechner im Cluster . . . . . . . . . . . . . . . . . . . 34 4.3.1 Hinzufügen von Clusterknoten . . . . . . . . . . . . . . . . . 34 4.3.2 Entfernen von Clusterknoten . . . . . . . . . . . . . . . . . . 37 4.3.3 Abfragen der Clusterknoten . . . . . . . . . . . . . . . . . . . 39 4.4 Ausfallerkennung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 4.5 Versenden von Nachrichten und Antworten . . . . . . . . . . . . . . 46 4.6 Signale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 4.7 Zusammenfassung 52 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 HADES-Konzepte 53 5.1 Management globaler Daten . . . . . . . . . . . . . . . . . . . . . . . 53 5.2 Datenverteilung und Adressierung . . . . . . . . . . . . . . . . . . . 55 5.3 Zweistufige Adressierung . . . . . . . . . . . . . . . . . . . . . . . . . 59 5.4 Berechnung der Servertabelle . . . . . . . . . . . . . . . . . . . . . . 61 5.5 Zusammenfassung 64 . . . . . . . . . . . . . . . . . . . . . . . . . . . . INHALTSVERZEICHNIS iii 6 Verteiltes Datenmanagementsystem 65 6.1 Client-Programmierschnittstelle . . . . . . . . . . . . . . . . . . . . . 65 6.2 Datenoperationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 6.2.1 Konsistenzeigenschaften . . . . . . . . . . . . . . . . . . . . . 66 6.2.2 Verteilung der Servertabelle . . . . . . . . . . . . . . . . . . . 68 6.2.3 Absicherung von Änderungen an globalen Daten . . . . . . . 70 6.2.4 Datentabellen anlegen und löschen . . . . . . . . . . . . . . . 71 6.2.5 Servertabelle auslesen . . . . . . . . . . . . . . . . . . . . . . 72 6.2.6 Adressierung . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 6.2.7 Daten schreiben . . . . . . . . . . . . . . . . . . . . . . . . . 73 6.2.8 Daten Lesen . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 6.2.9 Daten Löschen . . . . . . . . . . . . . . . . . . . . . . . . . . 76 6.2.10 Selektieren und Sortieren . . . . . . . . . . . . . . . . . . . . 76 6.2.11 Erweiterbarkeit . . . . . . . . . . . . . . . . . . . . . . . . . . 78 6.3 Umkopieren für Redundanz oder Lastverteilung . . . . . . . . . . . . 78 6.4 Transaktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79 6.4.1 Arbeiten ohne Transaktionen . . . . . . . . . . . . . . . . . . 79 6.4.2 Arbeiten mit Transaktionen . . . . . . . . . . . . . . . . . . . 80 6.4.3 Inter-Cluster-Transaktionen . . . . . . . . . . . . . . . . . . . 92 Audit-Logging und Backup . . . . . . . . . . . . . . . . . . . . . . . 96 6.5.1 Audit-Logging . . . . . . . . . . . . . . . . . . . . . . . . . . 96 6.5.2 Backup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 6.5 6.6 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101 iv INHALTSVERZEICHNIS 7 Leistungsmessung 103 7.1 Testumgebung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103 7.2 Testablauf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104 7.3 Benchmark . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105 7.3.1 Lesen und Schreiben von Daten außerhalb von Transaktionen 105 7.3.2 Lesen und Schreiben von Daten innerhalb von Transaktionen 7.3.3 Vergleich zwischen writePage/readPage und writePageSet/readPageSet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117 7.3.4 Transaktionen 7.3.5 Verteilte Transaktionen . . . . . . . . . . . . . . . . . . . . . 122 7.3.6 Selektieren 7.3.7 Auswirkung der Slice-Anzahl . . . . . . . . . . . . . . . . . . 124 7.3.8 Operationen während einer Datenneuverteilung . . . . . . . . 125 7.3.9 Auswirkungen der Prozessorleistung . . . . . . . . . . . . . . 127 108 . . . . . . . . . . . . . . . . . . . . . . . . . . 118 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122 7.4 Vergleich mit PostgreSQL . . . . . . . . . . . . . . . . . . . . . . . . 127 7.5 Berkeley DB 7.6 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130 8 Vergleich mit anderen Systemen 8.1 8.2 131 Bestehende Datenbanksysteme . . . . . . . . . . . . . . . . . . . . . 131 8.1.1 Konventionelle Datenbanksysteme . . . . . . . . . . . . . . . 132 8.1.2 Main-Memory-Datenbanksysteme . . . . . . . . . . . . . . . . 133 8.1.3 PRISMA/DB . . . . . . . . . . . . . . . . . . . . . . . . . . . 135 8.1.4 TimesTen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136 Distributed Hash-Tables . . . . . . . . . . . . . . . . . . . . . . . . . 137 INHALTSVERZEICHNIS 8.3 8.4 v LH* Schema – Scalable Distributed Data Structure . . . . . . . . . . 140 8.3.1 LH* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141 8.3.2 LH*m . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142 8.3.3 LH*s . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143 8.3.4 LH*g und LH*RS . . . . . . . . . . . . . . . . . . . . . . . . 145 Distributed Shared Memory . . . . . . . . . . . . . . . . . . . . . . . 147 8.4.1 Shared Virtual Memory . . . . . . . . . . . . . . . . . . . . . 147 8.4.2 Fehlertolerantes DSM . . . . . . . . . . . . . . . . . . . . . . 150 8.5 Checkpointing mit Parity . . . . . . . . . . . . . . . . . . . . . . . . 151 8.6 Distributed Reliable Cache 8.7 Middleware Mediated Transactions und X2 TS . . . . . . . . . . . . . 153 8.8 Active Disks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154 8.9 ISIS Toolkit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155 8.10 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . 151 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156 9 Zusammenfassung und Ausblick 157 9.1 Anforderungen und Ergebnisse . . . . . . . . . . . . . . . . . . . . . 157 9.2 Weitere Arbeiten und Verbesserungen . . . . . . . . . . . . . . . . . 159 9.2.1 Sensornetze . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159 A Grundlagen von Speichertechnologien 163 A.1 Festplatten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163 A.2 RAID . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164 A.2.1 RAID-Level 0 . . . . . . . . . . . . . . . . . . . . . . . . . . . 165 A.2.2 RAID-Level 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . 166 A.2.3 RAID-Level 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . 168 A.2.4 RAID-Fazit . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169 A.3 Solid State Disks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169 A.4 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170 vi INHALTSVERZEICHNIS B Algorithmen 171 B.1 Gesamtalgorithmus zur Erkennung ausgefallener Knoten . . . . . . . 171 B.2 Berechnung der Servertabelle . . . . . . . . . . . . . . . . . . . . . . 173 C Schnittstellenkurzreferenz 175 C.1 Programmierschnittstelle zur Kommunikationsschicht . . . . . . . . . 175 C.2 HADES Programmierschnittstelle . . . . . . . . . . . . . . . . . . . . 177 Literaturverzeichnis 181 Kapitel 1 Einführung In den letzten Jahren haben sich durch den Fortschritt die technischen Voraussetzungen dramatisch geändert: Hauptspeicher ist billiger geworden und kostet heute soviel wie vergleichbarer Plattenplatz vor 10 Jahren. Die Festplattenkapazit ät ist um den Faktor 100 gestiegen, während der Durchsatz nur um den Faktor 10 gesteigert werden konnte und die Zugriffszeit noch weiter zurückblieb. Damit verschlechtert sich das Verhältnis aus Festplattenkapazität und Durchsatz bzw. Zugriffszeit alle 10 Jahre um den Faktor 10 [GS00]. Gleichzeitig ist die verfügbare Rechenleistung dramatisch angestiegen und die Schere zwischen Rechenleistung und Zugriffszeiten geht immer weiter auseinander. Für viele Anwendungen stellt nicht mehr die Rechenleistung einen Flaschenhals dar, sondern die hohe Zugriffszeit von Festplatten. Deshalb ist es notwendig alternative Speichersysteme zu untersuchen, wenn man insgesamt zu schnelleren Systemen kommen möchte. Konventionelle Speichersysteme sind der Flaschenhals, m üssen also neu überdacht werden. Diese Arbeit entwickelt daher ein Speichersystem, das diesen Flaschenhals nicht besitzt und darüberhinaus weitere interessante Eigenschaften aufweist. In den folgenden Abschnitten möchte ich zunächst an ausgewählten Beispielen die Probleme aufzeigen und im weiteren Verlauf dieser Arbeit eine L ösung erarbeiten. 1.1 Publish/Subscribe Publish/Subscribe-Systeme gehören zu den eventbasierten Systemen. Diese werden verwendet, um größere verteilte Systeme aufzubauen, die die Eigenschaft haben, daß 1 2 KAPITEL 1. EINFÜHRUNG Client Event Broker F1 F2 F3 F1 F2 F1 F2 DB Event Notification System DB F1 F1 F2 F2 F3 F3 F1 F2 DB Abbildung 1.1: Eventbasiertes System die beteiligten Komponenten nur relativ lose miteinander gekoppelt sind [CNRW98]: • Eine Komponente benötigt kein Wissen darüber, welche anderen Komponenten im System existieren, sie braucht nur die Formate und Strukturen der Events zu kennen, die für sie interessant sind. • Man kann Komponenten hinzufügen und entfernen, ohne daß die anderen Komponenten direkt davon betroffen sind. Die verschiedenen Komponenten kommunizieren über Notifications (Benachrichtigungen), die das Auftreten eines bestimmten Ereignisses signalisieren. Abbildung 1.1 zeigt ein Beispiel für ein solches System. Das Event Notification System übernimmt dabei die Aufgabe, Benachrichtigungen, die von Produzenten erzeugt und publiziert werden, an Konsumenten weiterzuleiten. Da ein Produzent auch gleichzeitig Konsument für eine andere Art von Benachrichtigungen sein kann, werden Produzenten und Konsumenten in der Abbildung gleichermaßen als Client bezeichnet. Innerhalb des Event Notification Systems übernehmen Event-Broker die Aufgabe, Benachrich- 1.1. PUBLISH/SUBSCRIBE 3 tigungen weiterzuleiten und dabei mit Filtern bereits so zu filtern, daß Benachrichtigungen nur an die Stellen weitergeleitet werden, an denen es auch Konsumenten für diese Benachrichtigungen gibt. Damit dies möglich ist, geben Konsumenten dem Event Notification System per Subscription bekannt, für welche Ereignisse sie sich interessieren. Der Name Publish/Subscribe“ leitet sich aus eben dieser Funktions” weise von Publizieren und Abonnieren von Nachrichten ab. Eine weitere wichtige Möglichkeit die Datenmenge zu reduzieren und nur benötigte Informationen weiterzuleiten, besteht darin, Events zu aggregieren, d.h. mehrere Events auszuwerten und zusammenzufassen [BBC+ 04]. Im Unterschied zu einfachen Filtern ist hierfür aber die Speicherung von Zustandsinformationen notwendig: Es müssen mehrere Benachrichtigungen angesammelt und gespeichert werden, bevor sie aggregiert werden können. Das Publish/Subscribe Prinzip wurde bereits in mehreren Systemen umgesetzt. Beispiele sind: Rebecca [Müh02], Jedi [CNF01], Siena [CRW01], Gryphon [Cen], und Hermes [PB02] Unterschiede zwischen den Systemen liegen in der Art der Subscription (channel based, topic based, content based), im Event-Dispatching (zentral, verteilt, broadcast) sowie den eingesetzten Filter- und Routingverfahren. Der interne Aufbau dieser Systeme sowie deren Unterschiede spielen in der weiteren Betrachtung aber keine Rolle. Produzent Filter1 Filter2 Filter3 Konsument Abbildung 1.2: Datenfluß in einem eventbasierten System Abbildung 1.2 zeigt ein Beispiel für den Datenfluß von einem Produzenten zu einem Konsumenten. Dabei werden jeweils nur die aktiven, für diesen Datenfluß relevanten Filter gezeigt. Da man die Aggregation von Events ebenfalls als Filter auffassen kann, z.B. das Zusammenfassen mehrerer zeitlich aufeinanderfolgender Events als Filtern im Zeitbereich, möchte ich im folgenden beliebige Transformationen von Benachrichtigungen als Filter bezeichnen und nicht zwischen den verschiedenen M öglichkeiten, Daten zu bearbeiten, unterscheiden. In dieser einfachen Ausf ührung kann keine Garantie gegeben werden, ob bzw. wie oft Benachrichtigungen ausgeliefert werden. Falls z.B. ein Filter ausfällt, gehen alle Benachrichtigungen verloren, die von diesem Filter empfangen aber noch nicht weitergeleitet wurden. Es ist auch m öglich, daß Benachrichtigungen vervielfältigt werden, wenn Empfangsquittungen für Benachrichtigung verloren gehen und Benachrichtigungen deshalb wiederholt werden. Im Unterschied zu verbindungsorientierten Protokollen wie z.B. TCP ist keine Ende-zu-Ende Fehlerkorrektur möglich, da die Identität der beteiligten Komponenten und noch nicht 4 KAPITEL 1. EINFÜHRUNG einmal deren Anzahl bekannt sind. Eine Fehlerkorrektur kann jeweils nur w ährend der Übertragung einer Benachrichtigung zwischen zwei benachbarten Komponenten eingesetzt werden. Dieses Verhalten ist für Anwendungen nicht tolerabel, die davon abhängen, daß Benachrichtigungen exakt einmal übertragen werden, wie z.B. Anwendungen, die das Auftreten von Ereignissen zählen. Besonders wichtig wird die Eigenschaft, daß Benachrichtigungen exakt einmal übertragen werden, sobald Aggregate eingesetzt werden, da ein einzelnes Aggregat sehr viele einzelne Benachrichtigungen repr äsentieren und so ein Verlust dementsprechend eine große Auswirkung haben kann. Produzent Filter1 DB1 verteilte Transaktion Filter2 DB2 Filter3 Konsument DB3 einfache Transaktion Abbildung 1.3: Transaktionelle Nachrichten Dieses Problem läßt sich durch den Einsatz von Transaktionen und persistenter Speicherung also z.B. durch den Einsatz von Datenbankmechanismen l ösen (Abbildung 1.3). Wichtige Benachrichtigungen werden weitergegeben, indem sie innerhalb einer (verteilten) Transaktion aus einer Datenbank bzw. Tabelle gelöscht werden und in die nächste eingefügt werden. Da Transaktionen immer komplett oder gar nicht ausgeführt werden, wird so sichergestellt, daß Benachrichtigungen immer exakt einmal weitergegeben werden. Auch beim Ausfall eines Knotens bleiben Nachrichten sicher gespeichert und stehen nach einem Neustart des Knotens wieder zur Verf ügung. Je nach Fall werden sowohl einfache als auch verteilte Transaktionen eingesetzt. In diesem Aufbau vereinfachen sich auch die Anforderungen an die eingesetzten Filter: Da alle Zustandsinformationen in den Datenbanken gehalten werden, brauchen die Filter selbst keine Zustandsinformation mehr speichern und werden somit zustandslos. Dies betrifft auch die Speicherung der Events, die f ür eine Aggregation benötigt werden. Auf diese Weise vereinfacht sich der Aufbau der Filter: Eine aufwendige Zustandsspeicherung, Fehlererkennung und Behandlung ist nicht notwen- 1.1. PUBLISH/SUBSCRIBE 5 dig, bei einem Ausfall kann ein Filter einfach neu gestartet werden. Der Einsatz von Transaktionen verhindert, daß Daten mehrfach bearbeitet werden. Somit k önnen auch mehrere identische Filter parallel gestartet werden, um auf diese Weise sehr einfach Fehlertoleranz zu erreichen: Wenn ein Filter ausfällt, wird dessen Arbeit automatisch von einem identischen anderen Filter erledigt; eine eventuell vor dem Ausfall begonnene Transaktion wird automatisch abgebrochen. Dieses Vorgehen ist sehr ähnlich zu der Funktionsweise von Middleware Mediated Transactions, wie sie z.B. von X2 TS zur Verfügung gestellt werden [LMB00, LT01]. Allerdings beschränken sich diese Systeme darauf Transaktionen zur Verfügung zu stellen, während sie den Aspekt der persistenten Speicherung an angeschlossene Datenbanken auslagern. Durch dieses Vorgehen ergeben sich jedoch auch Probleme: Die Zeit, die f ür ein Commit benötigt wird, limitiert die Ende-zu-Ende Laufzeit, da der n ächste Filter Daten erst dann lesen kann, wenn das Commit des vorangegangenen Filters abgeschlossen ist. Auch der Einsatz von optimistischen Verfahren bringt nur teilweise eine Verbesserung: Zwar könnte der Lesezugriff bereits früher stattfinden, aber auch in diesem Fall kann ein Commit erst dann abgeschlossen werden, wenn alle vorangegangenen Transaktionen erfolgreich mit Commit beendet werden konnten, denn falls ein Commit fehlschlägt darf auch das Commit der nächsten Filterstufe nicht durchgeführt werden, da diese dann ungültige Daten gelesen hat. Somit wird das Problem nur bis zum nächsten Commit aufgeschoben, aber nicht gelöst. Aus diesen Gründen wird ein Datenmanagementsystem benötigt, das ein Commit möglichst schnell abschließen kann. Fällt das Datenmanagementsystem aus, so gehen zwar keine Benachrichtigungen verloren, aber der Datenfluß ist unterbrochen und auf gespeicherte Benachrichtigungen kann bis zum Neustart nicht zugegriffen werden. Um dies zu vermeiden wird ein Datenmanagementsystem benötigt, das eine hohe Verfügbarkeit und Zuverlässigkeit bereitstellt, so daß auch bei einem Ausfall von einzelnen Komponenten oder Rechnern weiterhin ein gesicherter Betrieb ohne Unterbrechung gew ährleistet ist. Steht ein solches Datenmanagementsystem zur Verfügung, so bietet es aber auch für andere Anwendungen innerhalb von eventbasierten Systemen Vorteile. Ein Beispiel hierfür ist mobile Publish/Subscribe [CFC+ 03]: Viele eventbasierte Applikationen zeichnen sich dadurch aus, daß sie eine Initialisierungsphase ben ötigen, in der sie Notifikationen beobachten, um in einen konsistenten Zustand zu gelangen. In mobilen Szenarios wird diese Phase hauptsächlich benötigt, um die Applikation an die aktuellen Kontextinformationen anzupassen, die nur lokal verf ügbar sind. Diese Phase läßt sich beschleunigen, wenn die benötigten Informationen von den Brokern 6 KAPITEL 1. EINFÜHRUNG in einem Cache auf Vorrat zwischengespeichert werden, von wo sie bei einer Subscription abgerufen werden können, um so die Initialisierungsphase zu beschleunigen. Werden diese Daten in einem hochverfügbaren Datenmanagementsystem gehalten, so läßt sich die Qualität dieser Daten gegenüber einem System verbessern, das bei einem Ausfall diese Daten verliert. Mit einem Ähnlichen Problem beschäftigt sich [Spi00]: Möchte man nicht nur aktuell auftretende Ereignisse betrachten, sondern wie sie sich über die Zeit verändern, wie sie zusammenhängen und voneinander abhängen so wird eine Historie der Ereignisse benötigt. Wird diese Historie im eventbasierten System gespeichert, so kann sie allen interessierten Clients zur Verfügung gestellt werden, ohne daß jeder Client eine eigene Historie verwalten müßte. 1.2 IP-Übernahmemechanismen Client Request IP−Adresse IP−Adresse Server Server Abbildung 1.4: IP-Übernahme bei zustandslosen Diensten Wird für Dienste eine hohe Verfügbarkeit benötigt, so ist es erforderlich diese redundant auszulegen. IP-Übernahmemechanismen stellen eine weit verbreitete Möglichkeit dar, dies zu realisieren [FHS03]. Ein Dienst wird in diesem Ansatz durch eine IP-Adresse (und Portnummer) repräsentiert. Clients verwenden diese Adresse, um mit dem Dienst via UDP oder TCP zu kommunizieren. Fällt der Dienst aus, so übernimmt ein anderer identischer Dienst die IP-Adresse. Zusammen mit der Eigenschaft, daß viele Clients eine Verbindung wieder aufbauen wenn sie unterbrochen wurde, um auf diese Weise Kommunikationsunterbrechungen im WAN auszugleichen, kann der Ausfall des Dienstes maskiert werden. Ganz so einfach funktioniert 1.2. IP-ÜBERNAHMEMECHANISMEN 7 Client Request IP−Adresse IP−Adresse Server Server zuverlässiges Datenmanagement− system Abbildung 1.5: Auslagerung der Zustandsinformation das aber nur mit zustandslosen Diensten, die keine Informationen zu einer ClientVerbindung speichern (Abbildung 1.4). Einfache Webserver sind ein Beispiel f ür diesen Fall. Viele Dienste benötigen jedoch Zustandsinformationen, um Anfragen korrekt beantworten zu können. Fällt ein Server aus, so geht diese Information verloren. Um dies zu vermeiden kann der Dienst so modifiziert werden, daß der Zustand auf ein zuverlässiges, ausfallsicheres Speichersystem ausgelagert wird, womit der eigentliche Dienst zustandslos wird (Abbildung 1.5). Die Antwortzeit dieses Systems hängt ganz entscheidend vom eingesetzten Speichersystem ab, da jede Operation, die den Zustand ändert, der einem Client zugeordnet ist, zunächst abgespeichert werden muß, bevor eine Antwort an den Client gesendet werden kann, damit nach einem Serverausfall ein anderer Server nahtlos an der gleichen Stelle die Arbeit wieder aufnehmen kann. Damit dies m öglich ist, muß von mehreren Rechnern auf das Speichersystem zugegriffen werden k önnen. Von besonderer Wichtigkeit ist die Verfügbarkeit des Speichersystems, da die Verfügbarkeit des Gesamtsystems nicht besser als die Verfügbarkeit des Speichersystems sein kann. 8 KAPITEL 1. EINFÜHRUNG Insgesamt wird für diesen Anwendungszweck also ein sehr schnelles, ausfallsicheres Speichersystem benötigt. 1.3 Ziele und Ergebnisse dieser Arbeit Die soeben vorgestellten Anwendungsgebiete stellen sehr ähnliche Anforderungen an das Datenmanagementsystem: Die Menge der zu speichernden Daten ist nicht besonders groß (weit weniger als ein Gigabyte). Gleichzeitig haben die Daten nur eine geringe Lebensdauer und werden sehr oft geändert. In Publish/Subscribe-Systemen zum Beispiel brauchen sie nur solange gespeichert werden, bis sie erfolgreich an den oder die nächsten Broker weitergereicht werden konnten. Allen gemeinsam ist auch, daß die geforderten Eigenschaften nur unzureichend von festplattenbasierten Speichersystemen erfüllt werden können (vgl. Anhang A). Ein Einsatz von Festplatten in Publish/Subscribe-Systemen oder in hochverf ügbaren Systemen, die stateless-Server mit zusätzlichem hochverfügbarem Speicher für Zustandsinformationen einsetzen, führt aufgrund der hohen Zugriffszeiten von Festplatten zu hohen Ende-zu-Ende Verzögerungen bzw. zu langen Antwortzeiten. Den Flaschenhals stellt hierbei der Schreibzugriff auf die Festplatte dar. Erst nachdem dieser abgeschlossen ist, sind die Daten persistent gespeichert. Damit muß als Teil der Speicherung der Daten mindestens auf einen Schreibzugriff gewartet werden, was die hohe Zugriffszeit verursacht. Dabei spielt es keine Rolle, welche Mechanismen eingesetzt werden, also ob z.B. ein DBMS zur Verwaltung der Daten verwendet wird oder ob die Daten direkt von der Anwendung verwaltet werden. Die Zeit für diesen Schreibzugriff dominiert die insgesamt benötigte Zeit. Selbst bei den derzeit schnellsten Platten (IBM Ultrastar, 15.000 U/min) fallen durchschnittlich 3, 4ms Positionierzeit und 2ms Latenz (rotational delay) an [IBM]. RAID-Systeme, die in anderen Fällen zur Steigerung der Leistung verwendet werden, helfen hier nicht weiter, da sie zwar den Durchsatz erhöhen und die Verfügbarkeit verbessern können, aber keine Vorteile beim Schreiben für die Zugriffszeit bringen. Bei RAID-Level 5 steigt sie sogar an, da zur Berechnung der neuen Parity-Pr üfsumme vor dem Schreiben der Daten zusätzlich die alten Daten und Parity-Prüfsumme gelesen werden müssen. Mit Solid State Disks (SSD) gibt es zwar alternative Speichertechnologien, die niedrigere Zugriffszeiten bieten, jedoch sind diese entweder wenig geeignet oder sehr teuer: Flash-ROM basierte SSDs sind vergleichsweise billig, aber der darin verwendete Flash-Speicher kann nicht beliebig oft beschrieben werden. Typische Werte liegen bei 100.000 Lösch-Schreib-Zyklen, z.B. für StrataFlash Memory (J3) [Int]. Auf eine 1.3. ZIELE UND ERGEBNISSE DIESER ARBEIT 9 Log-Datei finden im normalen Betrieb aber viele Schreibzugriffe statt (mind. ein Zugriff pro Transaktion), so daß diese Zahl innerhalb weniger Tage erreicht werden kann. Somit sind Flash-ROM SSDs für diesen Einsatz ungeeignet. DRAM-basierte SSDs unterliegen keiner Limitierung der Schreib-L ösch-Zyklen, sind aber um zwei Größenordnungen teurer als herkömmliche Festplatten. Für den Preis einer Athena-2 plus SSD mit 800MBytes bekommt man etwa 8 komplette Rechner mit insgesamt 8GBytes Hauptspeicher, wobei für ein hochverfügbares System mindestens zwei Platten notwendig sind. Ziel dieser Arbeit war es deshalb, ein hochverfügbares Datenmanagementsystem zu entwickeln, das auf den Einsatz von Festplatten zur sicheren Speicherung verzichten kann. HADES (Highly available distributed main-memory data management system) basiert daher auf der Idee, Persistenz nicht durch den Einsatz von externen Speichermedien, sondern durch den Einsatz von Redundanz zu erreichen. Daten werden im Hauptspeicher von mehreren Knoten eines Clusters gespeichert. Bei einem Rechnerausfall existiert somit noch eine Kopie und es kann weiterhin ohne Unterbrechung auf die Daten zugegriffen werden. Dies verspricht insgesamt niedrige Zugriffszeiten und eine hohe Verfügbarkeit. Da als Speichermedium keine externen Speichermedien eingesetzt werden, besteht keine Einschränkung auf blockorientierte Schreib- und Lesezugriffe, sondern es k önnen beliebige Operationen mit beliebiger Datengranularität implementiert werden. Darüberhinaus bietet die Verteilung auf mehrere Rechner Möglichkeiten zur Parallelisierung. Auf diese Weise können als Nebeneffekt weitere Optimierungen eingesetzt werden, um die Bearbeitung von Daten weiter zu beschleunigen. In dieser Arbeit entwickle ich ein hochverfügbares Datenmanagementsystem, wie es von den vorgestellten Anwendungen benötigt wird. Im einzelnen enthält diese Arbeit folgende Beiträge: • Beschreibung einer neuen Lösung (HADES). • Beschreibung einer Implementierung auf Standard-Hardware. • Evaluation eines Prototypen. • Vergleich mit anderen Arbeiten. HADES stellt erstmals ein Datenmanagementsystem zur Verfügung, das Daten verteilt im Hauptspeicher eines Clusters ablegt, Persistenz durch Fehlertoleranz garantiert und dabei für niedrige Zugriffszeiten optimiert wurde. HADES benötigt keine spezielle Hardware, sondern kommt mit preiswerter Standard-Hardware aus. Die 10 KAPITEL 1. EINFÜHRUNG niedrigen Zugriffszeiten werden ausschließlich durch Kommunikations- und Datenmanagementstrategien erreicht, die dafür sorgen, daß möglichst direkt mit wenigen Netzwerkzugriffen auf die Daten zugegriffen werden kann. Mit dem erstellten Prototypen wurden mehrere Meßreihen durchgeführt, die die Vorteile von HADES aufzeigen. U.a. konnte damit belegt werden, daß die Zugriffszeiten etwa eine Gr ößenordnung unter den Zugriffszeiten von schnellen Festplatten liegen. Es gibt verschiedene Systeme, die auf den ersten Blick eine große Ähnlichkeit zu HADES zu haben scheinen, die aber im Detail große Unterschiede aufweisen, die dazu f ühren, daß sie für Anwendungen ungeeignet sind, die niedrige Zugriffszeiten ben ötigen. 1.4 Überblick über die Arbeit Kapitel 2 gibt eine kurze Einführung in die Grundlagen der Fehlertoleranz, leitet daraus die Anforderungen an die Hardware ab und legt fest, welche Fehler vom System toleriert werden können. Kapitel 3 gibt einen Überblick über den Aufbau von HADES und die eingesetzten Komponenten. Kapitel 4 beschreibt die in HADES eingesetzte Kommunikationsschicht, die die Kommunikation im Cluster steuert, den Cluster verwaltet sowie dessen Konten überwacht. Kapitel 5 beschäftigt sich damit, wie die Daten im Cluster verteilt werden und welche Probleme bei der Verteilung gelöst werden mußten. Kapitel 6 stellt die Implementierung des Prototypen vor und beschreibt die zur Verfügung stehenden Operationen. In Kapitel 7 werden die Ergebnisse vorgestellt, die bei der Evaluation des Prototypen gewonnen wurden. Den Vergleich mit anderen Systemen und Arbeiten gibt Kapitel 8. Kapitel 9 schließt die Arbeit mit einer kurzen Zusammenfassung und gibt einen Ausblick auf weitere Themen. Anhang A gibt einen ausführlichen Überblick über Zugriffslatenzen und wie sie sich nicht vermeiden lassen. Der Pseudocode der in der Arbeit vorgestellten Algorithmen findet sich in Anhang B. Eine Kurzreferenz in Anhang C stellt die wichtigsten zur Verfügung stehenden Methoden der Programmierschnittstelle von HADES vor. Kapitel 2 Annahmen über die Systemumgebung Bevor man beginnt ein System zu implementieren, muß nicht nur festgelegt werden, was das System leisten können soll, sondern es müssen auch verschiedene Annahmen über die Randbedingungen gemacht werden. Im Falle von HADES sind die zwei wichtigsten: • Mit welchen Fehlern muß HADES umgehen können? • Welche Hardware kann/soll eingesetzt werden? Diese beiden Punkte sind nicht unabhängig, sondern sie beeinflussen sich stark gegenseitig. Die gewünschte Fehlertoleranz bestimmt, welche Hardware-Umgebung eingesetzt werden muß, und umgekehrt limitieren die Kosten der Hardware welche Fehlertoleranz erreicht werden kann. Darüberhinaus wird von diesen beiden Punkten auch die Leistungsfähigkeit des Gesamtsystems mitbestimmt, so daß hier ein Kompromiß zwischen Leistungsfähigkeit, Fehlertoleranz und Kosten gefunden werden muß. In diesem Kapitel möchte ich daher erläutern, welcher Kompromiß für HADES gewählt wurde und auf welchen Grundlagen er basiert. 2.1 Grundlagen der Fehlertoleranz Fehlertoleranz ist die Eigenschaft eines Systems, auch beim Auftreten von Fehlern noch korrekt zu funktionieren. Genauer unterscheidet man dabei zwischen dem Defekt (fault) und dem von diesem Defekt verursachte Fehler (error), der dann zum 11 12 KAPITEL 2. ANNAHMEN ÜBER DIE SYSTEMUMGEBUNG Ausfall (failure) eines Systems führen kann [Gär01, Lap92]. Fehlertolerante Systeme können zwar keine Defekte verhindern, jedoch können sie die Auswirkungen von Defekten begrenzen, so daß ein Ausfall des Systems verhindert werden kann. Ein fehlertolerantes System kann allerdings nicht beliebige Fehler tolerieren, es gibt immer Fehler die schlimmer“ in ihrer Auswirkung sind als alle Fehler, die man ” beim Entwurf des Systems berücksichtigt hat. Gleichzeitig kann es aber einen erheblichen Mehraufwand bedeuten, wenn man einen zusätzlichen Fehler tolerieren können möchte. Daher muß ein Kompromiß gefunden werden, der einerseits hinreichend viele Fehler abdeckt, die in der Realität auftreten können, andererseits aber nur Fehler berücksichtigt, die mit vertretbarem Aufwand abgefangen werden können. Voraussetzung dafür, daß ein Fehler abgefangen werden kann, ist, daß nicht das komplette System ausfällt, sondern nur Teile davon, so daß die verbliebenen Komponenten die Aufgaben weiterhin erfüllen können. Während dieser Fall bei einzelnen Rechnern nur in Sonderfällen vorkommt, da Rechner nach einem Defekt normalerweise komplett ausfallen, ist dies in verteilten Systemen die Regel. In verteilten Systemen ist es unwahrscheinlich, daß alle Rechner gleichzeitig ausfallen. Normalerweise ist nur ein Teil der Komponenten betroffen, während der andere Teil weiterhin funktioniert. Diese inhärente Redundanz in verteilten Systemen kann daher gut ausgenutzt werden, um fehlertolerante Systeme zu bauen. 2.1.1 Fehlermodelle Bevor man sich mit Fehlertoleranz beschäftigen kann, gilt es zunächst einmal zu klären: Was ist ein Fehler?“ [Gär01, S. 34f] gibt hierfür eine nützliche Klassifikation ” von formalisierten Fehlermodellen: Fail-Stop: Prozesse können anhalten und senden danach nur noch Fehlermeldungen als Antwort. Dies ist z.B. der Fall falls ein einzelner Prozeß auf einem Rechner abstürzt. Für alle folgenden Versuche mit diesem Prozeß zu kommunizieren, wird vom Betriebssystem eine Fehlermeldung generiert (vgl. hierzu auch [SS83]). Crash: Prozesse können abstürzen (crash) und antworten danach nicht mehr. Im Unterschied zum vorangegangenen Fall werden keine Fehlermeldungen als Antwort generiert. Dies passiert z.B. wenn ein kompletter Rechner abst ürzt. General Omission: Zusätzlich zu Crash können beliebig viele Nachrichten auf den Kommunikationsverbindungen z.B. durch Störungen verloren gehen. 2.1. GRUNDLAGEN DER FEHLERTOLERANZ 13 Crash-Recovery: Nach einem Absturz fangen Prozesse wieder an zu arbeiten, jedoch können dabei Zustandsinformationen verlorengegangen sein. Dies w äre z.B. der Fall bei einem Rechner, der nach einem Absturz selbst ändig oder von einem Watchdog initiiert neu startet. Daten, die nicht auf einem persistenten Speichermedium gespeichert sind, gehen dabei verloren. Timing Failure: Die Antwort eines Prozesses kommt zu spät an (Timeout) [TvS02]. Dies kann auf zu geringe Rechengeschwindigkeit zurückzuführen sein, kann aber auch als Folge einer Fehlerkorrektur entstehen, wenn diese nicht schnell genug den Fehler korrigieren konnte. Falls z.B. eine Nachricht verloren gegangen ist und wiederholt werden mußte, so kann sie nun unter Umst änden zu spät ankommen. Auch wenn sich Prozesse aufgrund von gesetzten Locks gegenseitig blockieren, kann dieser Fehler auftreten. Byzantine: Prozesse können sich beliebig und sogar bösartig verhalten und fehlerhafte Nachrichten senden [LSP82]. Ein bekanntes Beispiel f ür diesen Fall ist der unter dem Namen Pentium-Bug“ bekannt gewordene Fehler einer be” stimmten Pentium-Serie, der unter bestimmten Bedingungen zu fehlerhaften Ergebnissen bei der Division führte [Vau95]. Hieraus ergibt sich eine – wenn auch nicht perfekte – Hierarchie von Fehlermodellen, wobei Fail-Stop das einfachste Fehlermodell ist, das die wenigsten Fehler abdecken kann und Byzantine das umfassendste Fehlermodell in dieser Aufz ählung darstellt. General Omission spielt in dieser Aufzählung eine Sonderrolle, da es sich als einziges direkt mit Fehlern bei der Übertragung beschäftigt. 2.1.2 Fehlerabdeckung In der Realität tauchen diese Fehlermodelle allerdings nicht in ihrer Reinform auf, sondern es wird sich in der Regel um Mischformen handeln, die mit weiteren praxisrelevanten Annahmen angereichert werden müssen. Die Fehlermodelle haben in der Praxis unterschiedliche Relevanz. So kann zum Beispiel nur in seltenen F ällen davon ausgegangen werden, daß ein abgestürzter Prozeß noch in der Lage ist Fehlermeldungen zu senden. Dies kann zutreffen, wenn nur ein Prozeß auf einem Rechner angehalten hat und das Betriebssystem an seiner Stelle Fehlermeldungen als Antwort sendet, aber sobald ein Rechner komplett abgestürzt ist oder vom Netz getrennt wurde, so ist dies nicht mehr der Fall. Bei der Auswahl des Fehlermodells muß daher ein Kompromiß zwischen einer Abdeckung möglichst vieler praxisrelevanter Fehler und einer einfachen Realisierbarkeit des Fehlertoleranzmechanismus eingegangen werden. Ein fehlertolerantes Programm, das auf dem Fehlermodell Fail-Stop aufbaut, kann in der Praxis nur sehr 14 KAPITEL 2. ANNAHMEN ÜBER DIE SYSTEMUMGEBUNG begrenzte Fehler abfangen. Mit Rechnerausfällen nach einem Stromausfall, Hardwaredefekt oder Absturz des Betriebssystems kann es nicht umgehen. Byzantine andererseits deckt zwar alle Fehler ab, aber die Erkennung und Behandlung byzantinischer Fehler erfordert einen sehr hohen Aufwand, der h äufig nicht gerechtfertigt ist, da die meisten Fehler bereits unter der Annahme von Crash“ behandelt wer” den können. Vor allem der hohe Kommunikationsaufwand ist für den vorgesehenen Einsatzzweck problematisch (vgl. [LSP82]). Dazu kommt, daß auch byzantinische Fehler häufig in einem Absturz resultieren, wenn man von systematischen Fehlern wie dem Pentium-Bug absieht. Ein Beispiel dafür ist ein defekter Speicher, der Daten vergißt“. Da man nicht davon ausgehen kann, daß dies nur Nutzdaten betrifft ” und Programmcode und Zeiger in Datenstrukturen verschont bleiben, wird dieser Fehler typischerweise früher oder später auch zu einem Absturz führen [BWKI03]. Bei der Auswahl des Fehlermodells ist zu beachten, daß die Auswahl eines zu allgemeinen Fehlermodells, das viele Fehler abdecken kann, die Verf ügbarkeit in der Praxis sogar negativ beeinflussen kann, da mehr Komponenten eingesetzt werden müssen und damit die Wahrscheinlichkeit des Ausfalls einer Komponente ansteigt [Pow92]. 2.1.3 Fehlertoleranz, Redundanz und Hochverfügbarkeit Da sich das Auftreten von Fehlern nicht verhindern läßt, kann ein fehlertolerantes System nur dafür sorgen, daß das Auftreten von Fehlern nicht zum Ausfall des Systems führt [McK]. Dieses wird als Maskieren von Fehlern bezeichnet. Der Schl üssel zum Maskieren von Fehlern ist Redundanz. Redundanz bezeichnet dabei Anteile des Systems, die während des fehlerfreien Betriebs nicht benötigt werden. Redundanz wird sowohl zur Erkennung als auch zur Korrektur eines Fehlers ben ötigt und kann dabei in den unterschiedlichsten Ausprägungen auftreten. Beispiele für Redundanz sind zusätzliche Abfragen im Programm, doppelte Auslegung von Komponenten oder auch eine wiederholte Ausführung. Zusätzliche Abfragen in einem Programm, um Fehler zu erkennen, führen zu zusätzlichen Programmzuständen und werden deshalb auch als Speicherredundanz bezeichnet (der Zustandsautomat, der das Programm repräsentiert benötigt für die zusätzlichen Zustände Speicher) [Gär01]. Strukturredundanz ändert die Struktur eines Systems und vervielfältigt Komponenten, damit bei einem Ausfall die verbliebenen Komponenten die Aufgabe der ausgefallenen übernehmen können. Zeitredundanz schließlich ändert im Fehlerfall das zeitliche Verhalten und ermöglicht so Fehler zu korrigieren. Ein Beispiel hierfür ist das Wiederholen von Nachrichten, falls diese bei der Übertragung verloren gegangen sind [Gär01]. 2.2. DIE HADES-SYSTEMUMGEBUNG 15 Hochverfügbarkeit sagt zunächst nur etwas über die Verfügbarkeit aus, wie dies erreicht wird spielt keine Rolle. Die Verfügbarkeit kann durch verschiedene Maßnahmen verbessert werden [Dou99], z.B.: • besonders robuste Konstruktion • sorgfältige Qualitätskontrolle • Schulung der Anwender • regelmäßige Wartung • Fehlertoleranz Fehlertoleranz ist dabei eine sehr wichtige Methode, Hochverfügbarkeit zu erreichen, so daß Hochverfügbarkeit und Fehlertoleranz oft als sehr eng verwandt gelten. 2.1.4 Verteilte Systeme und Fehlertoleranz Zwischen verteilten Systemen und Fehlertoleranz besteht eine besondere Beziehung: Einerseits bietet die in verteilten Systemen meist vorhandene Redundanz eine gute Möglichkeit, Fehlertoleranz zu erreichen, andererseits steigt mit der Gr öße des Systems die Wahrscheinlichkeit, daß ein Fehler auftritt, weshalb sehr große Systeme auf Fehlertoleranz angewiesen sind, um überhaupt funktionieren zu können. Wann genau Fehlertoleranz eingesetzt werden muß, hängt dabei von der konkreten Anwendung ab. Eine wenige Stunden benötigende Number-Crunching-Anwendung, die auf einem Cluster läuft, in dem durchschnittlich einmal im Monat ein Rechner ausfällt, kann so z.B. auf Fehlertoleranz und den damit verbundenen Aufwand verzichten, da die Berechnung einfach ohne Verluste mit den gleichen Daten neu gestartet werden kann. Verzichtet man dagegen bei einer Berechnung, die mehrere Monate benötigt und auf dem gleichen Cluster laufen soll, auf Fehlertoleranz, so wird man nur mit extrem viel Glück die Berechnung abschließen können. 2.2 2.2.1 Die HADES-Systemumgebung HADES-Fehlermodell Aus den in den vorangegangenen Abschnitten genannten Gründen soll das HADESFehlermodell für die einzelnen Rechner auf dem Fehlermodell Crash“ basieren, d.h. ” entweder ein Rechner arbeitet fehlerfrei oder er arbeitet überhaupt nicht. 16 KAPITEL 2. ANNAHMEN ÜBER DIE SYSTEMUMGEBUNG Rechner müssen in der Lage sein, innerhalb einer bestimmten Zeit zu reagieren. Die Art der Antwort spielt keine Rolle, wichtig ist nur, daß ein Rechner ein Lebenszei” chen“ von sich gibt, ansonsten wird ein Ausfall angenommen (Timeout). Auf diese Weise wird Timing Failure auf Crash reduziert. Auf dem Netzwerk dürfen bis zu einem Gewissen Grad Verluste von Nachrichten auftreten. Die Verluste sollen aber innerhalb des Bereichs liegen, der von TCP oder einem ähnlichen Protokoll behandelt werden kann. Auch wenn gewisse Nachrichtenverluste verkraftet werden können, so ist dies doch nicht gleichzusetzen mit der Fehlerannahme General Omission, da diese beliebig viele Nachrichtenverluste zul äßt. Als zusätzliche Bedingung möchte ich annehmen, daß innerhalb eines bestimmten Zeitraums höchstens ein Rechner ausfallen darf. Die Dauer dieses Zeitraums bestimmt sich aus der Dauer, die benötigt wird, um die Redundanz wiederherzustellen. Die Länge dieses Zeitraums bestimmt sich aus der Menge der gespeicherten Daten und liegt z.B. bei 100 MBytes Daten in Bereich von 1-2 Minuten (vgl. Abschnitt 7.3.8). Außerdem möchte ich annehmen, daß durch eine Netzwerkunterbrechung höchstens ein Rechner vom Netz abgetrennt wird. Kapitel 2.2.2 gibt ein Beispiel, wie eine Vernetzung aussehen könnte, die diese Bedingung erfüllt. Zusammenfassend sollen also folgende Annahmen gemacht werden: • Rechner fallen nur per Crash“ aus. ” • Es gibt keine oder nur sehr geringe Datenverluste auf dem Netzwerk. • Es tritt keine Netzwerkpartitionierung auf. (Ausnahme: Die Verbindungen zu höchstens einem Rechner dürfen unterbrochen werden.) • Nach einem Ausfall eines Rechners fällt bis zum Abschluß der Fehlerbehandlung kein weiterer Rechner aus. 2.2.2 Hardwareplattform Im praktischen Einsatz spielt neben der Latenz und der Verf ügbarkeit auch der Preis eine Rolle. Aus diesem Grund sollen nur Standardkomponenten verwendet werden und auf teure Sonderanfertigungen soll verzichtet werden. Für die Hardwareplattform bleiben somit nicht viele Alternativen: Als Rechner kommen Standard-Rechner in einem Cluster zum Einsatz, die per Ethernet gekoppelt werden. Redundante Verbindungen schützen vor einer Teilung des Netzwerks beim Ausfall einer Komponente. Je nach geforderter Leistungsfähigkeit kann HADES auch parallel zu einer anderen Anwendung auf einem bereits vorhandenen Cluster laufen. 2.2. DIE HADES-SYSTEMUMGEBUNG 17 Damit bei einem Stromausfall nicht alle Rechner gleichzeitig ausfallen, werden die Rechner und Netzwerkkomponenten mit einer unabhängigen Stromversorgung ausgestattet. Insgesamt soll davon ausgegangen werden, daß Rechner unabh ängig voneinander ausfallen und daß es keinen Single-Point-of-Failure“ gibt, dessen Ausfall ” zu einem gleichzeitigen Ausfall mehrerer Rechner führt. USV USV USV USV USV Abbildung 2.1: Beispiel-Cluster Abbildung 2.1 zeigt ein Beispiel, wie ein Cluster aufgebaut sein k önnte, der nur auf Standardkomponenten basiert: Jeder Rechner verfügt über eine eigene unterbrechungsfreie Stromversorgung (USV), die den angeschlossenen Rechner sowie den dazugehörigen Switch bei einem Stromausfall mit Strom versorgt. Das Netzwerk wird durch redundant verkabelte Switches gebildet. Fällt ein Switch in diesem System aus, so ist dies äquivalent zum Ausfall eines einzigen Rechners, eine Teilung des Netzwerks tritt nicht auf. Das in den Switches verwendete Spanning-Tree-Protokoll sorgt dafür, daß jeweils redundante Verbindungen, die dazu führen würden, daß Nachrichten im Netz kreisen und die Bandbreite beeinträchtigen, abgeschaltet werden und bei einem Ausfall eines Switches oder einer Verbindung wieder aktiviert werden [IEE, S. 58ff]. Hierzu tauschen Switches regelmäßig Kontrollnachrichten aus, um ausgefallene Verbindungen zu erkennen oder neu hinzugekommene zu identifizieren. In diesem Prozeß werden auch die Verbindungskosten ber ücksichtigt, um die Pfade zu minimieren und die Benutzung von Verbindungen mit niedriger Bandbreite möglichst zu vermeiden. 2.2.3 Speicherressourcen Werden Daten in einem Cluster gespeichert, der aus einer wechselnden Zahl von Rechnern besteht, kann der Sonderfall auftreten, daß nach einem Ausfall nicht mehr genügend Hauptspeicher zur Verfügung steht, um alle Daten zu speichern. Dies ist ein fataler Fehler und es kann keine sichere Speicherung mehr gew ährleistet werden. 18 KAPITEL 2. ANNAHMEN ÜBER DIE SYSTEMUMGEBUNG Die einzige Möglichkeit besteht darin, den Speicherverbrauch zu überwachen und rechtzeitig neue Ressourcen zum Cluster hinzuzufügen, damit es nicht zu einem Speichermangel kommen kann. Eine einfache Maßnahme, die in diesem Fall Datenverlust verhindern kann, ist das Hinzufügen von Swap-Speicher in Form von Festplatten. Im normalen Betrieb wird er nicht verwendet und beeinträchtigt daher nicht die Geschwindigkeit. Wird jedoch mehr Speicher benötigt als Hauptspeicher zur Verfügung steht, so gehen keine Daten verloren, sondern durch die nun einsetzende Verwendung von Swap-Speicher sinkt nur die Geschwindigkeit ab. Auf diese Weise bleibt Zeit, neue Ressourcen hinzuzufügen, bevor ein Datenverlust auftritt. 2.2.4 Netzwerkressourcen Das Fehlermodell von HADES setzt voraus, daß keine Partitionierung des Netzwerks auftritt. In einem Wide-Area-Network (WAN) könnte dies nicht vorausgesetzt werden, aber für vorgesehenen Anwendungen von HADES ist eine Verteilung im WAN nicht notwendig und man kann daher voraussetzen, daß sich alle Komponenten im gleichen LAN befinden. Dies vereinfacht die Anforderungen an die Vernetzung sehr stark und es kann eine vollständige redundante Vernetzung, in der jede Netzwerkkomponente mit jeder anderen verbunden ist, eingesetzt werden. In einem vollständig verbundenen Netz, wie in Abbildung 2.1 gezeigt, können beliebig viele der Komponenten ausfallen, ohne daß es zu einer Teilung in zwei Teilnetze kommen kann. In vielen Fällen wird diese Eigenschaft aber nicht benötigt, denn die Anzahl der Ausfälle, die nacheinander auftreten können, wird auch durch die verbleibenden Speicherressourcen limitiert. Reicht der verbliebene Speicher nicht mehr aus, um alle Daten zu speichern, oder reicht die verbliebene Rechenleistung nicht mehr aus, um die Leistungsanforderungen der Applikation zu erfüllen, so ist es irrelevant, ob das Netzwerk weitere Ausfälle maskieren kann oder nicht. In diesem Fall ist kein Netz erforderlich, das einem vollständig verbundenen Graphen entspricht, sondern es kann einfacher aufgebaut sein. Sind z.B. in einem Netz aus 10 Rechnern mit jeweils 1 GBytes Speicher 2,5 GBytes Daten gespeichert, so werden hierf ür mindestens 5 Rechner benötigt, um jeweils 2 Kopien der Daten speichern zu können. Fallen also nacheinander mehr als 5 Rechner aus oder werden vom Cluster abgetrennt, so reichen die Ressourcen nicht mehr aus, um die Daten zu speichern. Daher braucht auch das Netzwerk nur den Verlust von 5 Switches verkraften zu können, da bei einem Ausfall von mehr als 5 Switches die verbliebenen Ressourcen ohnehin nicht mehr zur Speicherung aller Daten ausreichen würden. Daher reicht eine Verkabelung wie in Abbildung 2.2 aus. Um den Ring an einer Stelle zu unterbrechen, m üssen 3 benachbarte Knoten ausfallen. Um den Ring in zwei nicht zusammenh ängende Be- 2.2. DIE HADES-SYSTEMUMGEBUNG 19 Abbildung 2.2: Netzwerk, das 5 Ausfälle übersteht (die angeschlossenen Rechner sind nicht abgebildet) reiche zu unterteilen, muß er an zwei Stellen unterbrochen werden, es m üssen also mindestens 6 Knoten ausfallen. Dieses Verbindungsschema läßt sich für beliebige Anforderungen an die Fehlertoleranz verallgemeinern: Verbindet man jeden Knoten in einem Ring mit dem ersten, zweiten, . . ., i-ten Vorgänger und Nachfolger im Ring, so können 2i − 1 Ausfälle aufgefangen werden: Um den Ring an einer Stelle zu unterbrechen, m üssen mindestens i direkt benachbarte Knoten im Ring ausfallen, fallen weniger aus, so gibt es noch eine Verbindung über diese Stelle hinweg. Um den Ring in zwei Teile zu spalten, muß er an zwei Stellen unterbrochen werden, d.h. es müssen mindestens 2i Knoten ausfallen. Fallen weniger aus, so tritt keine Teilung auf. In der Graphentheorie ist dieses Konzept auch als k-Verbundenheit bekannt: Ein Graph ist k-verbunden, wenn bis zu k Komponenten entfernt werden können, ohne daß eine Partitionierung auftritt. Je nachdem, ob das Entfernen von Knoten oder Kanten betrachtet wird, wird zwischen der k-Kanten-Verbundenheit und der k-Knoten-Verbundenheit unterschieden. Gleichzeitig wird mit diesem Schema eine minimale Lösung erzielt: Jeder Knoten ist mit 2i anderen Knoten im Ring verbunden (i Vorgänger und i Nachfolger). Läßt man eine Verbindung weg, so wäre ein Knoten nur noch mit 2i−1 Knoten verbunden und der Ausfall dieser Knoten würde einen Knoten vom Netz abspalten, so daß nun nur noch 2i − 2 beliebige Ausfälle aufgefangen werden könnten. Die Betrachtungen gelten jeweils für den schlechtesten Fall und im besten Fall können auch mehr Ausfälle aufgefangen werden: Fallen nur benachbarte Knoten 20 KAPITEL 2. ANNAHMEN ÜBER DIE SYSTEMUMGEBUNG aus, so entsteht nur eine Unterbrechung im Ring aber keine Teilung in zwei Bereiche. Daher können beliebig viele benachbarte Knoten ausfallen. Fallen nur i − 1 benachbarte Knoten aus, so wird der Ring nicht unterbrochen. Daher k önnen beliebig viele Bereiche von jeweils i − 1 benachbarten Knoten ausfallen, solange diese jeweils durch mindestens einen funktionierenden Knoten getrennt sind, ohne daß der Ring unterbrochen wird. 2.3 Zusammenfassung In diesem Kapitel wurde das Fehlermodell von HADES und die Hardware-Umgebung vorgestellt, in der HADES laufen soll: HADES basiert auf dem Fehlermodell Crash“, ” d.h. entweder ein Rechner funktioniert fehlerfrei oder überhaupt nicht. HADES setzt keine spezielle Hardware voraus, sondern es werden nur Standardkomponenten eingesetzt. Kapitel 3 Überblick: Aufbau von HADES In diesem Kapitel möchte ich einen kurzen Überblick über den Aufbau von HADES und die Funktion der einzelnen Komponenten geben, die dann in den folgenden Kapiteln ausführlich beschrieben werden. Abbildung 3.1 zeigt den groben Aufbau von HADES. Um die Abbildung übersichtlich zu halten ist jeweils nur ein Client und ein Server abgebildet. HADES unterstützt aber durchaus mehrere Clients und normalerweise werden immer mehrere HADES-Server eingesetzt, um Daten redundant speichern zu können. HADES wurde so konzipiert, daß es als normales Anwenderprogramm laufen kann und keine Eingriffe in den Betriebssystemkern erfordert. Auf diese Weise ist es nicht an ein bestimmtes Betriebssystem gebunden, sondern kann leicht auf verschiedene Systeme portiert werden. So läßt sich z.B. die in dieser Arbeit vorgestellte Version unverändert sowohl unter Linux als auch unter Solaris einsetzen. 3.1 Fehlertolerante Kommunikationsschicht Die Kommunikationsschicht dient zur fehlertoleranten Kommunikation zwischen den Servern und zwischen Clients und Servern. Sie hat mehrere Aufgaben: • zuverlässige nachrichtenbasierte Kommunikation • Verbinden der Rechner zu einem Cluster • Überwachung der Rechner 21 22 KAPITEL 3. ÜBERBLICK: AUFBAU VON HADES HADES−Aufbau HADES−Client HADES−Server Anwenderprogramm HADES−API HADES−DB fehlertolerante Kommunikationsschicht fehlertolerante Kommunikationsschicht TCP/IP TCP/IP Ethernet Abbildung 3.1: Überblick über die Komponenten von HADES • Überwachung der Kommunikation • konsistente Sicht auf den Zustand des Clusters Die Kommunikationsschicht setzt auf TCP/IP auf, um eine zuverl ässige Kommunikation zur Verfügung stellen zu können. TCP/IP arbeitet jedoch Stream-orientiert, was für HADES wenig geeignet war. Deshalb übernimmt die Kommunikationsschicht die Aufgabe Nachrichten auf Senderseite in einen Datenstrom zu verwandeln und auf Empfängerseite wieder in die einzelnen Nachrichten zu zerlegen. Die Kommunikationsschicht verbindet die Rechner zu einem Cluster und überwacht die Rechner und deren Kommunikation. Rechnerausfälle werden per Heartbeat erkannt und an alle Rechner im Cluster gemeldet. Wird ein Rechnerausfall erkannt, so wird bestimmt, welche Nachrichten von einem Ausfall betroffen sind und Rechner, die auf eine Antwort zu einer Nachricht warten, werden ebenfalls informiert, daß keine Antwort mehr eintreffen wird. Allen Rechnern im Cluster wird eine konsistente Sicht auf den Cluster zur Verfügung gestellt, d.h. Rechner sind entweder 3.2. HADES-API 23 voll funktionsfähig oder sie werden von allen Rechnern im Cluster als ausgefallen eingestuft. Der Zustand, daß ein Rechner nur noch mit Teilen des Clusters kommunizieren kann, wird durch Ausschluß dieses Rechners aus dem Cluster aufgel öst. Auf diese Weise kann gewährleistet werden, daß Rechner entweder komplett fehlerfrei funktionieren oder als komplett ausgefallen betrachtet werden k önnen. Der genaue Aufbau und die zur Kommunikation und Überwachung eingesetzten Protokolle werden in Kapitel 4 beschrieben. 3.2 HADES-API Das HADES-API stellt zum Anwendungsprogramm hin Methodenaufrufe zur Verfügung, mit denen das Anwendungsprogramm Operationen auf den Daten ausf ühren kann. Alle durchsatzkritischen Methoden erlauben eine asynchrone Operationsweise, um auf diese Weise Latenzen überbrücken und optimalen Durchsatz erreichen zu können. Methodenaufrufe werden von der HADES-API serialisiert und die resultierenden Nachrichten per Kommunikationsschicht an die HADES-Server gesendet. Die Serialisierung verwendet eine systemunabhängige Darstellung, so daß es möglich ist, Systeme mit unterschiedlicher Datendarstellung (z.B. Big-Endian und Little-Endian) gemischt zu verwenden. 3.3 HADES-DB HADES-DB ist der eigentliche Kern des Datenmanagementsystems. Es sorgt f ür die redundante Speicherung der Daten und für die gleichmäßige Verteilung der Daten im Netz. HADES-DB deserialisiert die empfangenen Nachrichten und f ührt die darin enthaltenen Operationen durch. Es stellt folgende Operationen zur Verf ügung: • Tabellen anlegen und löschen • Daten schreiben und lesen • Daten suchen und vorsortieren • Transaktionen • verteilte Transaktionen 24 KAPITEL 3. ÜBERBLICK: AUFBAU VON HADES HADES-DB koordiniert die Operationen so, daß sich das System wie ein fehlerfreier nicht verteilter Speicher verhält: Nur Schreibzugriffe können Daten ändern, Rechnerausfälle bleiben dagegen ohne Auswirkungen auf die gespeicherten Daten. HADES-DB ist nicht auf die Zugriffsarten Schreiben und Lesen beschr änkt, sondern es läßt sich leicht um beliebige Operationen erweitern. Voraussetzung ist nur, daß diese Operationen keinen Zugriff auf alle gespeicherten Daten ben ötigen und sich daher gut parallelisieren lassen. Als Beispiel für eine solche Operation wird Suchen zur Verfügung gestellt. Eine Suche kann sehr einfach parallel auf den verschiedenen Teildatensätzen durchgeführt werden und die Teilergebnisse durch Aneinanderh ängen auf Client-Seite zu einem Gesamtergebnis zusammengefügt werden. Auf diese Weise stellt HADES eine einfache Möglichkeit zur Verfügung, Berechnungen im Cluster zu parallelisieren. Die Verteilung der Daten wird in Kapitel 5 beschrieben und die zur Verf ügung gestellten Operationen in Kapitel 6. 3.4 Beispiel Anhand eines Beispiels möchte ich das Zusammenspiel der einzelnen HADES-Komponenten illustrieren. Abbildung 3.2 zeigt vereinfacht den Ablauf eines Schreibzugriffs bei dem ein Rechnerausfall auftritt: 1. Der Client möchte Daten schreiben und ruft dazu die passende Methode des HADES-API auf. Dieses baut daraus eine Nachricht auf und sendet diese unter Verwendung der Kommunikationsschicht an den Server, der eine Kopie der zu ändernden Daten enthält. Danach ruft der Client die Methode der API auf, die auf die Antwort vom Server wartet. 2. Der Server (HADES-DB) übernimmt die Nachricht von der Kommunikationsschicht, ändert die Daten und sendet die Änderung an den Server, der die zweite Kopie der Daten besitzt. Da Daten erst dann sicher gespeichert sind, wenn sie auf mindestens zwei Servern abgelegt sind, wartet der Server auf eine Empfangsbestätigung des zweiten Servers, bevor er dem Client die Antwort sendet. In dieser Zeit fällt er aus. 3. Der zweite Server erhält die Schreibanfrage und führt sie aus. 4. Der Ausfall des ersten Servers wird von der Kommunikationsschicht erkannt und an alle Rechner im Cluster gemeldet. Zusätzlich wird das Warten des Clients auf die Antwort von der Kommunikationsschicht mit einer Fehlermeldung abgebrochen. 3.4. BEISPIEL Client 25 Server 1 Server 2 Server 3 Koordinator Daten schreiben Daten schreiben Ausfall Ausfall erkannt Ausfall erkannt: Datenverteilung neu berechnen Datenverteilung ermitteln Daten schreiben Daten schreiben ACK OK Abbildung 3.2: Beispiel: Schreibzugriff 5. Der Koordinator, der die Verteilung der Daten im Cluster verwaltet, legt eine neue Verteilung der Daten im Cluster fest und startet die Umverteilung der Daten, nachdem er von der Kommunikationsschicht über den Ausfall unterrichtet worden ist. 6. Der Client liest die Fehlermeldung. Zu diesem Zeitpunkt kann der Client nicht entscheiden, ob die Schreibanfrage oder die Antwort darauf verloren gegangen ist. Deshalb wendet sich das HADES-API des Clients daraufhin an den Koordinator, der die Verteilung der Daten im Cluster verwaltet, fragt die neue Verteilung der Daten ab und sendet die Schreibanfrage an den neuen“ Server ” für diese Daten. 7. Dieser Server bearbeitet die Anfrage und leitet sie an einen zweiten Server weiter. Sobald dieser den Empfang der Anfrage bestätigt, beantwortet der Server die Anfrage des Clients. 8. Der Client liest die Antwort und liefert eine entsprechende Best ätigung an die Anwendung zurück. Interessant ist hier anzumerken, daß auch wenn die Schreibanfrage nach dem Serverausfall nicht mehr durch das HADES-API wiederholt und an den neuen Server 26 KAPITEL 3. ÜBERBLICK: AUFBAU VON HADES gesendet werden kann, z.B. weil der Client ebenfalls ausfällt, keine widersprüchlichen Daten im Cluster gespeichert werden, da es nach dem Serverausfall insgesamt nur noch eine Kopie dieser Daten gibt. Nach dem Ausfall des Servers sind die gespeicherten Daten im Cluster konsistent, es ist nur unbekannt, ob die Änderungen durchgeführt wurden oder nicht. Die Wiederholung der Schreibanfrage hat nur die Aufgabe diese Unsicherheit aufzulösen, ob noch die alten oder schon die neuen Daten gespeichert sind. Kapitel 4 Fehlertolerante Kommunikationsschicht Möchte man einen fehlertoleranten Cluster aufbauen, so stellen sich zun ächst einmal einige Fragen: 1. Welche Rechner gibt es überhaupt im Cluster? 2. Wie gelangt ein Rechner in den Cluster? 3. Wie kommuniziert man mit den Rechnern? 4. Was passiert, falls ein Rechner ausfällt? 5. Und wie stellt man das überhaupt fest? Die Aufgabe der Kommunikationsschicht ist es, als Lösung dieser Probleme folgende Dienste anzubieten: • Verwaltung der Rechner im Cluster (Abschnitt 4.3) • Erkennung von ausgefallenen Knoten (Abschnitt 4.4) • Bereitstellen einer Kommunikationsinfrastruktur (Abschnitt 4.5) Die Implementierung dieser Dienste sowie eine Beschreibung ihres Verhaltens im 27 28 KAPITEL 4. FEHLERTOLERANTE KOMMUNIKATIONSSCHICHT Fehlerfall wird in den folgenden Abschnitten beschrieben. Dabei soll folgende Notation für die Beschreibung der Datenformate verwendet werden: • Serifenlos gedruckte Bezeichner stehen für vordefinierte Konstanten, während • kursiv gedruckte Bezeichner für Variablen bzw. Daten verwendet werden. • Bezeichner, die mit Cmd anfangen, bezeichnen Nachrichten bzw. Kommandos, die zwischen dem Programm und der Kommunikationsschicht ausgetauscht werden. • Bezeichner, die mit Msg anfangen, bezeichnen Nachrichten bzw. Kommandos, die zwischen zwei Instanzen der Kommunikationsschicht auf verschiedenen Rechnern ausgetauscht werden. • Da häufiger Adressen benötigt werden, möchte ich hierfür eine Abkürzung definieren. Adressen setzen sich jeweils aus einer IP-Adresse und Port-Nummer zusammen: ←− 32 −→ ←− 16 −→ IP-Adresse Port-Nummer Damit hat eine Adresse eine Größe von 48 Bit. Alle Adressen, die im folgenden verwendet werden, liegen in diesem Format vor. Alle Knoten im Cluster k önnen durch diese Adresse eindeutig identifiziert werden, da diese Adressen jeweils nur einmal existieren können. Alle Nachrichten zwischen verschiedenen Rechnern setzen sich aus einem Header und den Nutzdaten zusammen. Der Header hat folgendes Format: ←− 32 −→ ←− 32 −→ ←− 32 −→ Type Size Daten Type beschreibt den Typ der Nachricht. Size ist die Größe der nachfolgenden Daten in Bytes inklusive AckType und MessageID. Auf diese Weise können die Daten auf Empfängerseite unabhängig vom Inhalt wieder in einzelne Nachrichten zerlegt werden. Dies ist notwendig, da TCP-Verbindungen streamorientiert arbeiten und die Grenzen zwischen den Nachrichten verloren gehen. Type und Size liegen jeweils in Network-Byte-Order [Ste98, S. 68] vor, so daß eine Kommunikation zwischen verschiedenen Architekturen wie z.B. x86 (Little-Endian) und Sparc (Big-Endian) möglich ist. Dies trifft auch für alle anderen im folgenden beschriebenen Datenfelder zu, die zur Kommunikation zwischen verschiedenen Knoten verwendet werden. Für Nutzdaten trifft dies naturgemäß nicht zu, da diese unter der Kontrolle des jeweiligen Programms stehen. 4.1. ÜBERBLICK ÜBER DIE IMPLEMENTIERUNG 4.1 4.1.1 29 Überblick über die Implementierung Designentscheidungen Für die Verwaltung der Rechner im Cluster gibt es zwei prinzipielle M öglichkeiten: Statische Konfiguration und dynamische Verwaltung. Eine statische Konfiguration, die beim Start des Clusters eingelesen wird und die alle Rechner auflistet, ist unflexibel, da nach dem Programmstart keine Möglichkeit mehr besteht, neue Rechner in einen Cluster aufzunehmen, und beim Ausfall eines Rechners wird dieser immer noch weiter verwaltet, obwohl er nicht mehr zur Verfügung steht. Das erste Problem tritt bereits beim Starten des Clusters auf: Man kann nicht davon ausgehen, daß alle Rechner gleichzeitig gestartet werden können, so daß am Anfang nicht alle Rechner erreichbar sind und es an dieser Stelle nicht feststellbar ist, ob ein Rechner noch nicht gestartet oder bereits ausgefallen ist. Die Alternative, die Liste dynamisch zu verwalten, z.B. indem sich Rechner beim Start selbst am Cluster anmelden, besitzt diese Nachteile nicht. der Status der beteiligten Rechner ist besser bekannt: Die Liste enthält nur Rechner, die aktiv am Cluster beteiligt sind und noch nicht als ausgefallen erkannt wurden. Rechner, die noch nicht gestartet wurden, sind in dieser Liste nicht enthalten. Für die Kommunikation zwischen den Rechnern gibt es mehrere M öglichkeiten. Zur Auswahl stehen verbindungslose Protokolle wie UDP oder Protokolle, die eine feste Verbindung aufbauen und offenhalten, wie TCP. UDP [UDP, Ste98, S. 32] scheint auf den ersten Blick eine geeignete Wahl zu sein, da es als schnell gilt, paketbasiert arbeitet und somit f ür eine Anwendung, die Nachrichten verwendet, gut geeignet zu sein scheint, indem jede Nachricht in ein eigenes Paket verpackt wird. Da es verbindungslos arbeitet ist es nicht n ötig eine Verbindung aufzubauen, bevor Nachrichten versandt werden können. Allerdings gibt es beim Einsatz von UDP mehrere Einschränkungen: • Die maximale Paketgröße in UDP ist limitiert (knapp 64KBytes). • UDP verhindert nicht, daß Pakete verloren gehen oder verdoppelt werden, es garantiert nur, daß keine Veränderungen der Pakete auftreten. Um UDP einsetzen zu können, muß eine Fehlerbehandlung für verlorengegangene oder verdoppelte Pakete implementiert werden sowie eine Sonderbehandlung f ür Nachrichten eingerichtet werden, die größer als die maximale Paketgröße sind. 30 KAPITEL 4. FEHLERTOLERANTE KOMMUNIKATIONSSCHICHT Da im LAN sehr wenige Fehler auftreten, könnte man auf die Idee kommen, daß man dies mit wenig Aufwand erreichen kann, jedoch stellte sich heraus, daß bei hohem Durchsatz Pakete verworfen werden, wenn nicht genügend interner SocketPufferspeicher existiert [Ste98, S. 231]. Auf diese Weise ist eine einfache Fehlerkorrektur, die keine Kontrolle zur Limitierung der Auslastung des Empfangspuffers zur Verfügung stellt, nicht ausreichend. Auch wenn der empfangende Prozeß ununterbrochen zum Lesen vom Socket bereit ist, kann dieser Effekt auftreten, so daß sich dieser Weg als Sackgasse herausstellte. HADES verwendet deshalb als zugrundeliegendes Protokoll TCP [TCP]. Da TCP streamorientiert arbeitet [Ste98, S. 32f] und keine Abgrenzung der Pakete kennt, muß dies von der Implementierung emuliert werden, damit die empfangenen Daten wieder in einzelne Nachrichten unterteilt werden können. Pro Instanz der Kommunikationsschicht kann ein Programm ein Knoten in einem Cluster sein. Soll ein Programm gleichzeitig Knoten in mehreren Clustern sein, so kann dies durch den gleichzeitigen Einsatz mehrerer Instanzen der Kommunikationsschicht erreicht werden. Auf die gleiche Weise kann ein Rechner mehrere Knoten in mehreren Clustern enthalten. Zur Vereinfachung der Beschreibung m öchte ich hier jedoch davon ausgehen, daß jeder Knoten auf einem anderen Rechner l äuft, so daß Rechner und Knoten gleichgesetzt werden können. 4.1.2 Aufbau der Kommunikationsschicht Abbildung 4.1 gibt einen Überblick über die Architektur der Kommunikationsschicht. Zentraler Teil sind die zwei Tabellen mit den Eingangs- bzw. Ausgangspuffern. Die beiden Tabellen sind nach IP-Adressen und Port-Nummern sortiert. Eingangs- und Ausgangspuffer verfügen über eine TCP-Verbindung zur entsprechenden Gegenstelle. Die Eingangspuffer speichern jeweils noch nicht vollst ändig übertragene Nachrichtenpakete. Sobald ein Nachrichtenpaket vollst ändig empfangen wurde, wird es in einem der globalen Empfangspuffer Receive-Buffer“ oder ” Result-Buffer“ gespeichert, je nachdem, ob es sich um eine neue Nachricht oder ” um eine Antwort auf eine Nachricht handelt. Zu versendende Nachrichten werden in dem Ausgangspuffer gespeichert, der über die TCP-Verbindung zum Ziel verfügt. Zusätzlich besteht die Möglichkeit Signale an das Programm zu senden. Eines von diesen Signalen wird zur Signalisierung verwendet, wenn sich die Liste der Rechner im Cluster ändert. Die TCP-Verbindungen werden nur als Halb-Duplex Verbindungen verwendet, da dies den Verbindungsaufbau vereinfacht: Jede Seite baut die Verbindung auf, über 4.1. ÜBERBLICK ÜBER DIE IMPLEMENTIERUNG 31 Programm Result−Buffer Receive−Buffer Kommunikationsschicht Signale Receive−Buffer Transmit−Buffer Host A Host A Host B Host B Host C Host C Host D Host D Host E Host E Receive−Buffer Transmit−Buffer Receive−Buffer Transmit−Buffer Receive−Buffer Transmit−Buffer Receive−Buffer Transmit−Buffer Netzwerk Abbildung 4.1: Aufbau der Kommunikationsschicht 32 KAPITEL 4. FEHLERTOLERANTE KOMMUNIKATIONSSCHICHT die sie Daten sendet. Im Unterschied zu einer Voll-Duplex Nutzung ist keine Abstimmung erforderlich, welche Seite die Verbindung aufbaut. Der Verbindungsstatus durchläuft nacheinander die Phasen Connecting, Online und Terminated. Im Status Connecting wird die Verbindung aufgebaut, es können jedoch noch keine Daten übertragen werden. Dies ist erst im Status Online möglich, der für eine bestehende, vollständig aufgebaute Verbindung verwendet wird. Bricht eine Verbindung ab, so ändert sich der Status zu Terminated, bis die Verbindung gel öscht wird. Die Kommunikationsschicht läuft als eigener Prozeß, um unabhängig vom eigentlichen Programm Daten empfangen, quittieren und die Kommunikation überwachen zu können. Mit Programm“ möchte ich im folgenden jeweils den Prozeß bezeichnen, ” der die Kommunikationsschicht verwenden möchte. Die Verwendung eines Prozesses statt eines Threads bietet den Vorteil, daß die Adreßräume getrennt sind und so eine bessere Stabilität der Kommunikationsschicht erwartet werden kann, falls das Programm abstürzt. Die Kommunikation mit dem Programm findet über Pipes statt. Alle Ein-/Ausgabeoperationen, die unterbrochen werden könnten, sind nichtblockierend ausgelegt. Auf diese Weise wird verhindert, daß eine unterbrochene Verbindung die Kommunikationsschicht blockiert. Somit wird gewährleistet, daß die Kommunikationsschicht auch in diesem Fall weiterhin mit den anderen Knoten, die nicht von der Unterbrechung betroffen sind, kommunizieren kann. 4.2 Client-Programmierschnittstelle Jede Instanz der Kommunikationsschicht kann sich höchstens in einem Cluster als Knoten registrieren. Hierzu stehen Methoden zum Registrieren im Cluster und zum Verlassen des Clusters zur Verfügung. Nach der Registrierung erhält der Knoten eine Tabelle aller Knoten im Cluster. Zur Unterscheidung von verschiedenen Knotentypen im Cluster ist es möglich, eine Hostklasse anzugeben, um z.B. Server und Clients im Cluster unterschiedlich zu kennzeichnen. Die Kommunikationsschicht ist für Kommunikation von Typ Request/Reply ausgelegt. Um eine hohe Leistung zu erreichen, wurde das Senden von Nachrichten als nichtblockierende Funktion ausgelegt, um auf diese Weise mehrere Nachrichten parallel versenden zu können. Im Unterschied zu einer reinen asynchronen Kommunikation steht aber zusätzliche Unterstützung für Request/Reply zur Verfügung: Anfragen werden per sendMessage() gesendet und Antworten auf eine Anfrage per sendRequest(). Die MessageID, die von sendMessage() zurückgeliefert wird erlaubt es die Antworten den jeweiligen Anfragen zuzuordnen. Damit können sehr einfach 4.2. CLIENT-PROGRAMMIERSCHNITTSTELLE /////////////// Client-Seite MessagePort port; MessageID id1,id2; Buffer buf1,buf2,buf3,result; IPAddress ip1,ip2,ip3; // Nachricht, auf die eine Antwort erwartet wird id1=port.sendMessage(buf1,ip1,MessagePort::Reply); // Nachricht, für die ein Ack erwartet wird id2=port.sendMessage(buf2,ip2,MessagePort::Ack); // rein asynchrone Nachricht, ohne Antwort port.sendMessage(buf3,ip3,MessagePort::None); // // Auf die 2 Ergebnisse warten for(int i=0;i<2;i++) { MessageHeader mh; port.readResult(mh,result); if(mh.msg_id==id1) { /* Antwort auf Nachricht 1 */ } else if(mh.msg_id==id2) { /* Ack für Nachricht 2 */ } } /////////////// Server-Seite MessageHeader mh; Buffer buffer; // Nachricht lesen und Antwort senden port.readMessage(mh,buffer); port.sendReply(buffer,mh.host,mh.msg_id); Abbildung 4.2: Code-Fragmente: Paralleles Senden von Anfragen 33 34 KAPITEL 4. FEHLERTOLERANTE KOMMUNIKATIONSSCHICHT mehrere Anfragen überlappend gesendet werden. Abbildung 4.2 zeigt dies in einem einfachen Beispiel: Es werden 3 Nachrichten gesendet, eine auf die eine Antwort erwartet wird, eine für die eine Empfangsbestätigung benötigt wird und eine rein asynchrone Nachricht. Danach werden die Ergebnisse für die beiden ersten Nachrichten ausgelesen, für die asynchrone Nachricht wird keine Antwort gesendet, daher kann hierfür kein Ergebnis ausgelesen werden. Zusätzlich zu den Nachrichten werden Signale zur Verfügung gestellt, die zur schnellen Signalisierung von Zustandsänderungen verwendet werden können. Da sie über eigene Kanäle zur Anwendung verfügen, können sie bevorzugt ausgeliefert werden und auch ein gefüllter Nachrichten-Puffer verzögert die Auslieferung nicht. 4.3 Verwaltung der Rechner im Cluster 4.3.1 Hinzufügen von Clusterknoten Nach dem Start bildet zunächst einmal jede Instanz der Kommunikationsschicht einen Cluster für sich, mit sich selbst als einzigem Knoten im Cluster. Ab diesem Zeitpunkt können andere Knoten zu diesem Cluster hinzugefügt werden. Existiert bereits ein anderer Cluster, zu dem dieser Knoten hinzugef ügt werden soll, so sieht das Protokoll wie folgt aus (Abbildung 4.3): 1. Zu einem beliebigen Knoten des Clusters wird eine Verbindung aufgebaut und als erstes Paket wird eine Nachricht vom Typ MsgJoin über diese Verbindung gesendet: ←− 32 −→ ←− 32 −→ ←− 48 −→ ←− 16 −→ MsgJoin 8 Adresse Hostklasse MsgJoin wird immer als erstes Paket nach einem Verbindungsaufbau über die Verbindung gesendet, um die zur Identifizierung verwendete Adresse des Absenders zu übertragen. Diese Adresse wird explizit übertragen, da Rechner über mehrere IP-Adressen verfügen können, aber jeder Knoten über eine eindeutige Adresse identifiziert werden muß. Hostklasse erlaubt es mehrere Arten von Knoten im Cluster zu unterscheiden, HADES verwendet dies, um Server von Clients zu unterscheiden. Falls bereits eine Verbindung zu einem anderen Cluster besteht, wird diese zuvor abgebrochen. 4.3. VERWALTUNG DER RECHNER IM CLUSTER Rechner A 1 Rechner X 35 Rechner Y Rechner Z Msg Join 2 ble stta sgHo sttable +M in sgJo MsgHo M 3 MsgJ oin+M M sg Ho sgHo le ab sttabl tt os e H stt sg ab M le 4 MsgJoin MsgHosttable MsgJoin+MsgHosttable Abbildung 4.3: Hinzufügen eines Clusterknotens 2. Der Knoten zu dem die Verbindung aufgebaut wurde, baut eine Verbindung in Rückrichtung auf und sendet über diese Verbindung ebenfalls ein MsgJoin. Zusätzlich wird per MsgHosttable an diesen Knoten und an alle Knoten im Cluster eine aktualisierte Liste aller Knoten im Cluster übertragen. 3. Beim Empfang der per MsgHosttable übertragenen Liste vergleichen die Knoten die Liste mit der bereits vorhandenen Liste der Knoten im Cluster. Wenn ein neuer Knoten hinzugekommen ist, senden sie jeweils ihrerseits an alle Knoten per MsgHosttable die aktuelle Liste. Falls noch keine Verbindung zu einem Knoten existiert, wird sie vorher aufgebaut und MsgJoin gesendet. Auf diese Weise wird sichergestellt, daß alle Rechner eine aktuelle Liste erhalten, auch wenn währenddessen einzelne Knoten ausfallen sollten. 4. Haben bereits alle Knoten die aktuelle Liste der Knoten im Cluster, ändert sich die Liste nicht weiter. Es werden daher keine weiteren Nachrichten gesendet und der Vorgang ist abgeschlossen Abbildung 4.3 zeigt nur eine Möglichkeit, je nach Nachrichten-Laufzeiten kann die genaue Abfolge davon abweichen. 36 KAPITEL 4. FEHLERTOLERANTE KOMMUNIKATIONSSCHICHT Per MsgHosttable werden immer nur Knoten zur lokalen Liste hinzugefügt und nur falls Knoten hinzugefügt wurden, wird eine MsgHosttable Nachricht an alle anderen Knoten gesendet. Dies garantiert, daß keine Oszillationen auftreten k önnen: Es wird pro Knoten maximal einmal MsgHosttable pro eingefügtem Knoten gesendet, da die Anzahl der Knoten endlich ist, ist somit garantiert, daß nach einer endlichen Anzahl von Nachrichten alle Knoten in der lokal gespeicherten Liste enthalten sind und somit das Verfahren zum Abgleich der Knotenliste terminiert. Dies gilt genauso f ür den Fall, daß zwei Knoten gleichzeitig zum Cluster hinzugefügt werden. Die Aufnahme in einen Cluster wird vom Programm per CmdJoin initiiert. Das Datenformat hierfür ist: ←− 32 −→ ←− 48 −→ CmdJoin Adresse Fehlertoleranz In diesem Abschnitt möchte ich darauf eingehen, wie Fehler in den verschiedenen Schritten abgefangen werden, wenn neue Clusterknoten eingefügt werden. Die Punkte beziehen sich auf die Schritte in Abbildung 4.3. Das Beispiel bezieht sich zwar nur auf vier Rechner, kann allerdings analog für weitere Rechner erweitert werden – Rechner Y und Z stehen dann für die weiteren Knoten des Clusters. 1. Der Fall, daß Rechner A ausfällt, bevor er eine Verbindung aufgebaut hat ist trivial. Fällt der Rechner aus, nachdem er eine Verbindung aufgebaut aber bevor er MsgJoin gesendet hat, so ist die Verbindung zwar registriert, jedoch wird der Knoten ohne eine eindeutige Adresse noch nicht in die Liste der Clusterknoten aufgenommen. Somit belegt die Verbindung zwar Ressourcen, der Knoten taucht aber nicht im Cluster auf. Die Verbindung wird sp ätestens nach einem TCP-Timeout vom Unix-Kernel abgebrochen, so daß ein EOF (end of file) von der Verbindung gelesen wird. Danach wird die Verbindung geschlossen. 2. Fällt Rechner X aus, zu dem die initiale Verbindung aufgebaut wurde, bevor er ein MsgHosttable an irgendeinen Knoten senden konnte, so wird die Verbindung wieder geschlossen, ohne daß der Knoten in den Cluster aufgenommen werden konnte. In diesem Fall muß der Vorgang mit einem anderen Knoten wiederholt werden, zu dem die initiale Verbindung aufgebaut wird. Fällt Rechner X aus, nachdem bereits mindestens eine MsgHosttable Nachricht vollständig gesendet wurde, dann wird die Liste der Clusterknoten von dem 4.3. VERWALTUNG DER RECHNER IM CLUSTER 37 Rechner weiterverteilt, an den diese Nachricht gesendet wurde. Auf diese Weise wird der Knoten trotzdem in den Cluster aufgenommen. Ein weiterer Rechner darf aufgrund der Fehlerannahme in diesem Fall nicht mehr ausfallen. 3. Fällt Rechner A aus, so können keine Verbindungen mehr von Rechner Y oder Z zu diesem Rechner aufgebaut werden und es können keine Daten von X übertragen werden. Dies führt auf allen 3 Rechnern zu Timeouts, die zur Ausfallerkennung von Rechner A benutzt werden. Die weitere Vorgehensweise wird im Abschnitt 4.4 beschrieben. Sobald also eine MsgHosttable-Nachricht erfolgreich versandt werden konnte, ist ein Rechner als Knoten im Cluster aufgenommen und ab diesem Zeitpunkt kann der Ausfall eines einzelnen Rechners nicht dazu führen, daß ein anderer Rechner aus dem Cluster herausfällt. Die eigentliche Aufnahme in den Cluster findet also mit dem erfolgreichen Versenden einer MsgHosttable-Nachricht statt. 4.3.2 Entfernen von Clusterknoten Beim Entfernen von Knoten aus dem Cluster ist zwischen zwei Fällen zu unterscheiden: 1. Der Knoten entfernt sich selbst aus dem Cluster 2. Der Knoten wird aus dem Cluster ausgeschlossen, nachdem ein Timeout oder ein anderer Fehler aufgetreten ist. Der erste Fall ist einfach. Der Knoten schließt einfach alle Verbindungen zu den anderen Knoten. Diese lesen daraufhin ein EOF von der jeweiligen Verbindung und entfernen daraufhin den Knoten aus der Liste der Clusterknoten. Das Verlassen eines Clusters wird vom Programm per CmdLeave gestartet. Das Datenformat hierfür ist: ←− 32 −→ CmdLeave Im zweiten Fall, daß ein Knoten aus dem Cluster ausgeschlossen werden soll, sendet der Rechner, der den Ausfall erkannt hat, MsgKillHost an alle anderen Knoten. Das Datenformat für MsgKillHost ist: ←− 32 −→ ←− 32 −→ ←− 48 −→ MsgKillHost 6 Adresse 38 KAPITEL 4. FEHLERTOLERANTE KOMMUNIKATIONSSCHICHT Empfängt ein Knoten MsgKillHost für einen Knoten, der noch als Clusterknoten registriert ist und sich im Zustand Online befindet, so l öscht er den Knoten mit Adresse Adresse aus der Liste der Clusterknoten und sendet an alle anderen verbliebenen Knoten ebenfalls MsgKillHost. Auf diese Weise erreicht diese Information alle Knoten, auch wenn einzelne Knoten ausfallen. Nach dem Senden von MsgKillHost wird der Knoten mit der Adresse Adresse aus der Liste der Clusterknoten gel öscht. Fehlertoleranz Beim Entfernen eines Knotens aus dem Cluster muß sichergestellt werden, daß dies konsistent in allen verbliebenen Knoten im Cluster geschieht, da jeder Knoten im Cluster eine lokale Kopie aller im Cluster vorhandenen Knoten besitzt. Die beiden Fälle erfordern unterschiedliche Maßnahmen, um im Falle eines Ausfalls noch zuverlässig zu arbeiten: Fällt im ersten Fall, daß sich ein Rechner selbst aus dem Cluster entfernt, ein Rechner aus, bevor er alle Verbindungen geschlossen hat, so wird der Knoten zun ächst nicht vollständig aus dem Cluster entfernt. Da der Knoten jedoch nicht mehr arbeitet, wird er schließlich als ausgefallen erkannt (Abschnitt 4.4) und aus dem Cluster entfernt. Da eine konsistente Sicht auf die im Cluster vorhandenen Knoten besonders wichtig ist und größere Probleme auftreten könnten, falls ein Knoten nur einen Teil der Verbindungen schließt, aber auf den verbliebenen Verbindungen weiterhin antwortet 1 , habe ich hier eine zusätzliche Fehlerbehandlung vorgesehen: Wird von einem Knoten festgestellt, daß eine Verbindung zu einem anderen Knoten geschlossen wurde, so wird nach einem Timeout von KillTimeout zusätzlich dieser Knoten per MsgKillHost aus dem Cluster ausgeschlossen. Damit wird sichergestellt, daß dieser Knoten konsistent von allen Knoten aus dem Cluster entfernt wird. Die Wartezeit von KillTimeout wird auch vom auszuschließenden Knoten eingehalten wenn die Verbindungen zu ihm getrennt werden und sorgt dafür, daß der Knoten nicht seinerseits auf das Beenden der Verbindungen reagiert und MsgKillHost über noch offene Verbindungen sendet. Damit geht dieser Mechanismus über die ursprüngliche Fehlerannahme hinaus und bietet zusätzliche Sicherheit für eine konsistente Sicht auf die im Cluster vorhandenen Knoten. Im zweiten Fall, daß ein Knoten aus dem Cluster ausgeschlossen wird, muß betrachtet werden, was passiert, wenn der Knoten ausfällt, der zum ersten mal ein 1 Dieser Fall kann dann auftreten, wenn Störungen auftreten, die die verschiedenen Verbindungen eines Knotens zufällig unterschiedlich stark stören, und der TCP-Stack nur die stärker gestörten Verbindungen schließt. 4.3. VERWALTUNG DER RECHNER IM CLUSTER 39 MsgKillHost sendet. Fällt dieser Knoten aus, bevor er MsgKillHost senden konnte, so bleibt der zu löschende Knoten im Cluster erhalten. Da dies konsistent in allen verbliebenen Knoten der Fall ist, stellt dies kein Problem dar. F ällt der Knoten dagegen aus, nachdem er MsgKillHost bereits an einen anderen gesendet hat, so gibt es zwei Möglichkeiten: • Der zu löschende Knoten ist dort im Status Online, dann wird er dort gel öscht und es wird zusätzlich MsgKillHost von diesem Knoten an alle anderen gesendet. Damit wird der Knoten auch von allen anderen gelöscht. Dabei kann gemäß Fehlerannahme davon ausgegangen werden, daß kein weiterer Rechner ausfällt. • Der zu löschende Knoten ist bereits im Status Terminated oder bereits gelöscht. Dies bedeutet aber, daß bereits ein Ereignis stattgefunden hat, das zum Löschen des Knotens geführt hat. Also wurde entweder bereits eine Verbindung zu dem Knoten geschlossen oder bereits MsgKillHost empfangen. Beide F älle führen aber dazu, daß MsgKillHost gesendet wird oder bereits gesendet wurde, so daß das aktuell empfangene MsgKillHost keine Auswirkungen mehr darauf hat, ob der Knoten aus dem Cluster gelöscht wird. Damit braucht das aktuell empfangene MsgKillHost nicht weiter betrachtet werden. 4.3.3 Abfragen der Clusterknoten Zur Kommunikation mit anderen Knoten im Cluster ist es eine Voraussetzung, daß diese überhaupt bekannt sind. Um die Adressen der anderen Knoten zu erfahren, kann deshalb die Liste der Clusterknoten abgefragt werden. Hierzu wird CmdHosttable verwendet: ←− 32 −→ CmdHosttable Daraufhin liefert die Kommunikationsschicht die Liste der aktuell verf ügbaren Clusterknoten zurück. Für jeden Knoten wird die Adresse sowie die Hostklasse, die es erlaubt mehrere Arten von Knoten im Cluster zu unterscheiden, zur ückgeliefert. ←− 32 −→ Anzahl Anzahl× ←− 48 −→ ←− 32 −→ Adresse Hostklasse Änderungen an der Liste der Clusterknoten werden dem Programm über ein Signal angezeigt (vgl. Abschnitt 4.6), so daß ein Programm schnell auf Änderungen reagieren kann. 40 4.4 KAPITEL 4. FEHLERTOLERANTE KOMMUNIKATIONSSCHICHT Ausfallerkennung Damit auf den Ausfall einer Komponente reagiert werden kann, muß zun ächst bekannt sein, daß ein Ausfall aufgetreten ist. Daher muß ein Mechanismus existieren, der die Knoten im Cluster überwachen und Ausfälle erkennen kann. Diesen Mechanismus möchte ich in diesem Abschnitt vorstellen. Im fehlerfreien Cluster bilden die Knoten zusammen mit den Verbindungen dazwischen einen vollständig verbundenen Graphen, d.h. jeder Knoten kann zu jedem anderen Knoten Nachrichten senden. Die Verbindungen zwischen den Knoten müssen dabei nicht physisch vorhandene Verbindungen sein, sondern repr äsentieren nur die zwischen den Knoten aufgebauten TCP-Verbindungen. Aufgabe der Ausfallerkennung ist es, die ausgefallenen Knoten und Verbindungen aus dem Cluster zu entfernen, so daß die verbliebenen Knoten im Cluster wieder einen vollst ändig verbundenen Graphen bilden. Dabei sollen Ausfälle der nächsthöheren Schicht als konsistente Ausfälle [Lap92, S. 117] zur Verfügung gestellt werden: Ein Ausfall wird von allen Benutzern auf gleiche Weise beobachtet. Das Erkennen von ausgefallenen Rechnern ist der Klasse der Übereinstimmungsprobleme [Gär01, S. 36f] sehr ähnlich, die unter der Crash-Fehlerannahme in asynchronen Systemen nicht deterministisch allgemein lösbar sind [FLP85]. Dies liegt daran, daß nicht sicher zwischen einem ausgefallenen und einem sehr langsamen Rechner bzw. einer sehr langsamen Datenübertragung unterschieden werden kann. Um trotzdem den Ausfall von Rechnern erkennen zu können und eine praktikable Lösung zu erreichen, wird angenommen, daß ein Rechner innerhalb gewisser Zeitschranken antworten muß. Damit wird aus dem asynchronen System ein partiell synchrones System [Gär01, S. 42]. Antwortet ein Rechner nicht innerhalb der angenommenen Zeitschranke, so wird angenommen, daß der Rechner ausgefallen ist. Auf diese Weise kann zwar ein aktiver Rechner fälschlicherweise als abgestürzt betrachtet werden, dies spielt aber keine Rolle, solange dadurch die Applikation keine Fehler macht. Eine Möglichkeit dies zu erreichen ist, daß man dafür sorgt, daß Knoten, die als ausgefallen erkannt werden, auch wirklich ausfallen, indem man sie aus dem Cluster ausschließt. Damit besteht kein Unterschied mehr zwischen korrekt und irrt ümlich als ausgefallen betrachteten Knoten. Das Ergebnis ist in beiden F ällen das gleiche: der Knoten wird aus dem Cluster entfernt. Es gibt zwei Arten von Ausfällen: Den Ausfall von Rechnern oder den Ausfall von Verbindungen zwischen Rechnern. Während ein ausgefallener Rechner keine Nachrichten mehr an den Cluster senden kann, da bei einem Rechnerausfall die TCPVerbindungen ausfallen und nicht wieder aufgenommen werden k önnen, führt eine Unterbrechung der Netzwerkverbindung nur zu einem Timing-Failure. Sobald die 4.4. AUSFALLERKENNUNG 41 Unterbrechung beseitigt ist, können wieder Nachrichten gesendet werden und die Fehlerkorrektur von TCP sorgt dafür, daß zuvor auch alle noch nicht erfolgreich übertragenen Nachrichten wiederholt werden. Auf diese Weise gehen keine Nachrichten verloren, sie kommen aber unter Umständen mit einer großen Verzögerung an. Zur Erkennung von Rechnerausfällen und Verbindungsunterbrechungen wird ein Heartbeat eingesetzt: Jeder Knoten erhöht in regelmäßigen Abständen den TimeoutZähler aller Verbindungen und sendet MsgPing an alle anderen Knoten. Sobald diese mit MsgPingAck geantwortet haben, wird der Timeout-Zähler wieder zurückgesetzt. Zusätzlich wird dieser Zähler auch zurückgesetzt, wenn andere Daten von diesem Knoten empfangen werden. Die Datenformate für MsgPing und MsgPingAck sind: ←− 32 −→ ←− 32 −→ MsgPing 0 ←− 32 −→ ←− 32 −→ MsgPingAck 0 Überschreitet der Timeout-Zähler den Wert HostTimeout, so wird ein Fehler erkannt, aber es ist zu diesem Zeitpunkt nicht klar, ob ein Knoten ausgefallen ist oder ob nur die Verbindung zu diesem Knoten unterbrochen wurde. Ausgefallene Knoten können keinen weiteren Schaden anrichten, da sie keine Nachrichten mehr senden können. Dies gilt aber nicht für den Fall einer unterbrochenen TCP-Verbindung. Wird die Netzanbindung eines Knotens unterbrochen oder gest ört und nach einer Zeit wiederhergestellt, so kann nicht davon ausgegangen werden, daß alle über diesen Anschluß laufenden TCP-Verbindungen konsistent einen Timeout registrieren oder nicht. Nachrichten laufen seriell über die Netzanbindung, so kann es passieren, daß Nachrichten, die zu einer TCP-Verbindung gehören gerade noch übertragen werden konnten, während Nachrichten, die zu einer anderen TCP-Verbindung geh ören, dies nicht mehr geschafft haben, so daß der Timeout zu verschiedenen Zeitpunkten ausläuft. Wird die Netzanbindung wiederhergestellt, dann kann es passieren, daß der Timeout von manchen TCP-Verbindungen bereits abgelaufen ist und diese Verbindungen getrennt werden, während andere TCP-Verbindungen wieder aufgenommen werden. Dieses Phänomen kann unabhängig von der Anzahl der physisch vorhandenen Netzwerkverbindungen auftreten. Erschwerend kommt hinzu, daß sich bei einer Abtrennung eines Knotens 2 , jeder der beiden Teile jeweils den Ausfall des anderen annehmen kann, da Timeouts auf beiden 2 Die Unterteilung des Netzes in mehrere Teile mit jeweils mehr als einem Knoten wird durch das HADES-Fehlermodell nicht abgedeckt (vgl. Kapitel 2.2.1). 42 KAPITEL 4. FEHLERTOLERANTE KOMMUNIKATIONSSCHICHT H ill os t H sg K A os t H ill sg K Unterbrechung Rechner C M Rechner A MsgKillHost A M M sg K ill os t C D Rechner B Timeout Rechner D Abbildung 4.4: Gegenseitiges Ausschließen aus dem Cluster bei sofortigem Senden von MsgKillHost Seiten der Störung auftreten. Würden beide Seiten nun anfangen sich gegenseitig aus dem Cluster auszuschließen, und wird die Netzwerkunterbrechung in diesem Moment aufgehoben, so können MsgKillHost Nachrichten über verbliebene aktive TCP-Verbindungen übertragen werden, was zum Ausschluß von einem größeren Teil der Clusterknoten führen würde. Folgendes Beispiel verdeutlicht das Problem wenn MsgKillHost sofort gesendet w ürde (Abbildung 4.4). Nach einer Unterbrechung des Netzwerks sind in Knoten A Timeouts zu den Rechnern C und D aufgetreten. Daher nimmt Rechner A an, daß Rechner C und D ausgefallen sind und sendet entsprechende MsgKillHost-Nachrichten an den verbliebenen Knoten B. Wenn die Unterbrechung des Netzwerks verschwindet, können diese Nachrichten an B übertragen werden. Gleichzeitig erkennt Rechner D einen Timeout zur Verbindung nach A und nimmt jeweils an, daß Rechner A ausgefallen ist und sendet ein dementsprechendes MsgKillHost an B. Rechner B erhält also für Rechner A, C und D ein MsgKillHost und löscht diese aus dem Cluster und verbleibt somit als einziger Knoten im Cluster. Damit dieser Effekt nicht auftreten kann, muß dieser Sonderfall der Netzwerkunterbrechung bzw. Störung erkannt werden. Dabei ist die Fehlerannahme hilfreich, daß maximal ein Rechner vom Netzwerk getrennt werden darf. Bei einer Netzwerkun- 4.4. AUSFALLERKENNUNG 43 terbrechung wird ein Cluster aus n Rechnern in zwei Teile geteilt, von denen der eine genau einen Knoten und der andere die restlichen n − 1 Knoten enth ält. Charakteristisch für diese Teilung ist, daß der isolierte Knoten Timeouts von vielen anderen Knoten feststellt, während nacheinander alle der n − 1 Knoten einen Timeout desselben Knotens feststellen. Dabei ist zu beachten, daß die Timeouts nicht notwendigerweise gleichzeitig auftreten, sondern je nach Art der Unterbrechung oder Störung innerhalb kurzer Zeit nacheinander auftreten. Zusätzlich ist es denkbar, daß die Unterbrechung nicht vollständig ist, so daß nicht alle Verbindungen zu dem isolierten Knoten abgebrochen werden. In diesem Fall kann der isolierte Knoten dadurch erkannt werden, daß mehr als ein Knoten einen Timeout auf der Verbindung zu diesem Knoten feststellt. Dieses wird dadurch realisiert, daß ein Knoten, der einen Timeout feststellt MsgRequestKillHost mit der Adresse des auszuschließenden Knotens an alle anderen Knoten sendet. Erst wenn ein weiterer Knoten, der diese Nachricht empfängt, auch einen Timeout feststellt, wird für diesen Knoten MsgKillHost an alle anderen gesendet. Auf diese Weise wird der Knoten nur dann aus dem Cluster entfernt, wenn mindestens zwei Knoten einen Timeout festgestellt haben. Das bisher entwickelte Verfahren kann jedoch keine Netzstörungen behandeln, die nur eine einzige TCP-Verbindung zwischen zwei Rechnern betreffen. Dies ist ein Sonderfall der Fehlerannahme, daß ein einzelner Rechner vom Netz abgetrennt wird. In diesem Fall ist ein Rechner nur teilweise vom Netz getrennt. Dabei kann aus Symmetriegründen ein beliebiger der beiden beteiligten Rechner als teilweise vom Netz getrennt angenommen werden. Abbildung 4.5 zeigt ein Beispiel f ür diesen Fall. In diesem Beispiel können Rechner A, B und C ohne Fehler miteinander kommunizieren ebenso wie Rechner B, C und D. Allerdings gilt dies nicht f ür alle vier Rechner A, B, C und D. Rechner A stellt einen Timeout von Rechner D fest und umgekehrt, es wird aber kein Rechner von mehr als einem anderen als ausgefallen erkannt. Dieser Fall kann dadurch aufgelöst werden, daß einer der beiden Rechner A oder D aus dem Cluster gelöscht wird. Dadurch daß es aufgrund der Symmetrie zwei gleich gute Lösungen gibt, muß entschieden werden, welche der beiden Alternativen verwendet werden soll. Diese Entscheidung muß im gesamten Cluster identisch getroffen werden. Es darf nicht passieren, daß keiner oder beide Knoten aus dem Cluster ausgeschlossen werden. Hierbei helfen mehrere Dinge: Es stehen genau zwei Knoten zur Auswahl. Diese beiden Knoten besitzen unterschiedliche aber eindeutige Adressen bestehend aus IP-Adresse und Port-Nummer. Auf diese Weise kann man z.B. den Knoten mit der gr ößeren IP-Adresse (die IP-Adresse wird hierzu einfach als vorzeichenlose 32-Bit Zahl interpretiert) und bei Gleichheit den Knoten mit der größeren Port-Nummer als Knoten auswählen, der aus dem Cluster ausgeschlossen wird. 44 KAPITEL 4. FEHLERTOLERANTE KOMMUNIKATIONSSCHICHT Rechner B Rechner C U nt er br ec hu ng Rechner A Rechner D Abbildung 4.5: Ausfall einer einzelnen Verbindung durch Fehlkonfiguration HADES verwendet zur Erkennung dieses Falls ebenfalls die Timeout-Z ähler. Erreicht dieser den höheren Wert HostFinalTimeout, so bedeutet dies, das die bisherige Fehlererkennung den Fall nicht erkannt und durch Ausschluß eines Rechners aufgelöst hat. Das heißt, der Ausfall ist von nicht mehr als einem Rechner sichtbar. Damit liegt einer von zwei Fällen vor: 1. Ein einzelner Rechner wurde vom Netz abgetrennt und dieser Rechner kann daher keinen zweiten erreichen, der den gleichen Fehler feststellt. 2. Eine einzelne Verbindung ist ausgefallen. Der erste Fall wird bereits von der bisherigen Fehlererkennung behandelt und es darf durch die neue Erkennung kein Fehler eingefügt werden. Um die beiden Fälle zu Unterscheiden wird MsgRequestKillHost um einen Timeout erweitert: Es wird jeweils der aktuelle Stand des Timeout-Zählers mitgesendet. Um Symmetrien zu brechen wird der Zählerstand um HostT imeout/2 vermindert, falls die Adresse des anderen Knotens niedriger als die eigene Adresse ist. Empfängt ein Knoten MsgRequestKillHost mit T imeout > HostF inalT imeout, so sendet er für diesen Knoten MsgKillHost an alle anderen Knoten. Durch diese Indirektion über den zweiten Knoten wird verhindert, daß ein einzelner isolierter Knoten MsgKillHost senden kann. Abbildung 4.6 faßt die Zustandsübergänge der Ausfallerkennung eines Knotens zusammen. 4.4. AUSFALLERKENNUNG MsgRequestKillHost(node,timeout) empfangen und timeout>FinalTimeout 45 fehlerfreier Cluster Timeout von "node" erkannt Timeout−Zähler von "node" angestiegen sende MsgRequestKillHost(node,timeout(node)−(node<self)?HostTimeout/2:0) MsgRequestKillHost(node,....) empfangen sende MsgKillHost(node) Abbildung 4.6: Zustandsübergänge in der Ausfallerkennung Damit ergibt sich insgesamt für MsgRequestKillHost folgendes Format: ←− 32 −→ ←− 32 −→ ←− 48 −→ ←− 32 −→ MsgRequestKillHost 10 Adresse Timeout Schließlich bleibt noch ein Sonderfall: Ein Cluster bestehend aus zwei Knoten. F ällt in diesem Cluster die einzige Verbindung aus, so entstehen zwei gleichberechtigte Teilcluster mit jeweils genau einem Knoten. In diesem Fall schließt jeder der beiden Knoten nach einem Timeout die einzige Verbindung. Keiner der beiden Knoten kann diesen Fall ohne weitere Information erkennen. Um dies zu vermeiden, sollten Cluster aus mindestens drei Knoten aufgebaut werden. Eine ausführlichere Version des Algorithmus in Pseudocode befindet sich im Anhang in Kapitel B.1. 46 KAPITEL 4. FEHLERTOLERANTE KOMMUNIKATIONSSCHICHT Zusammenfassung aller Ausfallmöglichkeiten und deren Behandlung Folgende Fälle können unter der Fehlerannahme, daß maximal ein Rechner ausfallen oder vom Netz getrennt werden darf, auftreten. Die Trennung vom Netz muß dabei nicht vollständig sein. Ausfall eines Rechners: Sobald der Timeout HostTimeout erreicht und dies von einem zweiten Knoten bestätigt wurde, wird der Rechner aus dem Cluster gelöscht. Ausfall mehrerer Verbindungen zu einem Knoten: Dieser Fall wird genau wie der vorhergehende Fall behandelt. Ausfall einer einzelnen Verbindung bei mehr als 2 Knoten: Der Timeout für diese Verbindung erreicht FinalTimeout. Daraufhin wird einer der beiden von der Verbindung verbundenen Knoten von einem dritten Knoten aus dem Cluster entfernt. Ausfall einer einzelnen Verbindung bei 2 Knoten: Beide Knoten schließen nach einem Timeout von HostTimeout jeweils die Verbindung zum anderen Knoten. Nach der Fehlererkennung und Ausschluß eines Knotens aus dem Cluster bilden alle Knoten im Cluster wieder einen vollständig verbundenen Graphen, so daß wieder jeder Knoten zu jedem anderen Nachrichten senden kann. 4.5 Versenden von Nachrichten und Antworten Die Kommunikationsschicht setzt auf TCP auf, so daß die Fehlerkorrektur von TCP auch hier zur Verfügung steht: TCP stellt eine streamorientierte Verbindung zur Verfügung und garantiert auf dieser Verbindung, daß keine Daten verf älscht, umsortiert oder ausgelassen werden. Allerdings kann TCP nicht verhindern, daß bei einem Verbindungsabbruch die zuletzt gesandten Daten verloren gehen. In diesem Fall kann der Sender nicht feststellen, welche Daten noch übertragen wurden. Werden z.B. Nachrichten A,B und C in dieser Reihenfolge übertragen, so kann bei einem Verbindungsabbruch vom Sender nicht festgestellt werden, ob keine, nur A, nur A und B oder alle drei Nachrichten vollständig empfangen wurden, es ist nur sichergestellt, daß keine Löcher“ entstehen, d.h. falls C empfangen wurde, sind auch A ” und B vorher empfangen worden. 4.5. VERSENDEN VON NACHRICHTEN UND ANTWORTEN Programm 47 Programm sendMessage Kommunikationsschicht Cmd Sen d Kommunikationsschicht MsgSen dMessa ge readMessage Abbildung 4.7: Senden ohne Bestätigung (Modus none“) ” Die Kommunikationsschicht stellt deshalb zusätzliche Modi beim Senden von Nachrichten zur Verfügung, die sich darin unterscheiden, ob bzw. wann der Empfang einer Nachricht bestätigt wird: none Dieser Modus entspricht TCP. Bricht einen Verbindung ab, so kann nichts darüber ausgesagt werden, ob die Daten angekommen sind oder ob sie w ährend der Übertragung verloren gegangen sind. Auch wenn der Sender abst ürzt während sich die Daten noch im Ausgangspuffer befinden, gehen die Daten verloren. ack In diesem Modus sendet der Empfänger eine Bestätigung für den Empfang. Diese Bestätigung wird an das Programm weitergereicht. Nach dem Empfang der Bestätigung ist sichergestellt, daß die Nachricht im Eingangspuffer gespeichert ist und von dort auch gelesen wird, solange der Empfänger nicht abstürzt. reply Dieser Modus ermöglicht es, auf eine Antwort zu warten, nachdem der Empfänger die Nachricht empfangen und bearbeitet hat. Da die Kommunikationsschicht jeweils als eigener Prozeß läuft, findet die Kommunikation zwischen zwei Programmen in drei Stufen statt: Das sendende Programm sendet CmdMessage an die Kommunikationsschicht, diese sendet MsgMessage an die Kommunikationsschicht auf dem anderen Rechner und diese schreibt die Daten in 48 KAPITEL 4. FEHLERTOLERANTE KOMMUNIKATIONSSCHICHT Programm Programm sendMessage Kommunikationsschicht Cmd Sen d Kommunikationsschicht MsgSen dMessa ge MessageID ck readMessage MsgA readResult Abbildung 4.8: Senden mit Bestätigung (Modus ack“) ” Programm Programm sendMessage Kommunikationsschicht Cmd Sen d Kommunikationsschicht MsgSendMessa ge readMessage sendReply MessageID ply MsgRe CmdReply readResult Abbildung 4.9: Senden und Warten auf Antwort (Modus reply“) ” 4.5. VERSENDEN VON NACHRICHTEN UND ANTWORTEN 49 eine Pipe, von der sie vom Empfängerprogramm gelesen werden. Können sie nicht sofort gesendet bzw. geschrieben werden, so werden die Daten zwischengepuffert. Die Reihenfolge im Puffer entspricht dabei FIFO (first in first out). Abbildungen 4.7, 4.8 und 4.9 zeigen für die drei Modi jeweils den verwendeten Ablauf. Es werden folgende Datenformate verwendet: ←− 32 −→ ←− 48 −→ ←− 32 −→ ←− 64 −→ ←− 32 −→ CmdMessage Adresse AckType MessageID Size Nutzdaten Adresse enthält die Zieladresse der Nachricht, AckType den Modus, und MessageID die generierte Nachrichten-ID, die im Modus ack“ bzw. reply“ benötigt wird, um ” ” die Verbindung zwischen der gesendeten Nachricht und der empfangenen Best ätigung herzustellen. Nach dem Header folgen Size Bytes Nutzdaten. Die Nachricht zum Senden einer Antwort ist ähnlich aufgebaut, AckType wird jedoch nicht benötigt, da für eine Antwort keine weitere Bestätigung gesendet wird. Als MessageID wird zum Antworten die ID verwendet, die mit der Nachricht, auf die geantwortet werden soll, gesendet wurde. ←− 32 −→ ←− 48 −→ ←− 64 −→ ←− 32 −→ CmdReply Adresse MessageID Size Nutzdaten Zwischen den Rechnern wird folgendes Format verwendet, die Adresse ergibt sich aus der zum Übertragen gewählten Verbindung, so daß sie nicht Teil der Nachricht ist: ←− 32 −→ ←− 48 −→ ←− 32 −→ ←− 64 −→ MsgSendMessage Size AckType MessageID ←− 32 −→ ←− 48 −→ ←− 64 −→ MsgSendReply Size MessageID Nutzdaten Nutzdaten Im Modus ack“ oder reply“ wird die MessageID für alle noch nicht bestätigten ” ” bzw. beantworteten Nachrichten gespeichert. Wird der Ausfall einer Verbindung festgestellt, so werden alle noch ausstehenden Antworten mit einer Fehlermeldung beantwortet. Auf diese Weise braucht im Programm nicht mit Timeouts zur Erkennung von ausgefallenen Verbindungen gearbeitet werden. Im Modus none“ ist dies ” nicht nötig, da ohnehin keine Bestätigung gesendet wird. 50 KAPITEL 4. FEHLERTOLERANTE KOMMUNIKATIONSSCHICHT Programm Programm sendMessage Kommunikationsschicht Cmd Sen d Kommunikationsschicht MsgSen dMessa ge MessageID readMessage Timeout CRASH readResult Abbildung 4.10: Fehlerbehandlung beim Senden Zum Ausliefern der Nachrichten an das Empfängerprogramm werden die Daten in eine Pipe geschrieben, von der sie dann vom Programm gelesen werden k önnen. Für Nachrichten und Antworten werden unterschiedliche Pipes verwendet. Dies erleichtert es zwischen den verschiedenen Nachrichtenarten auszuw ählen. Zum Beispiel ist es so einfacher möglich, auf eine Antwort zu warten, ohne daß dazu alle anderen Nachrichten gelesen und verarbeitet werden brauchen. Der Header hierf ür hat folgendes Format: ←− 48 −→ ←− 32 −→ ←− 64 −→ ←− 32 −→ Adresse AckType MessageID Size Adresse enthält die Absenderadresse und AckType den Modus. Für Antworten auf Nachrichten enthält AckType den beim Senden angeforderten Modus oder Fail, falls eine Verbindung unterbrochen wurde, bevor eine Bestätigung oder Antwort empfangen wurde. Puffergrößen Wird die Größe der Puffer explizit begrenzt, so besteht die Möglichkeit, daß kein freier Pufferspeicher mehr zur Verfügung steht, wenn eine Nachricht empfangen werden 4.6. SIGNALE 51 soll. In diesem Fall gibt es zwei Möglichkeiten: Entweder die Nachricht wird verworfen oder es werden bei vollen Puffern keine Nachrichten mehr entgegen genommen. Der erste Fall führt zum Datenverlust, der zweite führt zu Deadlocks, wenn eine Anwendung auf eine bestimmte Nachricht wartet, bevor sie weiterarbeiten kann, diese aber aufgrund eines vollen Puffers nicht entgegengenommen werden kann. Deshalb wird nicht auf eine explizite Vorgabe der Puffergrößen gesetzt, sondern auf eine implizite Beschränkung der Puffergrößen, die sich aus den eingesetzten Interaktionsmechanismen (z.B. Request/Reply) der Anwendung ergibt: Steht nur eingeschränkt Speicher zur Verfügung, so kann auf Anwendungsebene eine geeignete Strategie eingesetzt werden, die dies berücksichtigt. Senden Clients z.B. erst dann die nächste Nachricht, wenn die vorangegangene beantwortet wurde (synchrone Operationsweise), so wird für jeden Client höchstens ein Puffer benötigt. Andererseits wird dadurch auch die Geschwindigkeit gegen über einer Lösung herabgesetzt, die mehr Pufferspeicher verwendet und deshalb mehr Nachrichten parallel versenden kann, bevor auf eine Antwort gewartet werden muß. Auf diese Weise kann durch eine geeignete Wahl der Strategie flexibel auf Hardwarevoraussetzungen und Leistungsanforderungen reagiert werden. 4.6 Signale Für besondere Verwendung stellt die Kommunikationsschicht vier Signale zur Verfügung. Diese stellen jeweils nur ein Bit an Information dar, das per Nachricht gesetzt und vom Programm zurückgesetzt werden kann. Mehrfaches Setzen ist dabei äquivalent zum einfachen Setzen des Signals. Signale werden nicht in die normalen Empfangspuffer eingefügt, sondern für jedes Signal steht eine eigene Pipeline zur Verfügung. Auf diese Weise können Signale direkt gelesen werden, ohne daß zuvor andere Nachrichten aus einem Empfangspuffer gelesen werden m üssen. Damit können Signale dazu verwendet werden besonders wichtige Ereignisse anzuzeigen, die bevorzugt behandelt werden müssen. Eines der vier Signale wird deshalb dazu verwendet Änderungen an der Liste der im Cluster befindlichen Knoten anzuzeigen. Die Datenformate zum Senden von Signalen sind: ←− 32 −→ ←− 48 −→ ←− 32 −→ CmdSignal Adresse Signal-Nummer ←− 32 −→ ←− 32 −→ ←− 32 −→ MsgSignal 4 Signal-Nummer 52 4.7 KAPITEL 4. FEHLERTOLERANTE KOMMUNIKATIONSSCHICHT Zusammenfassung HADES verwendet zur Verwaltung und Fehlererkennung eine separate Kommunikationsschicht. Diese stellt allen Rechnern eine ständig aktualisierte Liste der im Cluster vorhandenen Rechner zur Verfügung und überwacht die Rechner und die Kommunikation zwischen den Rechnerknoten. Wird ein Ausfall erkannt, so werden alle Rechner über den Ausfall informiert und falls noch Antworten oder Acknowledgements von diesem Rechner ausstehen, so werden von der Kommunikationsschicht alle ausstehenden Antworten mit einer Fehlermeldung beantwortet. Auf diese Weise braucht in der darüberliegenden Schicht nicht mit Timeouts gearbeitet werden, es wird garantiert, daß entweder eine Antwort oder eine Fehlermeldung empfangen wird. In Anhang C.1 findet sich eine Kurzreferenz für die von der Kommunikationsschicht zur Verfügung gestellten Methoden. Kapitel 5 HADES-Konzepte Eines der Hauptprobleme, Daten verteilt und fehlertolerant zu speichern, ist das Adressierungsproblem: Einerseits muß ein möglichst direkter Zugriff gewährleistet werden, wenn niedrige Zugriffszeiten erreicht werden sollen, andererseits k önnen Rechneradressen nicht Teil eines Adressierungsschemas sein, da sich die Rechneradresse, unter der Daten erreichbar sind, bei einem Ausfall eines Rechners im Cluster ändern kann. Da somit eine statische Adressierungslösung ausscheidet, kommt das Problem hinzu, die dynamischen Verwaltungsinformationen, die zum lokalisieren der Daten benötigt werden, ebenfalls im Cluster zu verteilen und zu verwalten. Dieses Kapitel beschäftigt sich daher damit, wie dieses Adressierungsproblem in HADES gelöst wurde. 5.1 Management globaler Daten Zur Koordination zwischen den beteiligten Servern werden Daten ben ötigt, die auf allen Servern identisch gehalten werden müssen und die im folgenden als globale Daten bezeichnet werden. Zu diesen Daten gehören unter anderem eine Liste der beteiligten Server, die Information über die Datenverteilung sowie die Liste mit den Tabellennamen. Die einfachste Möglichkeit besteht darin, die Verwaltung dieser Daten einem zentralen Koordinator zu überlassen, der alle Änderungen an diesen Daten koordiniert und alle anderen Server über Änderungen informiert. Auf diese Weise wird verhindert, daß Inkonsistenzen oder unterschiedliche Sichten auf globale Daten entstehen 53 54 KAPITEL 5. HADES-KONZEPTE können. Jeder Server im Cluster erhält vom Koordinator die gleiche Kopie der globalen Daten. Um den Koordinator zu bestimmen gibt es unterschiedliche M öglichkeiten, zu den bekanntesten zählen Wahlverfahren. Eine gute Übersicht über die verschiedenen Verfahren bietet [TvS02, S. 262f]. Es wird vorausgesetzt, daß sich alle Prozesse, die an der Wahl teilhaben, in einem Kriterium wie z.B. einer Prozeß-Nummer oder Netzwerkadresse unterscheiden. Diese Asymmetrie wird ausgenutzt, um in einer Abstimmung der beteiligten Prozesse einen Koordinator zu bestimmen, z.B. indem der Prozeß mit der größten Nummer als Koordinator ausgewählt wird. Eine einfache Wahl ohne besondere Beschränkungen hat jedoch einen Nachteil: Sie verhindert nicht, daß ein Server zum Koordinator gewählt wird, der u.U. erst neu zu einem Cluster hinzugekommen ist und noch keine Kopie der globalen Daten besitzt. Es gibt zwei Möglichkeiten, dieses Problem zu lösen: 1. Ein neuer Server, der an einer Wahl teilnimmt, kopiert vorher die globalen Daten. 2. Man verhindert, daß neue Server zum Koordinator gewählt werden. Bei der ersten Möglichkeit besteht das Problem, daß zu diesem Zeitpunkt nicht festgestellt werden kann, woher die globalen Daten kopiert werden k önnen: Der Koordinator ist ausgefallen und von den anderen Servern im Cluster ist nicht bekannt, ob sie über eine aktuelle Kopie verfügen, ob sie veraltete Informationen besitzen oder ob es sich ebenfalls um neu hinzugekommene Server handelt, die noch keine Informationen über globale Daten haben. HADES verwendet daher die zweite Strategie. Der Cluster wird durch sukzessives Einfügen von Servern in den Cluster aufgebaut. Um den Koordinator zu bestimmen, wird die Reihenfolge verwendet, in der die Server eingefügt wurden: Der erste Server im Cluster wird Koordinator, der zweite Server wird Backup-Koordinator, der nahtlos die Aufgaben des Koordinators übernehmen kann, sobald dieser ausfallen sollte. Sobald ein Server ausfällt rücken die anderen in der Reihenfolge nach. Eine Wahl ist bei diesem Verfahren nicht nötig, da durch die Reihenfolge immer allen Servern bekannt ist, welcher Server die Aufgabe des Koordinators übernimmt. Auf diese Weise wird immer der am längstem im Cluster befindliche Server Koordinator. Damit ist sichergestellt, daß dieser über eine vollständige Kopie der globalen Daten verfügt. Werden neue Server zu einem Cluster hinzugefügt, so erhalten sie bei der Anmeldung zusammen mit den anderen globalen Daten auch die aktualisierte Liste der Server im Cluster. 5.2. DATENVERTEILUNG UND ADRESSIERUNG 55 Eine konsistente Verteilung der globalen Daten muß auch bei einem Ausfall des Koordinators gewährleistet werden können. Hierfür existieren je nach Art der Daten zwei Strategien: Die Datenverteilung kann der nachrückende Backup-Koordinator auf Basis der bekannten Informationen nach einem Ausfall neu berechnen. Aus diesem Grund braucht die Verteilung der Datenverteilung nicht besonders gesch ützt werden, zumal diese Datenverteilung nach einem Ausfall des alten Koordinators ohnehin veraltet ist. Die zentrale Berechnung durch den neuen Koordinator verhindert, daß sich unterschiedliche Ergebnisse aufgrund von leicht unterschiedlichen Sichten auf den Cluster ergeben können. Anders verhält es sich mit Daten, die aufgrund von Anfragen von Clients oder anderen Servern geändert werden. In diesem Fall muß sichergestellt werden, daß diese Daten entweder an alle oder an keinen Server verteilt werden. Hierzu informiert der Koordinator zunächst den Backup-Koordinator über die bevorstehende Änderung. Erst danach werden die Daten an alle anderen Server versandt. Sobald Empfangsbestätigungen von allen Servern vorliegen, wird der Backup-Koordinator über den erfolgreichen Abschluß informiert. Fällt der Koordinator vorher aus, so übernimmt der Backup-Koordinator die Rolle des Koordinators und verteilt die globalen Daten erneut, gleichzeitig rückt der nächste Server in der Liste zum neuen BackupKoordinator auf. Auf diese Weise wird sichergestellt, daß auch beim Ausfall des Koordinators immer alle Server im Cluster über die gleiche aktuelle Kopie der globalen Daten verfügen. Ein Ausfall des Koordinators, bevor er die Nachricht an den Backup-Koordinator senden konnte, ist unkritisch, da in diesem Fall keine Daten verteilt werden und somit auch keine Inkonsistenzen auftreten k önnen. In diesem Fall wiederholt der Client bzw. der Server die Anfrage und sendet sie an den neuen Koordinator. Dieses Vorgehen ist eine Variation des Read-One-Write-All (ROWA) Prinzips [BHG87, HHB96]. Server verwenden jeweils ihre lokale Kopie zum Lesen, aber Schreibzugriffe werden nur via Koordinator ausgeführt, der sicherstellt, daß die geänderten Daten auf allen Server geschrieben werden. Write-All-Available, das Daten nur auf verfügbaren Servern schreibt, bringt hier keine Vorteile, da ausgefallene Server ohnehin automatisch aus dem System genommen und ihre Aufgaben von den verbleibenden Servern übernommen werden. 5.2 Datenverteilung und Adressierung Eine redundante verteilte Speicherung von Daten in einem Cluster muß mehrere Anforderungen erfüllen: 56 KAPITEL 5. HADES-KONZEPTE • Auch bei einem Ausfall eines Servers muß auf die Daten zugegriffen werden können. • Fällt ein Server aus, so sollen nach einer Übergangszeit Daten wieder redundant vorliegen, so daß ein Ausfall eines weiteren Servers zu keinem Datenverlust führt. Die Dauer der Übergangszeit hängt davon ab, wie lange es dauert, die Daten, die nicht mehr redundant vorliegen, über das Netz auf einen zweiten Server zu übertragen (vgl. Abschnitt 7.3.8). • Auf Daten muß effizient zugegriffen werden können, ohne daß alle Server im Cluster nach den Daten abgesucht werden müssen. Im Regelfall sollte sich der Ort, an dem Daten gespeichert sind, ohne Netzwerk-Zugriffe ermitteln lassen und nur in Ausnahmefällen weitere Zugriffe notwendig werden. Dabei muß berücksichtigt werden, wie Daten adressiert werden sollen. • Daten sollen gleichmäßig im Cluster verteilt werden, um eine gleichmäßige Auslastung der Ressourcen zu erreichen. • Bei Änderungen im Cluster sollten möglichst wenige Daten kopiert werden müssen, um die Netzwerklast möglichst gering zu halten. • Es muß einfach sein, die Daten zu bestimmen, die umkopiert werden m üssen. • Daten müssen auf verschiedenen Servern gespeichert werden. Es ist nicht ausreichend sicherzustellen, daß Daten zweimal gespeichert werden, denn wenn beide Kopien auf dem gleichen Server liegen, führt ein Ausfall dieses Servers zum Verlust beider Kopien und somit zum kompletten Verlust dieser Daten. Darin sind zwei Aspekte enthalten: Wie werden Daten verteilt, d.h. wieviele Daten werden auf welchem Server gespeichert, und wie kann man die Daten ansprechen, also an welcher Adresse liegen bestimmte Daten? Den ersten Aspekt m öchte ich als Datenverteilung bezeichnen und den zweiten als Adressierung. Die Datenverteilung hängt dabei von der Adressierung ab: Kenne ich die Adressen aller Datens ätze, so kenne ich damit auch die Verteilung. Umgekehrt nützt es nur wenig, nur die Verteilung zu kennen, denn damit ist es nicht möglich die Daten anzusprechen. Deshalb werde ich im folgenden nur die Adressierung betrachten, die Verteilung ergibt sich daraus automatisch. Die Serveradresse des Clusterknotens, der ein bestimmtes Datum speichert, kann nicht Bestandteil der Adressierung sein, da sich durch Serverausf älle der Ort, an dem Daten gespeichert sind, ändern kann. Ich möchte daher annehmen, daß Daten über fortlaufende Seitennummern adressiert werden. Seiten sollen in diesem Kontext ein zusammenhängendes Stück Speicher bezeichnen, das eine beliebige Größe 5.2. DATENVERTEILUNG UND ADRESSIERUNG 57 haben kann und in dem beliebige Daten gespeichert sein können. Die Seitennummer ist somit vergleichbar zu der Blocknummer eines Datenblocks auf einer Festplatte nur mit dem Unterschied, daß die Größe nicht festgelegt ist und daß jede Seite eine andere Größe haben kann. Es ist vorgesehen, daß pro Seite ein Datensatz gespeichert wird1 , wobei es Aufgabe der Client-Applikation ist, festzulegen, wie die Daten innerhalb einer Seite angeordnet sind. Gegenüber einer Adressierung, die einen beliebigen Schlüssel als Adresse verwendet, hat dieses Verfahren Vor- und Nachteile: Es ist einfacher, über Daten zu iterieren: Kennt man die Anzahl N der gespeicherten Seiten, so ergeben sich die Adressen zu 0 . . . N − 1. Dies ist bei beliebigen Schl üsseln nicht der Fall, so daß hier zusätzlicher Aufwand zum Iterieren betrieben werden muß. Soll auf Daten über einen beliebigen Schlüssel zugegriffen werden, so ist es andererseits aber ein Vorteil, wenn dieser Schlüssel direkt zur Adressierung verwendet werden kann und nicht vorher aus dem Schlüssel eine Seitennummer berechnet werden muß. Die Anforderungen nach gleichmäßiger Auslastung, geringer Netzwerklast nach einem Ausfall und einfachem Zugriff lassen sich nicht alle gleichzeitig optimal erf üllen, es können jeweils lediglich zwei von diesen optimal erreicht werden, bei der dritten muß dagegen ein Kompromiß eingegangen werden: Möchte man eine sehr gleichmäßige Auslastung und geringe Netzwerklast bei einem Serverausfall, so wird der Zugriff auf Daten aufwendiger: Nach einem Serverausfall müssen Daten, die auf diesem Server lagen auf anderen Servern rekonstruiert werden. Dazu wird jeweils von der Reserve-Kopie eine neue Kopie auf einem anderen Server angelegt, so daß wieder zwei Kopien existieren. Minimale Netzwerklast wird dann erreicht, wenn nur genau von den Daten Kopien angelegt werden, die auf dem ausgefallenen Server gespeichert waren. Um eine möglichst gleichmäßige Auslastung zu erreichen, müssen die Daten gleichmäßig auf die verbliebenen Server verteilt werden. Dadurch wird es jedoch aufwendiger den Ort zu ermitteln, auf dem Daten aktuell gespeichert sind. Sei f (a) eine Funktion, die aus der Adresse a den Server ermittelt, auf dem Daten gespeichert sind, so muß diese nach einem Ausfall abgeändert werden in: f2 (a) : falls f (a) ausgefallen ∗ f (a) = f (a) : sonst Nach einem Ausfall muß also mit zwei Funktionen (f (a) und f 2 (a)) gearbeitet werden, nach einem weiteren Ausfall mit drei Funktionen, usw. Während dieser Fall noch recht einfach zu handhaben ist, gestaltet sich dies schwieriger, wenn ein Server hinzukommt. In diesem Fall muß eine Abbildung gefunden 1 Da HADES keine Kontrolle über den Inhalt einer Seite hat, kann dies nicht erzwungen werden, allerdings ist die Zugriffs- und insbesondere die Locking-Semantik an diese Annahme angepaßt. 58 KAPITEL 5. HADES-KONZEPTE werden, die einen Anteil der Seiten auf den neuen Server abbildet, der genau so groß ist, daß eine gleichmäßige Auslastung erreicht wird, sich aber der Server für die anderen Seiten nicht ändert. Das Problem kann wie folgt modelliert werden: Sei fi (a) die Abbildung für i Server (durchnumeriert von 0 bis i − 1) und si (a) eine Selektionsfunktion, die genau für 1 aller a 1 und ansonsten 0 liefert. Dann läßt sich die Abbildung wenn ein Server i hinzukommt wie folgt abändern: i : falls si+1 (a) = 1 fi+1 (a) = fi (a) : sonst Damit durch die Selektion die gleichmäßige Verteilung der verbliebenen Seiten nicht zerstört wird, müssen die Selektionsfunktionen si (a) voneinander unabhängig sein, 1 aller a selektieren. d.h. für i 6= j muß si (a) ∗ sj (a) genau i∗j Falls diese Bedingung nicht erfüllt ist, wird keine gleichmäßige Verteilung erreicht, wie das folgende Beispiel illustriert: Sei s2 (a) = 1, falls a mod 2 = 1 und s4 (a) = 1, falls a mod 4 = 0. In diesem Fall selektiert s2 (a) nur ungerade Seiten, während s4 (a) nur gerade Seiten selektiert. Somit werden nur Seiten umverteilt, die nicht von s2 (a) selektiert werden. Eine gleichmäßige Auslastung wird also nicht mehr erreicht. Ähnlich wie oben kommt mit jedem neuen Server eine Funktion s i (a) hinzu. In HADES wird der Cluster wie in Abschnitt 5.1 beschrieben dadurch aufgebaut, daß nacheinander Server in den Cluster eingefügt werden. Damit würden ebensoviele si (a) benötigt, wie Server im Cluster vorhanden sind. Insgesamt führt dieser Ansatz aufgrund der vielen Adressierungsfunktionen, die zum Einsatz kommen, zu keiner einfachen Möglichkeit der Adressierung. Dabei wurde die Anforderung, daß bei einer Serveränderung die Seiten einfach bestimmt werden können, die vom Umkopieren betroffen sind, noch gar nicht ber ücksichtigt. Auch die auf den ersten Blick vielleicht einfachste Möglichkeit, die Adressen aller Seiten in einer Tabelle abzulegen, die für jede Seite die Serveradresse enthält, hat Nachteile. Diese Tabelle muß bei jedem Einfügen einer zusätzlichen Seite geändert werden. Dabei ist zu beachten, daß diese Tabelle auf allen Servern aktuell gehalten werden muß, damit ermittelt werden kann, auf welchem Server eine Seite liegt und damit nicht zwei Seiten mit der gleichen Seitennummer aber unterschiedlichem Inhalt auf unterschiedlichen Servern eingefügt werden. Sonst könnte nicht mehr gewährleistet werden, daß ein Client Daten lesen kann, die ein anderer Client geschrieben hat. 5.3. ZWEISTUFIGE ADRESSIERUNG 59 Diese global vorhandene Tabelle auf einem aktuellen und konsistenten Stand zu halten bedeutet einen nicht unerheblichen Kommunikationsaufwand, weshalb diese Lösung nur geringe Leistungsfähigkeit bietet und ich sie nicht weiter verfolgt habe. Nimmt man eine höhere Netzwerklast bei einem Serverausfall in Kauf, so können die Daten nach einem Serverausfall so umsortiert werden, daß wieder eine einfache Adreßberechnung zum Einsatz kommen kann. Nimmt man beispielsweise an, daß sich die Servernummer, auf dem Daten gespeichert werden, per Modulo-Arithmetik aus der Seitennummer berechnet, Servernummer = Seitennummer mod Serveranzahl so müssen fast alle Daten bei einem Serverausfall oder wenn ein Server hinzukommt, umkopiert werden, da sich das Modul ändert. Dies liefert zwar eine sehr gleichmäßige Verteilung, erzeugt aber in der Übergangsphase, in der mit beiden Adressierungsarten parallel gearbeitet werden muß, eine hohe Netzlast, wenn alle Daten umkopiert werden. Aus diesen Gründen habe ich eine Lösung gewählt, die einen Kompromiß bei der gleichmäßigen Auslastung eingeht, die aber einen einfachen Zugriff auf die Daten und niedrige Netzlast bietet. Diese Lösung wird im folgenden Abschnitt beschrieben. 5.3 Zweistufige Adressierung Aus der Seitennummer wird in einer zweistufigen Abbildung die Adresse des Servers ermittelt, auf dem die Seite gespeichert ist (Abbildung 5.1): Slice 0 Slice 1 Slice 2 .... Seiten 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ... ... ... ... ... ... ... ... Slice 0 1 2 3 4 P-Server Server A Server A Server B Server B Server C S-Server Server B Server B Server C Server C Server A Abbildung 5.1: Beispiel: Zweistufige Adressierung T-Server — — — — — Slice Primär− server 0 1 2 3 4 5 A A B B C C Sekundär− server B B C C A A Slice Primär− server 0 1 2 3 4 5 A A B B A A Sekundär− server B B A A B B Last−Rebalancierung KAPITEL 5. HADES-KONZEPTE Wiederherstellung der Redundanz 60 Slice Primär− server 0 1 2 3 4 5 B A B B A A Sekundär− server A B A A B B Abbildung 5.2: Datenverteilung: 2. Stufe 1. Im ersten Schritt wird die Seitennummer auf die Slice-Adresse abgebildet mit: Slice = Seitennummer mod SliceAnzahl Die Slice-Anzahl ist eine konstante Größe, die sich zur Laufzeit auch bei einem Ausfall oder Hinzukommen von Servern nicht ändert. Dies entspricht einer horizontalen Fragmentierung der Daten [SK98, S. 589] 2. Im zweiten Schritt werden jedem Slice zwei Rechner zugeordnet (Prim är- und Sekundärserver). Somit repräsentiert ein Slice eine Klasse von Seiten, die alle immer auf dem selben Server gespeichert werden. Bei der Neuberechnung wird auf eine gleichmäßige Lastverteilung geachtet. Dabei berücksichtigt HADES die vorangegangene Verteilung und minimiert Änderungen. Auf diese Weise kann die Menge der Daten, die bei der Neuverteilung auf andere Rechner umkopiert werden muß, reduziert werden. Abbildung 5.2 verdeutlicht dies: Nach dem Ausfall von Rechner C brauchen Daten in den Slices 0 und 1 nicht umkopiert werden. Es werden nur Daten in den Slices 2-5 kopiert, um die Redundanz wiederherzustellen (grau unterlegt). Durch ein abschließendes Vertauschen von Primär- und Sekundärservern wird eine weitere Lastverteilung erreicht. Diese Adressierung bietet verschiedene Vorteile: Die erste Stufe der Abbildung ist konstant und muß daher nicht an andere Server übertragen zu werden. Die Tabelle der zweiten Stufe, die ich im folgenden als Servertabelle bezeichnen m öchte, braucht nur noch Slice-Adressen abzubilden und ist daher recht klein und kann einfach an andere Server übertragen werden. Sie ändert sich nur, falls Server hinzukommen oder ausfallen, sie ändert sich jedoch nicht, wenn Daten eingefügt oder gelöscht werden. Diese Adressierung ermöglicht auch eine einfache Bestimmung der Seiten, die umkopiert werden müssen, wenn sich Server ändern. Es werden jeweils immer alle Seiten eines Slice kopiert. 5.4. BERECHNUNG DER SERVERTABELLE 61 Die Slice-Anzahl ist konstant und wird zur Startzeit festgelegt. Sie muß dabei mindestens so groß sein, wie die Anzahl der Server, wobei eventuelle sp ätere Erweiterungen zu berücksichtigen sind. Je größer die Slice-Anzahl ist, desto mehr Möglichkeiten bleiben für zukünftige Erweiterungen, desto mehr Overhead entsteht allerdings auch. Die Servertabelle enthält für jeden Slice 3 Serveradressen: • primärer Server • sekundärer Server • ternärer Server Der primäre Server ist der Server, der Anfragen von Clients bearbeitet, und nur dieser Server bearbeitet für das jeweilige Slice Anfragen von Clients und stellt die Datenkonsistenz sicher (vgl. Abschnitt 6.2.1). Der sekund äre Server speichert eine vollständige Kopie aller Daten des primären Servers, die zu diesem Slice gehören. Solange ein Server beim Umkopieren von Daten nur eine unvollst ändige Kopie besitzt, nachdem ein Server ausgefallen oder hinzugekommen ist, wird er in der Servertabelle als ternärer Server aufgeführt. Dies hat den Sinn, daß wenn ein Server hinzugekommen ist, der sekundäre Server erst dann seine Rolle als sekundärer Server aufgibt, nachdem eine vollständige neue Kopie auf einem anderen Server angelegt wurde. Auf diese Weise wird die Redundanz beim Hinzukommen eines Servers immer gew ährleistet. In Abbildung 5.1 ist Server A z.B. primärer Server für Slice 0 und 1 und sekundärer Server für Slice 2 und 3. Ternäre Server sind nicht eingetragen, da zu dieser Zeit keine Daten umkopiert werden. Aufgrund der geringen Größe der Servertabelle und der geringen Häufigkeit von Änderungen an der Tabelle, kann diese an die Clients verteilt werden. Damit k önnen Clients selbständig ermitteln, auf welchem Server Daten abgelegt sind. Auf diese Weise können Clients direkt den Rechner ansprechen, auf dem die Daten abgelegt wurden. Sollte sich die Tabelle einmal geändert haben und der Client den falschen Rechner ansprechen, so wird er über die Änderung informiert und kann somit normalerweise spätestens mit den zweiten Zugriff auf die Daten zugreifen. 5.4 Berechnung der Servertabelle Wenn neue Server hinzukommen oder wegfallen muß die Servertabelle neu berechnet werden. Dazu wird folgende Strategie verwendet, um die Servertabelle neu zu berechnen. 62 KAPITEL 5. HADES-KONZEPTE 1. Falls der primäre Server ausgefallen ist, wird er durch den sekundären Server ersetzt. 2. Für ausgefallene Server wird ein neuer ternärer Server eingetragen, der möglichst gering ausgelastet ist. 3. Es wird versucht durch zusätzliche Vertauschungen die Auslastung als Primärserver gleichmäßig zu verteilen. Nach dem Ausfall oder Hinzukommen eines Rechners oder nach dem Aufstieg eines Ternärservers zum Sekundärserver wird folgender Algorithmus durchgeführt: 1. Ausgefallene Rechner werden aus der Servertabelle gestrichen. 2. Ist ein Primärserver ausgefallen, so steigt der Sekundärserver zum Primärserver auf. 3. Für Slices, für die kein Sekundärserver mehr existiert, wird der am wenigsten ausgelastete Rechner als Ternärserver eingetragen. 4. Ist ein Primärserver eines Slices häufiger Primärserver als der Sekundärserver des gleichen Slices, so werden die Rollen zur Lastverteilung vertauscht. Eine ausführliche Darstellung des Algorithmus in Pseudocode befindet sich in Abschnitt B.2 im Anhang. Diese Servertabelle wird an alle Server im Cluster verteilt. Sobald ein tern ärer Server über eine vollständige Kopie der Daten verfügt, steigt er zum sekundären Server auf, woraufhin dieser die nicht mehr benötigte Kopie freigibt. Nach dem Aufsteigen eines Servers zu einem neuen sekundären Server wird der Algorithmus erneut aufgerufen, um zusätzliche Chancen zur Lastverteilung zu nutzen. Dies ist erforderlich, wenn mehrere Server gleichzeitig2 hinzukommen, wie das folgende Beispiel zeigt (Primärserver=P, Sekundärserver=S, Ternärserver=T: 2 Server werden zwar nacheinander in den Cluster eingefügt, werden jedoch mehrere Server kurz hintereinander eingefügt, so wird eine noch nicht abgeschlossene Umverteilung abgebrochen und die Anpassung der Servertabelle neu gestartet. Aus Sicht des Algorithmus zur Anpassung der Servertabelle entspricht dies dann der gleichzeitigen Aufnahme mehrerer Server. 5.4. BERECHNUNG DER SERVERTABELLE 63 Ausgangslage: Slice 0 1 2 3 4 P A A A B B S B B B A A T P B A A A B S A B B B A T C D E C D Hinzufügen von 3 Servern C,D und E: Slice 0 1 2 3 4 P A A A B B S B B B A A T ⇒ Slice 0 1 2 3 4 ⇒ Slice 0 1 2 3 4 P B A A A B S C D E C D T Nach dem Umkopieren werden die sekundären Server durch die ternären ersetzt und der Algorithmus erneut aufgerufen: Slice 0 1 2 3 4 P B A A A B S C D E C D T ⇒ Slice 0 1 2 3 4 P C D E A B S B A A C D T E ⇒ Slice 0 1 2 3 4 P C D E A B S B E A C D T Ein weiterer Aufruf des Algorithmus führt zu keiner weiteren Änderung. Der Grund dafür, daß mehr als ein Durchgang erforderlich ist, liegt darin, daß pro Durchgang nur ein Server (Primärserver oder Sekundärserver) ersetzt werden kann. Müssen zur Lastverteilung dagegen beide, primärer und sekundärer, Server ersetzt werden, dann reicht ein Durchgang nicht aus. Interessant ist anzumerken, daß die Bedingung zum Vertauschen von Prim är- und Sekundärserver dazu führen kann, daß bei jedem Durchgang primäre und sekundäre Server ihre Rollen tauschen, wenn sich zwei Server in der Häufigkeit der Verwendung als Primärserver genau um eins unterscheiden, da beim Tausch die H äufigkeit des einen um eins vermindert und die Häufigkeit des anderen um eins erhöht wird, so 64 KAPITEL 5. HADES-KONZEPTE daß beim nächsten Durchgang wieder zurückgetauscht wird. Zum Beispiel: Slice 0 1 2 P A A B S B B A ⇒ T Slice 0 1 2 ⇒ Slice 0 1 2 P A B B S B A A P B A A S A B B T ⇒ T ⇒ Slice 0 1 2 P A A B S B B A Slice 0 1 2 P B B A S A A B T T Dies führt jedoch zu keinem Problem, da der Algorithmus nur nach einem Umkopieren erneut aufgerufen wird, was beim Tausch zwischen Prim ärserver und Sekundärserver nicht der Fall ist, da bereits alle Daten vorhanden sind und nur die Rolle getauscht wird. Vertauscht man Primärserver und Sekundärserver nur dann, wenn die Differenz der Häufigkeit der Verwendung als Primärserver mindestens zwei beträgt, so tritt dieser Effekt nicht auf, allerdings habe ich damit teilweise schlechtere Verteilungen erhalten. Zum Beispiel wird in folgender Verteilung das Ungleichgewicht in der Verwendung als Primärserver mit der geänderten Bedingung nicht beseitigt, da für keinen Slice p usage(), das in der folgenden Tabelle die H äufigkeit der Verwendung als Primärserver angibt, des Primärservers um mehr als 1 höher ist als p usage() des Sekundärservers. Slice 0 1 2 3 4 5.5 P C D A B B S A E E C D T Server i A B C D E p usage(i) 1 2 1 1 0 Zusammenfassung In einem verteilten Speichersystem ist eines der Hauptprobleme, die Daten sicher zu speichern und gleichzeitig einen effizienten Zugriff zu erm öglichen. HADES verwendet zur Lösung dieses Adressierungsproblems eine zweistufige Adressierung: Seiten werden auf Slices abgebildet und diese über eine Tabelle den Servern im Cluster zugeordnet. Da sich diese Tabelle nur selten ändert und nur einen geringen Umfang hat, kann sie auch an die Clients verteilt werden, was den Clients im nicht-Fehlerfall einen direkten Zugriff auf die Daten ermöglicht. Kapitel 6 Verteiltes Datenmanagementsystem Dieses Kapitel beschreibt die Architektur des verteilten Datenmanagementsystems sowie die Protokolle, die zum Einsatz kommen. Die Kommunikation basiert auf der im vorangegangenen Kapitel vorgestellten Kommunikationsschicht. Das Datenmanagementsystem implementiert die in Kapitel 5 vorgestellte Adressierung, um Daten redundant und verteilt zu speichern. Um den Zugriff zu vereinfachen, wird für jede Daten-Tabelle ein eigener Adreßraum zur Verfügung gestellt, der jeweils mit der Seitenadresse 0 beginnt. Für die in den folgenden Abschnitten angegebenen Datenformate soll wie im vorangegangenen Kapitel gelten: Serifenlos gedruckte Bezeichner stehen für vordefinierte Konstanten, während kursiv gedruckte Bezeichner für Variablen verwendet werden. Da hier auch komplexere Objekte übertragen werden, die vor der Übertragung serialisiert werden und exakte Bit-Größen vom Inhalt der Daten abhängen, möchte ich zur übersichtlicheren Darstellung auf eine Größenangabe verzichten. Datenformate werden ohne die von der Kommunikationsschicht zusätzlich eingefügten Header betrachtet, auch wenn die Implementierung diese natürlich verwendet. 6.1 Client-Programmierschnittstelle Die von HADES für Clients zur Verfügung gestellte Programmierschnittstelle übernimmt mehrere Aufgaben: Zur Client-Anwendung hin bietet sie eine Schnittstelle in Form von Methoden-Aufrufen an und zur Server-Seite eine Schnittstelle in Form 65 66 KAPITEL 6. VERTEILTES DATENMANAGEMENTSYSTEM eines nachrichtenbasierten Protokolls. Die Client-Programmierschnittstelle übernimmt das Ein- und Auspacken der Nachrichten und ermittelt die Serveradressen, an die eine Nachricht gesendet werden muß. Darüberhinaus führt sie eine Fehlerbehandlung für die Fälle durch, die automatisch korrigiert werden können. Fällt zum Beispiel ein Server aus und muß aus diesem Grund eine Nachricht erneut gesendet werden, so wird dies transparent von der Programmierschnittstelle übernommen, so daß sich der Programmierer nicht selbst darum kümmern muß. Damit mehrere Operationen verschachtelt parallel ausgeführt werden können, um den Einfluß von Latenzzeiten zu verringern, sind alle Funktionen (mit Ausnahme der Funktionen zum Anlegen und Löschen von Tabellen) in zwei Teilfunktionen aufgeteilt: Es gibt eine Funktion zum Starten der Operation, die eine MessageID zurückliefert, mit der dann auf die Terminierung der Operation gewartet und das Ergebnis ausgelesen werden kann. So kann man z.B. sehr einfach mehrere Seiten parallel lesen, ohne daß sich die auftretenden Latenzzeiten aufaddieren, wie das Code-Fragment in Abbildung 6.1 zeigt. 6.2 6.2.1 Datenoperationen Konsistenzeigenschaften HADES unterstützt zwei Modi, die sich in den Eigenschaften der Datenkonsistenz unterscheiden: Es kann mit und ohne Transaktionen gearbeitet werden. Mit aktivierten Transaktionen unterstützt HADES die klassische ACID-Eigenschaft [HR83]: Atomicity, Consistency, Isolation und Durability. Atomicity: Aus der Sicht eines Client-Programms wird eine Transaktion komplett oder überhaupt nicht ausgeführt. Änderungen werden für andere Clients und Programme erst sichtbar, sobald eine Transaktion erfolgreich ein Commit ausführen konnte. Dies bedeutet, daß die Transaktion komplett bearbeitet werden konnte und keine Fehler aufgetreten sind. Andernfalls, falls Fehler aufgetreten sind oder falls ein Abort ausgeführt wurde, werden die Änderungen rückgängig gemacht, so als ob die Transaktion niemals existiert hätte. Consistency Preservation: Konsistenzbedingungen, die für Daten definiert wurden, werden von Transaktionen aufrecht erhalten: Eine Transaktion f ührt von einem konsistenten Zustand der Datenbank zu einem anderen konsistenten Zustand. Dieses kann nicht automatisch garantiert werden. Anwendungen müssen daher so implementiert werden, so daß beim Ausführen der Operationen zwischen dem Beginn einer Transaktion und deren Commit wieder ein konsistenter Zustand erreicht wird. 6.2. DATENOPERATIONEN 67 MessageID id1,id2,id3; int table,page1,page2,page3; Array<char> data1,data2,data3; // 3 Seiten parallel lesen: Operation starten id1=db.beginReadPage(table,page1); id2=db.beginReadPage(table,page2); id3=db.beginReadPage(table,page3); // Auf Ende warten. Daten liegen in data1..data3, // der Return-Code in r1..r3 r1=db.endReadPage(id1,data1); r2=db.endReadPage(id2,data2); r3=db.endReadPage(id3,data3); Abbildung 6.1: Code-Fragment: Parallele Lesen von Seiten Isolation: Jede Transaktion ist von den anderen Transaktionen isoliert, so als ob sie als einzige auf einem System ausgeführt würde. Jede Transaktion kann dabei nur auf konsistente Daten zugreifen: Sie sieht nur Änderungen von anderen Transaktionen, die bereits vollständig mit Commit abgeschlossen wurden, oder Änderungen, die sie selbst durchgeführt hat. Durability: Wenn eine Transaktion erfolgreich mit Commit abgeschlossen wurde, wird garantiert, daß alle Änderungen auch nach danach folgenden Softwareoder Hardwarefehlern erhalten bleiben, bis sie von einer anderen Transaktion überschrieben werden. Transaktionen erlauben die Koordination mehrerer Clients, indem sie den Zugriff auf Daten kontrollieren und koordinieren. Ein Beispiel hierf ür ist die Datenaggregation: Ein Client liest alle vorhandenen Daten aus, wertet sie aus, faßt sie zusammen, löscht die einzelnen Werte und schreibt das aggregierte Ergebnis in die Datenbank zurück. Die Transaktion garantiert nun, daß genau die Werte gelöscht werden, die im Aggregat zusammengefaßt wurden und daß keine anderen Werte, die gleichzeitig von anderen Clients in die Datenbank eingefügt werden sollten, gelöscht werden, die noch nicht im Aggregat erfaßt sind. Auf diese Weise kann die Arbeit mehrerer Clients koordiniert werden, ohne daß eine direkte Kommunikation zwischen den einzelnen Clients erforderlich wäre. 68 KAPITEL 6. VERTEILTES DATENMANAGEMENTSYSTEM Ohne aktivierte Transaktionen garantiert HADES die Eigenschaften bez üglich Datenkonsistenz, die eine fehlerfreie Festplatte bietet: • Sobald Daten erfolgreich geschrieben wurden, bleiben sie auch bei Softwareoder Hardwarefehlern (innerhalb des durch die Fehlerannahme vorgegebenen Rahmens) erhalten, bis sie durch neue Daten überschrieben werden. • Beim Lesen werden immer nur Daten gelesen, die vorher erfolgreich geschrieben wurden. Insbesondere kann es nicht passieren, daß bei einem gleichzeitigen Zugriff mehrerer Programme Daten gelesen werden, für die das Schreiben noch nicht abgeschlossen ist und noch mit einem Fehler abbrechen kann. An einem Beispiel möchte ich verdeutlichen, welche Inkonsistenz der zweite Punkt ausschließt: Angenommen ein Client überschreibt Daten auf dem Primärserver und ein anderer Client liest diese Daten. Danach fällt der Primärserver aus. Hat der Sekundärserver zu diesem Zeitpunkt noch keine Kopie der neuen Daten, so liefert ein weiterer Lesezugriff wieder die alten Daten, d.h. Daten haben sich durch den Ausfall geändert, ohne daß ein Schreibzugriff stattgefunden hat. Um dies zu verhindern, finden bei HADES alle Zugriffe über den Primärserver statt, der die Zugriffe und den Abgleich von Kopien koordiniert und das Auftreten von Inkonsistenzen verhindert. Damit der Primärserver nicht zum Flaschenhals wird, werden für unterschiedliche Slices unterschiedliche Prim ärserver verwendet und so die Last verteilt. Die folgenden Abschnitte beschreiben wie die Anforderungen in der Implementierung umgesetzt wurden. 6.2.2 Verteilung der Servertabelle Sobald ein Server hinzukommt oder wegfällt, berechnen alle Server eine neue Serverliste, indem ausgefallene Server aus der Liste gestrichen werden und neue an das Ende angehängt werden. Diese Liste enthält die Reihenfolge, in der Server in den Cluster eingebunden wurden, und wird verwendet, um den Koordinator zu bestimmen. Der erste Server in dieser Liste übernimmt die Rolle des Koordinators. Danach berechnet der Koordinator eine neue Servertabelle. Diese wird zusammen mit den anderen globalen Daten an alle Rechner gesendet. Auf diese Weise erhalten auch neue Rechner eine komplette Kopie aller globalen Daten. SetServertable Version Modus Serverliste Servertabelle Tabellennamen 6.2. DATENOPERATIONEN 69 Version enthält die Versionsnummer der Servertabelle, die bei jeder Neuberechnung erhöht wird. Modus legt fest, ob auf Daten geschrieben werden darf (ReadWrite) oder ob nach dem gleichzeitigen Ausfall mehrerer Rechner (fataler Fehler) nur noch gelesen werden darf (ReadOnly), um die noch verbliebenen unvollst ändigen Daten auszulesen. Serverliste enthält die Liste der Server im Cluster in der Reihenfolge, in der die Server in den Cluster aufgenommen wurden, Servertabelle enth ält die Zuordnung von Primär-, Sekundär- und Ternärservern zu Slices und Tabellennamen enthält eine Tabelle mit den Namen aller im Cluster bekannten Datentabellen. Während der Verteilung der Servertabelle an alle Server kann es zu unterschiedlichen Laufzeiten der Nachrichten kommen, so daß Rechner die neue Servertabelle zu unterschiedlichen Zeitpunkten erhalten. Ohne weitere Gegenmaßnahmen k önnte es daher passieren, daß zwei Rechner gleichzeitig Primärserver sind und somit Inkonsistenzen auftreten. Um dies zu vermeiden, werden in der Übergangsphase nach dem Verteilen einer neuen Servertabelle keine neuen Nachrichten bearbeitet, die Informationen darüber voraussetzen, ob ein Rechner primärer oder sekundärer/ternärer Server ist. Diese Nachrichten werden zurückgestellt, bis die Übergangsphase abgeschlossen ist. Dazu ist es notwendig das Ende der Übergangsphase bestimmen zu können. Dazu sendet jeder Server eine Bestätigung über den Erhalt der neuen Servertabelle an alle anderen Server. Die Übergangsphase ist abgeschlossen, sobald ein Server eine Bestätigung von allen anderen Servern erhalten hat. Als wichtiger Nebeneffekt ist damit auch sichergestellt, daß keine alten Nachrichten, die noch auf der vorangegangenen Servertabelle basieren, mehr zwischen den Servern unterwegs sind, da die Bestätigung über die gleiche Verbindung gesendet wird, über die auch die anderen Nachrichten laufen. Der Beginn einer Übergangsphase wird beim Empfang einer Servertabelle oder beim Empfang einer Bestätigung des Empfangs einer neuen Servertabelle erkannt. Der zweite Fall, bei dem die Bestätigung eines anderen Servers vor der eigentlichen Servertabelle empfangen wird, kann durch unterschiedliche Nachrichtenlaufzeiten auftreten und darf deshalb nicht vernachl ässigt werden. Dazu wird bereits die neue Versionsnummer gespeichert und es werden bereits die zu dieser Nummer passenden Bestätigungen gezählt. Die Bestätigung des Empfangs einer Servertabelle hat folgendes Format: AckServertable Versionsnummer Fehlertoleranz Innerhalb dieses Protokolls ist keine gesonderte Fehlerbehandlung n ötig. Fällt ein Server aus oder geht eine Nachricht verloren, was ebenfalls zur Erkennung eines 70 KAPITEL 6. VERTEILTES DATENMANAGEMENTSYSTEM ausgefallenen Servers führt, so wird dieser Ausfall durch die Kommunikationsschicht automatisch erkannt und das Verfahren mit einer neuen Versionsnummer neu angestoßen. Die Verwendung von Versionsnummern garantiert dabei, daß Best ätigungen zu bestimmten Servertabellen zugeordnet werden können, so daß veraltete Bestätigungen ignoriert werden können. 6.2.3 Absicherung von Änderungen an globalen Daten Zur Verteilung von globalen Daten wird das in Kapitel 5.1 beschriebene Vorgehen umgesetzt: Bevor die Nachricht vom Koordinator zur Änderung von globalen Daten an alle Server versandt wird, erhält der Backup-Koordinator eine Kopie der Nachricht als: BeginGlobal Nachricht Und nach dem erfolgreichen Verteilen der Daten erhält der Backup-Koordinator mit EndGlobal, das keine weiteren Parameter benötigt, die Bestätigung über den Abschluß der Operation. Fällt der Koordinator während der Verteilphase aus, so übernimmt der Backup-Koordinator die Rolle des Koordinators und sendet die Nachricht erneut. Fehlertoleranz Insgesamt gibt es folgende Möglichkeiten: Fällt der Koordinator aus, bevor er BeginGlobal an den Backup-Koordinator senden konnte, so erhalten die anderen Server keine Information über Änderungen an globalen Daten. Der Client erhält eine Fehlermeldung und kann die Operation wiederholen. Fällt der Koordinator nach dem Senden von BeginGlobal aus, so sorgt der Backup-Koordinator für die Verteilung der globalen Daten. Der Client erhält aber trotzdem eine Fehlermeldung, da der Koordinator ausgefallen ist, bevor er eine Antwort an den Client senden konnte. In diesem Fall wiederholt der Client die Anfrage. Da diese Strategie so nur bei idempotenten Operationen funktioniert, sind alle Operationen, die diesen Mechanismus verwenden, entsprechend ausgelegt worden. Durch dieses Vorgehen wird sichergestellt, daß die globalen Daten entweder auf allen Servern geändert werden oder auf keinem Server. 6.2. DATENOPERATIONEN 6.2.4 71 Datentabellen anlegen und löschen HADES kann verschiedene Datentabellen verwalten. Jede Datentabelle verf ügt über einen eigenen Adreßraum, der mit der Seitenadresse 0 beginnt. Die existierenden Datentabellen werden vom Koordinator verwaltet. Dazu existiert eine globale Tabelle, die eine Zuordnung von Datentabellennamen zu Datentabellennummern enth ält. Zum Anlegen oder Öffnen von bestehenden Datentabellen wird folgende Nachricht verwendet: OpenTable Name Neu Name ist hierbei der Name der Datentabelle und Neu legt fest, ob die Datentabelle neu angelegt werden soll, falls sie nicht bereits existiert. Die Nachricht muß an den Koordinator gesendet werden. Wird sie an einen anderen Server gesendet, so antwortet dieser mit einer Fehlermeldung und der Adresse des Koordinators, so daß die Anfrage mit der korrekten Adresse wiederholt werden kann. Falls die Datentabelle nicht existiert, so wird sie vom Koordinator angelegt und der neue Eintrag mit InstallTable Name Index an alle anderen Server verteilt. Index enthält die der Datentabelle zugeordnete Datentabellennummer. Sobald dies abgeschlossen ist oder falls die Datentabelle bereits existiert, antwortet der Koordinator mit der Tabellennummer. Das Löschen von Datentabellen funktioniert prinzipiell identisch. Intern werden gelöschte Tabellen durch einen leeren Namen gekennzeichnet. Daten in den Tabellen werden beim Löschen freigegeben. Die dazugehörenden Nachrichten sind: Client → Koordinator: Koordinator → andere Server: DropTable InstallTable Name Name Index Fehlertoleranz Da es sich bei den Datentabellennamen um globale Daten handelt, wird die Verteilung mit BeginGlobal/EndGlobal geschützt (Abschnitt 6.2.3). 72 6.2.5 KAPITEL 6. VERTEILTES DATENMANAGEMENTSYSTEM Servertabelle auslesen Server erhalten automatisch bei Änderungen eine neue Servertabelle. Dies ist für Clients nicht der Fall, da nicht vorausgesetzt werden kann, daß ein beliebiger Client jederzeit diese Information verarbeiten kann. Stattdessen verwenden Clients solange eine Servertabelle, bis ein Fehler auftritt und lesen erst dann aktiv eine neue Servertabelle ein. Ein Fehler tritt z.B. dann auf, wenn ein Client eine Operation an einen Server sendet, für die dieser nicht primärer Server ist. In diesem Fall antwortet der Server mit dem Fehler WrongServer. Zum Auslesen der Servertabelle sendet der Client an einen beliebigen Server folgende Nachricht: GetServertable Dieser antwortet mit der Adresse des Koordinators und der Servertabelle: OK Koordinator Servertabelle Fehlertoleranz Fällt der Server aus, an den diese Anfrage gestellt wird, so erh ält der Client eine Fehlermeldung von der Kommunikationsschicht und kann diese Anfrage mit einem anderen Server wiederholen. 6.2.6 Adressierung Die Adressierung der Seiten funktioniert gemäß dem in Kapitel 5.2 angegebenen Verfahren. Allerdings kann nicht davon ausgegangen werden, daß der Client zu jedem Zeitpunkt über die aktuelle Servertabelle verfügt. Fallen Server aus oder kommen neue hinzu, so kann es passieren, daß eine veraltete Servertabelle zur Adressierung verwendet wird und ein Adressierungsfehler auftritt, d.h. es wird versucht, einen Server anzusprechen, der nicht mehr existiert, oder es wird ein falscher Server angesprochen. Während der erste Fall bereits von der Kommunikationsschicht erkannt wird, wird zur Erkennung des zweiten Falls die Adressierung auf Serverseite überprüft und wenn ein Adressierungsfehler auftritt, antworten Server mit WrongServer, um dem Client anzuzeigen, daß er eine veraltete Servertabelle zur Adressierung verwendet hat. In beiden Fällen holt sich der Client vom Koordinator die aktuelle Servertabelle und wiederholt die Operation. Dieses geschieht automatisch, so daß sich der Anwender nicht darum kümmern muß. 6.2. DATENOPERATIONEN 73 Dieses Verfahren betrifft die im folgenden beschriebenen Operationen zum Lesen und Schreiben von Daten. 6.2.7 Daten schreiben Client Primärserver Sekundärserver writePage slaveWritePage ack OK Abbildung 6.2: Schreiben von Daten Zum Schreiben von Daten bestimmt der Client aus der Servertabelle den Server, der für das entsprechende Slice primärer Server ist, und sendet an diesen folgende Daten: WritePage Client-Adresse TX-ID Timeout Tabelle Seite Daten Insert-ID Client-Adresse und TX-ID bilden zusammen die Transaktions-ID, die angibt, unter welcher Transaktion der Schreibzugriff ausgeführt werden soll (siehe hierzu auch Kapitel 6.4). Timeout spezifiziert einen Timeout, falls eine Lock-Anforderung beim Zugriff auf Seite Seite in Tabelle Tabelle nicht sofort erfüllt werden kann. Eine negative Seitenadresse wird zum Einfügen verwendet, der Server sucht eine leere Seite, auf die die Daten gespeichert werden können. Das Slice wird genauso wie beim normalen Schreiben per Modulo-Arithmetik aus der Seitenadresse berechnet. Daten enthält die eigentlichen Nutzdaten. Soll eine Seite eingefügt werden, so wird dem Einfügen die ID Insert-ID zugeordnet. Jeder Rechner speichert zu jedem aktiven Client die zuletzt verwendete Insert-ID und fügt nur dann Seiten ein, wenn die aktuelle Insert-ID größer als diese gespeicherte Insert-ID ist. Die Insert-ID wird nur beim Einfügen benötigt, da es sich beim Einfügen um keine idempotente Operation handelt. Nachdem alle Locks erfolgreich angefordert werden konnten, sendet der Prim ärserver die Nachricht als SlaveWritePage an den sekundären und ternären Server, hierzu 74 KAPITEL 6. VERTEILTES DATENMANAGEMENTSYSTEM muß lediglich WritePage in der Nachricht durch SlaveWritePage ersetzt werden, ein Umkopieren der Daten ist nicht erforderlich. Da der sekund äre Server eine exakte Kopie besitzt, ist gesichert, daß dort Lock-Anforderungen ohne Wartezeit erf üllt werden können, nachdem die Lock-Anforderung im Primärserver erfolgreich war. Sobald die Bestätigung des Empfangs von SlaveWritePage vom Sekundärserver vorliegt, wird die Antwort mit der aktuell verwendeten Seitenadresse an den Client gesendet: OK WritePage Seite Sollen mehrere Seiten, die zum gleichen Slice gehören, geschrieben werden, so kann zur Optimierung WritePageSet verwendet werden, das mehrere Seiten gleichzeitig schreiben kann: WritePageSet Client-Adresse TX-ID Timeout Tabelle Seite[ ] Daten[ ] Insert-ID Seite[ ] sowie Daten[ ] enthalten die Adressen sowie die Daten für mehrere Seiten, die geschrieben werden sollen. Die weitere Verarbeitung ist analog zum Schreiben von einer Seite, in der Nachricht zum Sekundär-/Ternärserver wird SlaveWritePageSet statt SlaveWritePage verwendet. Kapitel 6.4 enthält weitere Informationen, wie Transaktionen beim Schreiben und Lesen behandelt werden. Fehlertoleranz Fällt während des Schreibens der Sekundärserver oder der Ternärserver aus, so kann der Primärserver direkt ein OK an den Client senden. Dies ist deshalb m öglich, da gemäß der Fehlerannahme kein zweiter Rechner ausfallen darf, bis ein neuer Sekundärserver aufgebaut ist. Fällt der Primärserver während des Schreibens aus, so erhält der Client automatisch eine Fehlermeldung von der Kommunikationsschicht. In diesem Fall ist ungeklärt, ob die Daten geschrieben wurden oder nicht. Deshalb wiederholt der Client den Schreibzugriff. Handelt es sich um eine Einf ügeoperation, so wird diese vom Server nur dann ausgeführt, falls die Insert-ID noch nicht bekannt ist. Auf diese Weise wird verhindert, daß Werte doppelt eingef ügt werden. Aber auch falls der Client die Operation nicht wiederholt, ist garantiert, daß alle verbliebenen Server eine konsistente Sicht auf diese Daten haben: Die neue Servertabelle, die nach dem Ausfall des Primärservers berechnet wird, enthält nur den Sekundärserver, der nun zum Primärserver aufgestiegen ist, sowie einen (neuen) Ternärserver, der noch keine Daten besitzt und damit auch keine falschen Daten enthalten kann. 6.2. DATENOPERATIONEN 6.2.8 75 Daten Lesen Wie beim Schreiben von Daten bestimmt der Client zum Lesen von Daten mit der Servertabelle den primären Server und sendet an diesen die Leseanfrage: ReadPage Client-Adresse TX-ID Timeout Tabelle Seite Die Bedeutung der Werte ist die gleiche wie beim Schreiben einer Seite. Wurden beim Lesen neue Shared-Locks (SLock) gesetzt, so werden diese per SlaveSetLock Client-Adresse TX-ID SLock Tabelle Seite[] an den Sekundärserver übermittelt. Die gelesenen Daten werden in folgendem Format an den Client gesendet. OK ReadPage Daten Sollen mehrere Seiten, die zum gleichen Slice gehören, gelesen werden, so kann zur Optimierung ReadPageSet verwendet werden, das analog zu WritePageSet funktioniert: ReadPageSet Client-Adresse TX-ID Timeout Tabelle Seite[ ] Fehlertoleranz Die Leseoperation wird sofort ausgeführt, sobald alle Locks angefordert werden konnten, jedoch wird das Ergebnis nur dann sofort gesendet, solange keine Schreibzugriffe auf diese Seite ausgeführt wurden, die noch nicht vom Sekundärserver bestätigt wurden. In diesem Fall wird das Senden der Antwort bis zum Eintreffen der Best ätigung verzögert (Abbildung 6.3). Auf diese Weise wird gewährleistet, daß keine Daten als Resultat eines Lesezugriffs an einen Client gesendet werden, die noch nicht sicher geschrieben sind. Stürzt der Primärserver ab, bevor er eine Kopie der geänderten Daten an den Sekundärserver senden konnte, wird auf diese Weise verhindert, daß vorher ein Lesezugriff diese Daten an einen Client senden konnte. Wurde ein neuer Lock angefordert, so wird die Information darüber mit SlaveSetLock an den Sekundärserver übertragen und die Antwort an den Client bis zur Bestätigung durch den Sekundärserver verzögert. Damit wird gewährleistet, daß beim Absturz des Primärservers kein Lock verloren geht, der bereits verwendet wurde, um Daten zu lesen. Fällt der Primärserver während des Lesens aus, so wird der Lesezugriff vom Client wiederholt. Dies ist problemlos möglich, da Lesen eine idempotente Operation darstellt. 76 KAPITEL 6. VERTEILTES DATENMANAGEMENTSYSTEM Client Client readPage Primärserver Sekundärserver writePage slaveWritePage ack OK OK Abbildung 6.3: Interaktion zwischen Lesen und Schreiben von Daten 6.2.9 Daten Löschen HADES betrachtet Seiten der Größe 0 als nicht existent. Überschreibt man eine Seite mit einer Seite der Größe 0 so hat dies den Effekt, daß die Seite gelöscht wird. Benötigt eine Applikation Seiten der Größe 0, so muß sie diese Seiten beim Schreiben um ein Dummy-Byte erweitern und dieses Byte beim Lesen wieder entfernen. 6.2.10 Selektieren und Sortieren Hauptspeicher stellt im allgemeinen eine größere Bandbreite als das Netzwerk zur Verfügung. Aus diesem Grund ist es von Vorteil, wenn bereits auf Serverseite entschieden werden kann, welche Daten übertragen werden müssen und welche nicht, um Netzwerkbandbreite einzusparen. Deshalb bietet HADES die M öglichkeit, Selektionen auf dem Server auszuführen. Und um die parallele Rechenleistung noch besser zu nutzen, bietet es sich an die Daten bereits auf den Servern vorzusortieren, falls dies notwendig ist. HADES unterstützt dies, indem parametrisierte Selektionskriterien und Sortierkriterien an den Server übermittelt werden können. Da auf Serverseite keine Information über das Layout der Daten vorliegt, wird die Parametrisierung in Byte-Offsets und Längen sowie Datentypen angegeben. Eine Beispiel-Parametrisierung k önnte z.B. so aussehen: Interpretiere Bytes 10 bis 13 als vorzeichenlose Ganzzahl in Big-Endian Darstellung ” und vergleiche ob dieser Wert kleiner oder gleich 1000 ist.“ Es ist dabei möglich mehrere Bedingungen anzugeben, die mit den booleschenOperationen verknüpft werden können. Die Parametrisierung des Sortierkriteriums 6.2. DATENOPERATIONEN 77 erfolgt ähnlich: Es können mehrere Felder definiert werden, die (bei Gleichheit) nacheinander verglichen werden, bis eine Reihenfolge ermittelt werden kann. Das Datenformat sieht wie folgt aus: MatchSort Client-Adresse TX-ID Timeout Tabelle Selektion Sortierung Client-Adresse, TX-ID, Timeout und Tabelle haben die selbe Bedeutung wie oben. Selektion und Sortierung sind das parametrisierte Selektions- bzw. Sortierkriterium. Die Parametrisierung unterstützt derzeit folgende Datentypen: • ganze Zahlen beliebiger Größe (beliebiger Byteanzahl) • sowohl vorzeichenlos als auch vorzeichenbehaftet • sowohl in Little-Endian als auch in Big-Endian Darstellung • Strings fester Länge • nullterminierte Strings (C-Strings) Zur Selektion stehen die Vergleichsoperationen =, 6=, <, ≤, ≥ und > zur Verf ügung. Mehrere Operationen können mit den Booleschen Operationen And, Or und Not verknüpft werden. Zum Sortieren können mehrere Datenfelder in der Parametrisierung definiert werden, so daß bei Gleichheit des ersten Feldes die weiteren Felder zum Sortieren herangezogen werden können. Als Ergebnis dieser Operation wird pro Slice eine Liste von Seitenadressen geliefert, die nur die Seiten enthält, die das Selektionskriterium erfüllen. Die Seitenadressen werden in der Reihenfolge geliefert, die sich aus der Sortierung ergibt. Selektieren und Sortieren ist jeweils auf einen Slice beschr änkt. Wird eine Sortierte Liste aller Daten benötigt, so müssen die vorsortierten Teil-Listen auf Client-Seite zu einer gemeinsamen Liste zusammengemischt werden. Durch die Vorsortierung wird der Aufwand jedoch im Vergleich zu einer vollen Sortierung auf Client-Seite beträchtlich reduziert: Der Aufwand auf Client-Seite beträgt O(N log S), wobei S die (konstante) Anzahl der Slices darstellt. Vernachlässigt man die Konstante log S, so stellt das Mischen einen zur Anzahl der Daten linearen Aufwand dar. Dies wird erreicht, indem von jedem Slice eine Seite in einen Heap eingef ügt wird und dann je nach Sortierung jeweils das kleinste bzw. größte Element aus dem Heap entfernt 78 KAPITEL 6. VERTEILTES DATENMANAGEMENTSYSTEM Client Slice 0 Slice 1 Slice N matchSort selektieren+sortieren selektieren+sortieren selektieren+sortieren Abbildung 6.4: Paralleles Selektieren und Sortieren und durch die nächste Seite aus dem gleichen Slice wie das entfernte Element ersetzt wird. Indem man die Operation für alle Slices parallel startet, kann man die parallele Rechenleistung des Clusters nutzen (Abbildung 6.4). 6.2.11 Erweiterbarkeit Analog zum Selektieren und Sortieren können weitere komplexe Operationen hinzugefügt werden. Besonders gut eignen sich Operationen, für die es kein Problem darstellt, nur auf einem Ausschnitt der Daten zu arbeiten. Beispiele hierf ür sind Suchen und Ersetzen oder auch die Aggregation von Daten, wenn die Teilaggregate auf Client-Seite zu einem Gesamtergebnis zusammengeführt werden können (z.B. sum, count, min, max, . . .). 6.3 Umkopieren für Redundanz oder Lastverteilung Fällt ein Server aus oder kommt ein neuer Server hinzu, so wird begonnen Daten umzukopieren. Das Umkopieren dient dabei zum Wiederherstellen der Redundanz (Ausfall) und/oder zur Lastverteilung (neuer Server). Zum Einsatz kommen die beiden Nachrichtentypen PushPage und PopPage. Nach dem Verteilen und Best ätigen der Servertabelle sendet dazu der neue Ternärserver für jeden Slice PopPage an den jeweiligen Primärserver: PopPage Tabelle Seite 6.4. TRANSAKTIONEN 79 Die erste Nachricht enthält als Tabelle -1 und als Seite den Slice, zu dem die Daten gehören. Die weiteren Nachrichten enthalten jeweils die Tabelle und Seite des letzten vom Primärserver gesendeten PushPage. Auf diese Weise werden vom Ternärserver nacheinander alle Daten angefragt. Tabelle und Seite dienen als Z ähler um den jeweils nächsten Datenblock zu bestimmen. Auf diese Weise wird der Z ähler jeweils zwischen Primärserver und Sekundärserver hin und her gesendet und braucht nicht lokal auf einem Server verwaltet zu werden. Empfängt der Primärserver PopPage so, so berechnet er die nächsten Werte von Tabelle und Seite und sendet die zu dieser Adresse gehörenden Daten: PushPage Tabelle Seite Daten Daten enthält je nach Wert von Seite unterschiedliche Daten. Für Seite ≥ 0 enthält Daten die Daten zu der dadurch adressierten Seite, zum Übertragen von Locks auf Slice-Ebene wird die negative Seitennummer Slice − SliceAnzahl verwendet (vgl. Abschnitt 6.4.2). Die Nachrichten können ohne Acknowledge gesendet werden, da bei einem Ausfall eine neue Servertabelle erstellt wird und das Umkopieren neu gestartet wird. Stellt der Primärserver fest, daß der Ternärserver bereits alle Daten erhalten hat, so sendet er PromoteServer Slice Version an den Koordinator, der diese Nachricht dann geschützt per BeginGlobal/EndGlobal an alle Server verteilt. Diese streichen daraufhin den Sekund ärserver und ersetzen ihn durch den Ternärserver. Empfängt der alte Sekundärserver PromoteServer, so gibt er den von diesem Slice belegten Speicher frei. Das Mitsenden der Versionsnummer der Servertabelle in Version verhindert, daß veraltete Nachrichten ber ücksichtigt werden. 6.4 Transaktionen HADES bietet sowohl die Möglichkeit Transaktionen zu verwenden als auch die Möglichkeit ohne Transaktionen zu arbeiten. 6.4.1 Arbeiten ohne Transaktionen Soll ohne Transaktionen gearbeitet werden, so wird dieser Modus durch die spezielle Transaktions-ID 0 ausgewählt. Operationen werden dann unmittelbar aus- 80 KAPITEL 6. VERTEILTES DATENMANAGEMENTSYSTEM geführt, das Ergebnis dieser Operationen ist sofort für andere Clients sichtbar. Abort/Commit stehen nicht zur Verfügung. 6.4.2 Arbeiten mit Transaktionen Aufgrund der verteilten Natur von HADES sind alle Transaktionen prinzipiell verteilte Transaktionen. Verteilte Transaktionen reichen über mehrere Knoten hinweg. Sie besitzen die gleichen Eigenschaften wie Transaktionen, die sich nur auf einen Knoten beziehen. Insbesondere gilt dies auch dafür, daß Transaktionen als atomare Operationen aufgefaßt werden können, die immer nur komplett oder überhaupt nicht ausgeführt werden. Das eigentliche Problem liegt in der Terminierung der Transaktion: Alle beteiligten Knoten müssen konsistent ein Abort oder alle ein Commit ausführen. Dieses Problem ist als Atomic Commit bekannt, ein Protokoll, das diese Anforderungen erfüllt, wird als Atomic Commitment Protokoll bezeichnet [BHG87, S. 218]. Die Anforderungen, die ein solches Protokoll erfüllen muß, sind folgende: 1. Alle Prozesse, die eine Entscheidung treffen, treffen die gleiche Entscheidung (Abort oder Commit). 2. Ein Prozeß kann eine Entscheidung nicht mehr ändern, sobald er eine Entscheidung getroffen hat. 3. Es darf nur dann ein Commit ausgeführt werden, wenn dem alle Prozesse zustimmen. 4. Treten endlich viele Fehler auf, die repariert“ werden können, so wird nach ” endlicher Zeit eine Entscheidung getroffen. Für verteilte Transaktionen reicht es nicht aus, einfach zwei getrennte Transaktionen zu verwenden und nacheinander abzuschließen, da nicht garantiert werden kann, daß beide konsistent mit Commit oder mit Abort terminieren. St ürzt der Client nach dem ersten Commit ab, so kann der Knoten, auf dem die zweite Transaktion l äuft, nicht bestimmen, ob diese mit Abort oder mit Commit abgeschlossen werden muß. Darüberhinaus kann im allgemeinen nicht davon ausgegangen werden, daß immer ein Commit ausgeführt werden kann. Sollte es also erforderlich werden, daß die zweite Transaktion mit Abort abgebrochen werden muß, so müßte ein Commit der ersten Transaktion zurückgenommen werden, was nicht möglich ist, da die geänderten Daten u.U. bereits von weiteren Transaktionen gelesen und weiterverarbeitet worden sind. Die Anforderungen an das Atomic Commitment Protokoll werden von zwei getrennten Transaktionen, die nacheinander mit Commit abgeschlossen werden, also nicht erfüllt. 6.4. TRANSAKTIONEN 81 Um das Atomic Commit Problem lösen zu können, muß zusätzlicher Aufwand betrieben werden, und es wird zusätzliche Unterstützung durch die beteiligten Knoten benötigt. Das bekannteste Atomic Commitment Protokoll ist das Two-PhaseCommit (2PC) Protokoll [Gra79]. Die grundlegende Funktionsweise ist wie folgt: 1. In der Abstimmungsphase befragt der Koordinator alle Beteiligten, ob sie einem Commit zustimmen oder nicht. Prozesse, die mit Commit“ abstimmen, ” müssen diese Zusage später auch einhalten und können diese Zusage nicht mehr zurückziehen. Dies gilt auch für den Fall, daß ein Prozeß abstürzt und neu gestartet wird. Prozesse, die nicht zustimmen, entscheiden Abort“. ” 2. Falls alle einem Commit zustimmen, entscheidet der Koordinator Commit“ ” und sendet dies an alle Beteiligten. Falls ein Prozeß nicht zustimmt, entscheidet der Koordinator Abort“ und verteilt diese Nachricht an alle Beteiligten. ” Ein Prozeß, der mit Commit“ abgestimmt hat, wartet auf diese Nachricht ” und entscheidet sich nach dem Empfang entsprechend der Vorgabe des Koordinators. Soweit werden alle Anforderungen mit Ausnahme von Anforderung 4 erf üllt. Falls eine Nachricht verloren geht oder ein Fehler auftritt, warten Prozesse u.U. ewig. Um dies zu vermeiden, müssen geeignete Timeouts gewählt werden und geeignete Maßnahmen getroffen werden, falls ein Timeout auftritt: • Tritt in der Abstimmungsphase ein Timeout auf, so können Prozesse, die noch nicht abgestimmt haben oder der Koordinator Abort“ entscheiden. ” • Tritt jedoch ein Timeout auf, während ein Prozeß auf die Antwort des Koordinators wartet, kann er nicht alleine eine Entscheidung treffen, da er keine Information darüber besitzt, ob der Koordinator nicht bereits eine Entscheidung getroffen hat. Solange kein anderer Prozeß bereits die Entscheidung des Koordinators kennt oder bereits Abort“ entschieden hat, kann keine Ent” scheidung getroffen werden und es muß weiter auf den Koordinator gewartet werden. In diesem Fall ist 2PC auch beim Einsatz von Timeouts blockierend, da ohne den Koordinator keine Entscheidung getroffen werden kann. ThreePhase-Commit [Ske81] kann auch mit diesem Fall umgehen, erkauft dies aber mit einem deutlich höheren Kommunikationsaufwand. Aus diesen Anforderungen ergeben sich mehrere Konsequenzen f ür das Recovery: 1. Ein Prozeß muß vor dem Abstimmen mit Commit“ sicherstellen, daß er dieses ” auch garantieren kann, d.h. er muß alle für das Commit benötigten Informationen persistent gespeichert haben (z.B. im Log). KAPITEL 6. VERTEILTES DATENMANAGEMENTSYSTEM Locks 82 1. Phase: Locks anfordern 2. Phase: Locks freigeben Ausführung Abbildung 6.5: Strict Two-Phase-Locking 2. Der Koordinator muß die Entscheidung Abort oder Commit ebenfalls persistent speichern, bevor die anderen Prozesse über diese Entscheidung informiert werden, damit auch bei einem Ausfall des Koordinators die Information über die Entscheidung nicht verloren geht. In HADES sind jedoch zusätzliche Aspekte zu berücksichtigen: 2PC behandelt nicht das Problem, daß noch Nachrichten zwischen den Knoten unterwegs sein k önnen, die dem Client, der committen möchte, nicht bekannt sind, die aber noch zu der Transaktion gehören und die vor einem Commit berücksichtigt werden müssen bzw. die nach einem Abort verworfen werden müssen. Aus diesem Grund kann in HADES kein einfaches 2PC Protokoll verwendet werden, sondern es waren verschiedene Modifikationen notwendig. Diese werden zusammen mit weiteren Optimierungen in den folgenden Abschnitten beschrieben. Locking HADES verwendet pessimistisches strict Two-Phase-Locking [Gra79]. Locks werden in der ersten Phase je nach Bedarf angefordert. Beim Beenden der Transaktion mit Abort oder Commit werden alle Locks gleichzeitig freigegeben (Abbildung 6.5). Daten werden erst gelesen oder geschrieben, nachdem die jeweils entsprechende Sperre erfolgreich angefordert werden konnte. Auf diese Weise wird sichergestellt, 6.4. TRANSAKTIONEN 83 daß keine ungültigen Daten gelesen oder Daten von anderen aktiven Transaktionen überschrieben werden und eine Transaktion aus diesem Grund abgebrochen werden muß, obwohl sie mit einem Commit beendet werden sollte. Zur Deadlock-Erkennung wird ein Timeout eingesetzt, der vom Client spezifiziert werden kann. Kann innerhalb des gegebenen Timeouts kein Lock angefordert werden, wird davon ausgegangen, daß ein Deadlock aufgetreten ist und der Client erh ält eine Fehlermeldung. Daraufhin hat der Client die Wahl, ob er eine weitere Zeit warten möchte, z.B. weil er über zusätzliche Informationen von anderen Clients verfügt, die ihm anzeigt, daß kein Deadlock aufgetreten ist, oder ob er die Transaktion abbrechen möchte. Ein Zugriff auf die Daten ist aber nicht möglich, solange keine Sperre gesetzt werden konnte. Zusätzlich darf der Client auch Commit senden, das dann aber nur die Änderungen durchführt, für die kein Timeout aufgetreten ist. Ein Commit ist an dieser Stelle zwar normalerweise nicht besonders sinnvoll, da dadurch aber die Notwendigkeit entfällt zwischen den Servern eine Abstimmung durchzuführen, ob ein Commit durchgeführt werden kann, wird es aus Geschwindigkeitsgründen unterstützt. Da in diesem Fall nur Operationen committet werden, die keine Sperren brechen, ist dies aber unkritisch und richtet keinen Schaden an. Es bietet keinen Weg, das Locking zu umgehen. Die Wahl des Timeouts ist kritisch: wird er zu kurz gewählt, so werden Transaktionen möglicherweise abgebrochen, obwohl kein Deadlock aufgetreten ist, wird er zu lange gewählt, dauert die Erkennung eines Deadlocks entsprechend lange. Der Timeout sollte daher so eingestellt werden, daß einerseits nur wenige Transaktionen abgebrochen werden, die nicht in einen Deadlock involviert sind, daß andererseits aber Transaktionen, die von einem Deadlock betroffen sind, nicht zu lange warten müssen. Dieses Tuning ist aber machbar, wie der Einsatz in verschiedenen kommerziellen Produkten wie z.B. Tandem beweist [BHG87, S. 56]. Aufgrund dieses pessimistischen Lockings und der Art, wie mit Timeout-Fehlern umgegangen wird, ist immer gewährleistet, daß von jedem Knoten ein Commit ausgeführt werden kann. Daher wird angenommen, daß alle Knoten immer mit Commit abstimmen. Auf diese Weise kann die erste Phase im 2PC entfallen und der Koordinator entscheidet immer Commit“. Dieses Vorgehen ist nicht zu verwech” seln mit presumed commit (PC) [MLO86, AHCL97]. PC verzichtet nicht auf die Abstimmungsphase, in der die Knoten jeweils mit Abort oder Commit über das weitere Schicksal der Transaktion entscheiden, sondern es reduziert im Commit-Fall das forced-Logging und es verzichtet auf das Ack, mit dem die Knoten das vom Koordinator gesendete Commit bestätigen (Abbildungen 6.6 und 6.7). Stattdessen wird auf die erste Phase (prepage, vote abort/commit) verzichtet. Daher ist dieses 84 KAPITEL 6. VERTEILTES DATENMANAGEMENTSYSTEM Teilnehmer−Knoten Koordinator Teilnehmer−Knoten Koordinator prepare prepare vote commit vote abort commit abort ack Abbildung 6.6: PC: Commit-Fall S X IS IX SIX S √ – √ – – X – – – – – IS √ – √ √ √ IX – – √ √ – SIX – – √ – – Abbildung 6.7: PC: Abort-Fall S : Shared Lock X : Exclusive Lock IS : Intention Shared Lock IX : Intention Exclusive Lock SIX : Shared Intention Exclusive Lock Abbildung 6.8: Kompatibilitätsmatrix des Multi-Granularity Locking Vorgehen auch als One-Phase-Commit (1PC) bekannt [SC90, AGP98]. Da alle Server in HADES ohnehin immer bereit für ein Commit sind, können die negativen Auswirkungen, die 1PC ansonsten auf das Logging haben kann, nicht auftreten. Als Locking-Variante kommt Multi-Granularity Locking [Gra79] mit der in Abbildung 6.8 gezeigten Kompatibilitätsmatrix zum Einsatz. Zur Erinnerung möchte ich die Regeln hierfür kurz wiederholen: 1. Eine Transaktion kann auf jede Granularitätsstufe ein Shared- (S) oder Exclusive-Lock (X) setzen, je nachdem, ob nur Lesezugriffe oder auch Schreibzugriffe beabsichtigt werden. 2. Bevor eine Transaktion für eine Granularitätsstufe ein S oder X-Lock setzen kann, muß sie für alle umfassenderen Granularitätsstufen bereits einen IS oder IX-Lock halten. Ein SIX-Lock stellt die Kombination aus einem S und einem IX-Lock dar und wird verwendet, wenn innerhalb eines Tablescans alle Datensätze gelesen aber nur ein Teil 6.4. TRANSAKTIONEN 85 geschrieben werden soll. Die derzeit realisierten Operationen ben ötigen diesen LockTyp nicht, soll HADES aber um weitere komplexe Operationen erweitert werden, so kann dieser Typ genutzt werden. Die Granularitätsstufen, die in HADES verwendet werden, sind: ein komplettes Slice einer Tabelle oder eine einzelne Seite. Soll z.B. ein Lock auf eine Seite gesetzt werden, so muß vorher der zugehörige Intention-Lock auf den Slice einer Tabelle gesetzt werden. Der Vorteil, der sich daraus ergibt, ist daß Selektieren und Sortieren“ ” keine Shared Locks für jede einzelne Seite anzufordern braucht, sondern es ausreicht einen Shared Lock für den kompletten Slice einer Tabelle anzufordern. Auf diese Weise kann der Overhead für das Locking deutlich reduziert werden. Benötigt eine Applikation eine Sperre für eine gesamte Tabelle, so muß sie alle Slices einzeln sperren. Locking auf Tabellenebene ist in HADES nicht als Granularitätsstufe vorgesehen, da jeder Server nur einen Ausschnitt aller Daten (den Slice) besitzt und deshalb ein Locking auf Tabellenebene bedeuten w ürde, daß zum Setzen von Locks zusätzliche Kommunikation mit den anderen Servern benötigt wird, um vorher den dazugehörenden Intention-Lock auf Tabellenebene zu setzen. Durch das Weglassen dieser Granularitätsstufe wird dieser Overhead vermieden, der z.B. bei Schreibzugriffen bis zu 100% ausmachen würde1 . Recovery HADES verwendet Shadow-Paging [Lor77]. Änderungen, die von Transaktionen ausgeführt werden, werden in einen separaten Speicherbereich geschrieben. W ährend Shadow-Paging bei festplattenbasierten Systemen verschiedene Nachteile hat, spielen diese Nachteile hier keine Rolle: • Shadow-Paging betrachtet immer nur ganze Blöcke. In festplattenbasierten Systemen ist die Größe dieser Blöcke durch die Sektorgröße fest vorgegeben und, um eine vernünftige Ressourcenauslastung zu erreichen, müssen in einem Block mehrere Datensätze gespeichert werden. Da pro Block jeweils nur ein Shadow-Block existiert, kann jeweils nur eine Transaktion Daten in einem Block ändern. Damit wird die Parallelität von Transaktionen eingeschränkt: Auch wenn Transaktionen unterschiedliche Datensätze bearbeiten, können sie nicht parallel arbeiten, wenn die Daten zufällig im gleichen Block liegen. 1 Zusätzlich zu den vier Nachrichten vom Client zum Primärserver und vom Primärserver zum Sekundärserver und jeweils zurück, kommen noch mindestens vier weitere Nachrichten, die sequentiell zwischen den beiden anderen ausgeführt werden müssen: Eine Nachricht zu dem dann benötigten Table-Lock Server und von diesem zum Backup Table-Lock Server sowie die jeweils dazugehörenden Antworten. Somit steigen die nicht parallelisierbaren Nachrichten bzw. Antworten von 4 auf 8 an. 86 KAPITEL 6. VERTEILTES DATENMANAGEMENTSYSTEM • Shadow-Paging zerstört die Datenlokalität auf der Festplatte: Daten werden über die Platte zerstreut, so daß die Zugriffszeit ansteigt. • Shadow-Paging muß als Teil des Commit mehrere Sektoren schreiben, w ährend bei Log-basierten Verfahren der Log-Eintrag zumindest bei kleinen Transaktionen mit dem Schreiben von einem Sektor auskommt [SK98, S. 525ff]. Diese Nachteile entfallen in HADES: Aufgrund der variablen Seitengr öße wird pro Seite nur ein Datensatz gespeichert, auf diese Weise behindern sich Transaktionen nicht unnötig gegenseitig. Datenlokalität spielt keine Rolle, die Zugriffszeit ist konstant, egal wo Daten im Hauptspeicher liegen. Es gibt keine Zugriffe auf Festplatten, daher entfällt der Vorteil von Logging (als Teil einer steal/no-force-Strategie), diese Zugriffe zu minimieren. Seite: Lock−Status Shadow−Status Daten−Zeiger Daten Shadow−Zeiger Shadow Abbildung 6.9: Aufbau einer Seite Unter diesen Randbedingungen ist die Implementierung von Shadow-Paging sehr einfach: Seiten enthalten zwei Zeiger, einen auf den regulären Datenbereich und einen auf den Shadow-Bereich, der nur nach Bedarf per malloc() alloziert wird (Abbildung 6.9). Bei einem Commit braucht nur in allen veränderten Seiten jeweils der Zeiger auf den Shadow-Bereich umkopiert zu werden, um die Änderungen zu aktivieren, ein umkopieren der Daten ist nicht erforderlich. Im Unterschied zur festplattenbasierten Variante werden zum Umschalten keine atomaren Operationen ben ötigt, da bei einem Ausfall des Rechners diese Zeiger ohnehin verloren gehen und keine Rolle mehr spielen. Transaktionsbeginn Transaktionen werden in HADES implizit begonnen, indem ein Client eine Transaktions-ID neu verwendet, die noch zu keiner anderen laufenden Transaktion geh ört. 6.4. TRANSAKTIONEN 87 Die Transaktions-ID enthält nicht nur eine fortlaufende Nummer, sondern zusätzlich die Client-Adresse sowie einen Zeitstempel, der die Startzeit des Clients tr ägt. Da nicht zur gleichen Zeit unter der gleichen IP-Adresse zwei verschiedene Clients gestartet werden können, sondern sich die Adresse zumindest in der Port-Nummer unterscheiden muß, werden von unterschiedlichen Clients immer unterschiedliche Transaktions-IDs generiert. Auf diese Weise können Transaktions-IDs lokal erzeugt werden, ohne daß eine Kommunikation mit einem Server erforderlich w äre, um kollisionsfreie Transaktions-IDs zu erhalten. Wird der Ausfall eines Clients erkannt, so werden alle Transaktionen des Clients automatisch abgebrochen. Abort/Commit Bevor Commit ausgeführt werden darf, müssen alle Operationen, die innerhalb der Transaktion ausgeführt werden sollen, abgeschlossen sein, d.h. der Client muß den Rückgabewert der Operation ausgelesen haben. Wird dies mißachtet, so k önnen diese Operationen nach dem Commit ausgeführt werden. Da zu diesem Zeitpunkt die Transaktions-ID zu keiner Transaktion mehr zugeordnet ist, wird eine neue, von der ersten unabhängige Transaktion begonnen. Soll eine Transaktion mit Abort abgebrochen werden und wurden die R ückgabewerte der Operationen noch nicht ausgelesen, so kann nicht in allen F ällen davon ausgegangen werden, daß alle Operationen bereits abgeschlossen sind. Diese werden abgebrochen, indem AbortTXOp TX-ID Client-Adresse an alle Server gesendet wird. Nachdem die Server den Erhalt dieser Nachricht bestätigt haben, kann das Abort fortgeführt werden. AbortTXOp erfüllt hierbei zwei Aufgaben: 1. Es bricht alle noch ausstehenden Operationen ab. 2. Es stellt sicher, daß keine Nachrichten zu dieser Transaktion mehr unterwegs sind, da sie über die gleiche Verbindung zwischen Client und Server gesendet wird. Falls keine Operationen mehr ausstehen, dies ist dann der Fall, wenn bereits die Rückgabewerte aller Operationen ausgelesen wurden, kann auf diesen Schritt verzichtet werden. 88 KAPITEL 6. VERTEILTES DATENMANAGEMENTSYSTEM Beim Beenden einer Transaktion mit Abort oder Commit muß sichergestellt sein, daß keine Nachrichten zwischen Primär- und Sekundär- bzw. Ternärserver wie z.B. SlaveWrite unterwegs sind, die noch zu dieser Transaktion geh ören2 . Bevor also das eigentliche Abort/Commit durchgeführt wird, müssen diese Nachrichten bearbeitet worden sein. Dieses wird dadurch erreicht, daß Sekundär- bzw. Ternärserver das Abort/Commit vom Primärserver erhalten. Dabei wird die Eigenschaft ausgenutzt, daß sich Nachrichten auf einer Verbindung nicht überholen können. Aus dieser Anforderung resultiert das folgende Protokoll. Zum Beenden einer Transaktion mit Abort oder Commit sendet der Client folgende Nachricht an den Koordinator: EndTransaction Abort/Commit TX-ID Client-Adresse Der Koordinator sendet daraufhin folgende Nachricht zunächst an den BackupKoordinator: BeginEndTransaction Version Abort/Commit Client-Adresse TX-ID Version ist die Version der Servertabelle. Diese Version wird verwendet, um nach dem Neustart des Protokolls nach einem Ausfall eines Servers, alle Nachrichten korrekt zuordnen zu können. Sobald das Ack vom Backup-Koordinator vorliegt, sendet der Koordinator ein OK an den Client, da ab diesem Zeitpunkt bereits gew ährleistet ist, daß das Abort bzw. Commit durchgeführt wird. Danach sendet der Koordinator BeginEndTransaction auch an alle anderen Server. Jeder Rechner, der ein BeginEndTransaction empfängt, trägt dies in eine Liste der zu beendenden Transaktionen ein. Falls bereits ein Eintrag mit der gleichen Versionsnummer existiert, wird die Nachricht ignoriert. Daraufhin sendet jeder Server für jedes Slice, für das er Primärserver ist eine Nachricht an den Sekundär- bzw. Ternärserver: SlaveEndTransaction Version Abort/Commit Client-Adresse TX-ID Slices Slices enthält die betroffenen Slices. Danach führt jeder Server das Abort/Commit für die Slices durch, für die er Primärserver ist. 2 Es gibt zwei Fälle, die zu diese Situation führen können: Es handelt sich um einen Nachricht zwischen Primär- und Ternärserver, da Nachrichten an den Ternärserver asynchron gesendet werden und daher auch noch unterwegs sein können, wenn der Client bereits ein Ergebnis erhalten hat. Oder ein Client sendet unerlaubterweise ein Commit, bevor alle Einzeloperationen abgeschlossen sind. 6.4. TRANSAKTIONEN 89 Jeder Server, der SlaveEndTransaction empfängt führt Abort/Commit durch. Vorher senden Server für diese Slices AckEndTransaction Version Abort/Commit Client-Adresse TX-ID Slices an den Koordinator. Hat dieser für alle Slices dieses Ack erhalten, so wird die Transaktion wieder aus der Liste der zu beendenden Transaktionen gel öscht. An alle anderen Server wird EndEndTransaction Abort/Commit Client-Adresse TX-ID gesendet, die daraufhin ihrerseits die Transaktion löschen. Hiermit ist das Abort/ Commit abgeschlossen. Die mitgesendete Versionsnummer stellt sicher, daß bei einer Änderung in der Servertabelle und bei einem möglichen Wechsel des Koordinators nur aktuelle Nachrichten berücksichtigt werden und keine veralteten AckEndTransaction Nachrichten mitgezählt werden, was zu einem vorzeitigen Abbruch eines Abort/Commit f ühren könnte. Kommt es im Verlauf vor, daß ein Rechner eine Nachricht an sich selbst senden müßte, so wird die Nachricht nicht gesendet, sondern die davon ausgel öste Aktion direkt ausgeführt. Zur besseren Veranschaulichung möchte ich den Ablauf in einem Beispiel verdeutlichen. Es soll angenommen werden, daß folgende Servertabelle verwendet wird: Slice 0 1 2 3 4 Primärserver A A B B C Sekundärserver C C A A B Ternärserver D D Server A sei der Koordinator, Server B der Backup-Koordinator. Abbildung 6.10 gibt einen Überblick über die gesendeten Nachrichten. Abbildung 6.11 zeigt den Ausschnitt der Nachrichten daraus, der Slice 0 betrifft. 1. Ein Client sendet EndTransaction an A. 2. A sendet BeginEndTransaction an den Backup-Koordinator. 90 KAPITEL 6. VERTEILTES DATENMANAGEMENTSYSTEM Client Server A EndTransaction Server B Server C Server D BeginEndTransaction ACK OK BeginEndTransaction SlaveEndTransaction AckEndTransaction EndEndTransaction Abbildung 6.10: Abort/Commit Client Koordinator Primärserver Sekundärserver EndTransaction BeginEndTransaction ACK OK SlaveEndTransaction AckEndTransaction EndEndTransaction Abbildung 6.11: Nachrichten für Slice 0 6.4. TRANSAKTIONEN 91 3. Sobald A ein Ack für BeginEndTransaction von B erhält, sendet es OK an den Client und BeginEndTransaction an C und D. 4. Da A Primärserver für Slices 0 und 1 ist, sendet es SlaveEndTransaction mit den Seiten 0 und 1 an C und SlaveEndTransaction mit den Slices 1 an D. 5. Sobald B BeginEndTransaction von A empfängt, sendet B SlaveEndTransaction mit den Slices 2 und 3 an A und mit Slice 2 an D. 6. C sendet SlaveEndTransaction mit dem Slice 4 an B. 7. D sendet nach dem Empfang von BeginEndTransaction keine Nachricht, da D kein Primärserver für ein Slice ist. 8. Nach dem Empfang von SlaveEndTransaction sendet B AckEndTransaction für Slice 0, C für Slices 0 und 1 und D für Slices 1 und 2 an A. 9. Nachdem A alle AckEndTransaction empfangen hat, wird der Vorgang abgeschlossen, und B, C und D werden mit EndEndTransaction darüber informiert. Auf den ersten Blick werden viele Nachrichten gesendet. Dabei ist aber zu beachten, daß bereits in Schritt 3, dem Client die korrekte Durchf ührung des Commit bzw. Abort garantiert werden kann. Bis zu diesem Zeitpunkt werden insgesamt 4 Nachrichten benötigt. Diese Zahl ist unabhängig von der Zahl der Server im Cluster. Alle weiteren Nachrichten haben keine Auswirkung mehr auf die Latenz dieses Commits/Aborts, so daß trotz der Komplexität eine sehr niedrige Latenz erreicht wird (vgl. Abschnitt 7.3.4). Zusätzlich muß man auch die Auswirkungen auf andere Transaktionen betrachten: Andere Transaktionen können blockiert sein und auf die Freigabe von Sperren warten. Sie können weiterarbeiten, sobald auf dem Primärserver die Sperren aufgehoben werden. Die Kette von Nachrichten, die zum Freigeben der Sperren f ührt, ist ebenfalls 4 Nachrichten lang. Zwar werden weitere Nachrichten in der Zeit gesendet, jedoch können diese parallel zu den anderen Nachrichten gesendet werden, so daß die Auswirkungen davon gering sind. Fehlertoleranz Es muß sichergestellt werden, daß ein Abort oder Commit entweder von allen oder von keinem Server durchgeführt wird. Solange der Koordinator nicht ausfällt, wird dies dadurch gewährleistet, daß für alle Transaktionen, die in der Liste der zu beendenden Transaktionen stehen, bei einem Ausfall eines Servers BeginEndCommit 92 KAPITEL 6. VERTEILTES DATENMANAGEMENTSYSTEM erneut mit der neuen Versionsnummer der Servertabelle an alle Server gesendet wird und somit das Verfahren erneut durchgeführt wird. Eine doppelte Ausführung ist dabei unkritisch, da beim zweiten mal keine Operationen mehr zu einer Transaktion gespeichert sind, so daß hierdurch keine Daten verfälscht werden können. Fällt der Koordinator aus, so muß für alle Transaktionen, die sich gerade in der Abort- oder Commit-Phase befinden, das Abort oder Commit neu gestartet werden. Dies wird vom Backup-Koordinator übernommen, der beim Ausfall zum Koordinator aufsteigt. Der Backup-Koordinator besitzt eine aktuelle Kopie der zu beendenden Transaktionen, da er vom Koordinator immer als erster Server BeginEndTransaction erhält. Auf diese Weise wird garantiert, daß sobald der Koordinator BeginEndTransaction an den Backup-Koordinator gesandt hat, das Abort bzw. Commit auch bei einem Ausfall des Koordinators vollständig durchgeführt wird. Bei einem Ausfall des Koordinators erhält der Client eine Fehlermeldung, aber er kann nicht feststellen, ob der Ausfall des Koordinators vor der Benachrichtigung des Backup-Koordinators oder danach stattgefunden hat, ob also das Abort bzw. Commit vollständig durchgeführt wurde oder überhaupt nicht. Aus diesem Grund wiederholt der Client in diesem Fall das Abort/Commit. Aber auch wenn nun zusätzlich der Client ausfällt, bevor er das Abort/Commit wiederholen konnte, stellt dies kein Problem für die Datenkonsistenz dar: • Das Abort/Commit wurde nicht durchgeführt: Dies ist äquivalent zu einem Ausfall des Clients unmittelbar vor dem Absenden des Abort/Commit. Der Ausfall des Clients wird erkannt und die Transaktion abgebrochen. • Das Abort/Commit wurde bereits durchgeführt: Dies ist äquivalent zu einem Ausfall nach dem Senden des Abort/Commit. 6.4.3 Inter-Cluster-Transaktionen HADES unterstützt Transaktionen, die über mehrere Cluster hinwegreichen. Normalerweise werden Transaktionen, die über mehrere Datenbanken hinwegreichen, als verteilte Transaktionen bezeichnet, da aber bereits einfache Transaktionen in HADES verteilte Transaktionen sind, möchte ich diese zur Unterscheidung InterCluster-Transaktionen nennen, während ich Transaktionen, die sich nur auf einen Cluster beziehen, Intra-Cluster-Transaktionen nennen m öchte. Für Inter-Cluster-Transaktionen verwendet HADES eine ähnliche Strategie wie für Intra-Cluster-Transaktionen. Damit keine Wartezeiten durch den Ausfall eines Koordinators entstehen, wird ein kompletter fehlertoleranter Cluster als Koordinator 6.4. TRANSAKTIONEN 93 Inter−Cluster− Transaktionskoordinator Cluster TX Cluster Cluster TX Cluster TX TX Abbildung 6.12: Anbinden von Transaktionen an andere verwendet. Auf diese Weise kann ein Commit nicht durch einen einzelnen ausgefallenen Rechner blockiert werden und auf Three-Phase-Commit kann verzichtet werden. Ebenso wie bei den Intra-Cluster-Transaktionen ist immer gew ährleistet, daß alle teilnehmenden Cluster ein Commit durchführen können. Alle Rechner stimmen deshalb immer mit Commit“ ab. Zur Optimierung wird deshalb auf die erste Phase ” im 2PC verzichtet, also ebenfalls wie im Intra-Cluster-Fall ein 1PC durchgef ührt. HADES realisiert Inter-Cluster-Transaktionen, indem Transaktionen an andere Transaktionen, die auf anderen Clustern laufen, so angebunden werden k önnen, daß immer gewährleistet ist, daß entweder alle mit Abort oder alle mit Commit terminieren (Abbildung 6.12). Der Cluster, auf dem die Transaktion l äuft, an die andere angebunden werden, wird damit zum Inter-Cluster-Transaktionskoordinator. Wird eine Transaktion an eine andere gebunden, so geht die Kontrolle über Commit/Abort an den Inter-Cluster-Transaktionskoordinator über und der Client kann kein Commit/Abort mehr für diese Transaktion ausführen. Zum Anbinden einer Transaktion an eine andere werden folgende Schritte ausgeführt: 1. Der Client wählt eine normale Transaktions-ID für Cluster 1 (ID1 ). 2. Der Client wählt eine (externe) Transaktions ID für Cluster 2 (ID2 ) und markiert diese so, daß die Transaktion bei einem Absturz des Clients nicht mit Abort beendet wird. 3. Der Client sendet ChainTransaction an Cluster 1 mit der lokalen ID ID1 und der ID ID2 des zweiten Clusters. 4. Der Client wiederholt die letzten beiden Schritte für weitere Cluster, die an dieser verteilten Transaktion beteiligt sein sollen. 94 KAPITEL 6. VERTEILTES DATENMANAGEMENTSYSTEM 5. Der Client führt Operationen auf den beteiligten Clustern durch, wobei er jeweils die zu dem Cluster gehörende Transaktions-ID verwendet. 6. Der Client sendet Abort oder Commit an Cluster 1. 7. Cluster 1 führt als Koordinator das Commit durch. In Schritt 1 wird eine normale Transaktions-ID verwendet. Dies garantiert, daß bei einem Ausfall des Clients für diese Transaktion und alle daran angebundenen Transaktionen ein Abort durchgeführt wird. Da externe Transaktionen zuerst an diese Transaktion gebunden werden, bevor damit Operationen durchgef ührt werden, ist sichergestellt, daß beim Ausfall des Clients keine Überreste übrig bleiben. Die folgenden Abschnitte beschreiben die zum Einsatz kommenden Nachrichtenformate. Externe Transaktions-IDs Als externe Transaktions-IDs möchte ich Transaktions-IDs bezeichnen, die zum Verbinden von Transaktionen verwendet werden. Sie besitzen zwei Eigenschaften: Fällt ein Client aus, so werden sie nicht automatisch abgebrochen. Dieses ist wichtig, da diese Funktion vom Transaktionskoordinator wahrgenommen wird und bei einem automatischen Abbruch nicht gewährleistet werden könnte, daß der Koordinator nicht bereits Commit“ entschieden hat. ” Externe Transaktions-IDs enthalten die Adressen von zwei Servern des jeweiligen Clusters. Diese Adressen werden verwendet, um eine Verbindung zwischen den beiden beteiligten Clustern beim Verbinden von zwei Transaktionen herzustellen. Dabei ist jedoch zu beachten, daß diese Adressen u.U. nur eine beschr änkte Lebensdauer besitzen: Bei einem Ausfall von Servern im Cluster, werden diese Adressen ung ültig. Für den vorgesehenen Einsatzzweck stellt dies jedoch kein Problem dar. Die Adressen werden nur für ChainTransaction benötigt, das direkt nach dem Generieren der ID aufgerufen werden sollte, und es reicht aus, wenn nur eine der beiden Adressen gültig ist. Somit stellt ein Ausfall eines einzelnen Servers kein Problem dar. Der gleichzeitige Ausfall mehrere Server liegt außerhalb der Fehlerannahme und f ührt in der Regel zu Datenverlusten, so daß es in diesem Fall keine Rolle mehr spielt, ob ChainTransaction dann noch erfolgreich ausgeführt werden kann oder nicht. Sobald die Verbindung zwischen den Transaktionen hergestellt ist, verbindet sich der Cluster, der die Rolle als Koordinator übernimmt, mit dem anderen Cluster 6.4. TRANSAKTIONEN 95 und wird so über Rechnerausfälle informiert, so daß ab diesem Zeitpunkt die Adressen der externen Transaktions-ID keine Rolle mehr spielen. Zum Durchf ühren von Operationen werden externe Transaktions-IDs in normale Transaktions-IDs konvertiert, die diese Adressen nicht enthalten. Verbinden von Transaktionen Zum Verbinden von zwei Transaktionen sendet der Client folgende Nachricht an den Koordinator der Datenbank, die die Rolle als Transaktions-Koordinator übernehmen soll: ChainTransaction TX-ID externe TX-ID IP-Adresse TX-ID ist eine ID, die zu der Datenbank gehört, die als Koordinator fungieren soll, und externe TX-ID die ID, die zu der zweiten Datenbank gehört. Zusammen mit IP-Adresse ergeben sie die Transaktions-IDs für das jeweilige System. Der Datenbank-Koordinator ersetzt ChainTransaction durch SlaveChainTransaction und sendet die Nachricht an den Backup-Koordinator. Beide Server registrieren sich danach als Client bei der zweiten Datenbank, um auf diese Weise über Serveränderungen informiert zu werden. Über diese Registrierung als Client wird später auch ein Abort oder Commit gesendet, um die verbundene Transaktion zu terminieren. Fehlertoleranz Fällt ein Server oder der Koordinator aus, so wird die Tabelle mit den externen Transaktionen vom (evtl. neuen) Koordinator neu an den Backup-Koordinator übertragen. Hierzu werden zu jedem externen Datenbank-Cluster zwei Server als Stellvertreter für den Cluster ausgewählt und deren Adressen übermittelt. Falls der Backup-Koordinator noch keine Verbindung zu diesem Cluster besitzt, registriert er sich dort als Client. Auf diese Weise bleibt die Verbindung auch bei einem Ausfall des Koordinators zu einer anderen Datenbank erhalten. Da immer aktuelle Adressen verwendet werden, ist dies auch möglich, wenn sich Adressen im zweiten Cluster seit dem ersten Kontakt geändert haben. 96 KAPITEL 6. VERTEILTES DATENMANAGEMENTSYSTEM 6.5 6.5.1 Audit-Logging und Backup Audit-Logging HADES verwendet Shadow-Paging, um Transaktionen zu implementieren, und benötigt daher hierfür kein Log. Rechnerausfälle werden durch redundante Speicherung aufgefangen, so daß auch hierfür kein Log benötigt wird. Jedoch kann es aus anderen Gründen, z.B. zur Dokumentation von Änderungen an Daten, notwendig sein, ein Log zu führen. Da dieses Log nicht für Transaktionen benötigt wird, sind die Anforderungen geringer als an ein Transaktions-Log. Insbesondere braucht das Log nicht als Teil eines Commit auf Platte herausgeschrieben werden, sondern kann als komplett asynchrones Log im Hintergrund geschrieben werden, so daß es die Latenzzeit nicht beeinflußt. Server schreiben das Log jeweils in eine lokale Datei. Damit ist das Log ebenso verteilt wie die Server. Es werden folgende Daten ins Log geschrieben, sobald Änderungen aufgetreten sind: • Tabellennamen und Tabellen-IDs • Die Servertabelle – daraus kann ermittelt werden, welche Funktion ein Server zu einem Zeitpunkt hatte, und es kann somit auch ermittelt werden, in welchem Log bestimmte Änderungen stehen. Die Servertabelle wird zu dem Zeitpunkt ins Log eingefügt, zu dem die Servertabelle aktiv wird, also nachdem die Bestätigungen aller anderen Server zu dieser Servertabelle vorliegen. • Änderungen an Daten (inkl. Transaktion) – dies sind Daten, die committet wurden oder ohne Transaktionen geschrieben wurden. Transaktionen, die mit Abort abgebrochen wurden, brauchen nicht berücksichtigt zu werden. Jeder Server loggt alle committeten Änderungen, die ihn betreffen. Somit werden Änderungen sowohl vom Primärserver als auch vom Sekundärserver ins Log geschrieben. Auf diese Weise werden alle Änderungen in zwei Logs geschrieben und auch bei einem Ausfall eines Logs können die Änderungen noch nachvollzogen werden. Es werden keine Änderungen, die noch nicht committet wurden, ins Log geschrieben. Auf diese Weise repräsentiert jeder Log-Eintrag den aktuellen, zu diesem Zeitpunkt gültigen Zustand der Daten. Log-Einträge werden chronologisch ins Log geschrieben und jedes Log enthält Einträge zum gleichen Slice jeweils in gleicher Reihenfolge. Auf diese Weise k önnen die Log-Einträge der verschiedenen Logs zueinander 1:1 zugeordnet werden. Enthalten 6.5. AUDIT-LOGGING UND BACKUP 97 z.B. zwei Logs Einträge zu Slice 0, so ist der erste Eintrag zu Slice 0 im ersten Log der gleiche Eintrag wie der erste Eintrag zu Slice 0 im zweiten Log. Die Zuordnung der Log-Einträge von Primär und Sekundärserver kann damit aufgrund der Position im Log erfolgen. Der mitgeloggte Zeitstempel wird f ür diesen Zweck nicht benötigt, er dient ausschließlich zu Informationszwecken, wenn Änderungen nachvollzogen werden sollen. Die Zeitstempel verwenden jeweils die lokale Rechnerzeit, die sich von Rechner zu Rechner unterscheiden kann. Dadurch, daß sich Log-Einträge aufgrund ihrer Position im Log zueinander zuordnen lassen, ist es aber möglich, die Uhren der beteiligten Rechner nachträglich abzugleichen: Informationen über das Commit erreichen den Sekundärserver vom Primärserver, so daß der Sekundärserver den Log-Eintrag mit leichter Verzögerung schreibt. Die Verzögerung wird dabei im wesentlichen von der Nachrichtenlaufzeit dominiert. Existiert ein Ring von Servern, wobei eine gerichtete Kante im Ring jeweils einem Slice entspricht, für das der erste Knoten Primär- und der zweite Knoten Sekundärserver ist, so läßt sich die mittlere Nachrichtenlaufzeit bestimmen: Nimmt man an, daß die Nachrichtenlaufzeit für alle Nachrichten gleich ist, so entspricht der Mittelwert der Differenzen der Nachrichtenlaufzeit, da sich unterschiedliche Uhrzeiten auf den verschiedenen Systemen wegmitteln: Angenommen der Ring habe n Kanten und ∆i sei die Differenz der Zeitstempel. Die Differenz der Zeitstempel setzt sich zusammen aus der Nachrichtenlaufzeit l und dem Gangunterschied di = t(i+1 mod n) − ti der Uhren. Dann erhält man als Abschätzung für l: ∆1 + ∆2 + . . . + ∆ n = d1 + l + d 2 + l + . . . + d n + l = d1 + d2 + . . . + d n + n × l = = ⇐⇒ 1 × (∆1 + ∆2 + . . . + ∆n ) n = t2 − t1 + t3 − t2 + . . . + t n − t1 + n × l n×l l Die so erhaltene Nachrichtenlaufzeit l kann man verwenden, um die Uhren nachträglich zu korrigieren. Um die Größe der Log-Dateien zu begrenzen, ist es möglich, die bestehenden LogDateien abzuschließen und neue anzulegen, die dann alle weiteren Änderungen aufnehmen. Der Wechsel auf eine neue Log-Datei wird immer an den Grenzen von Log-Records vorgenommen. 98 KAPITEL 6. VERTEILTES DATENMANAGEMENTSYSTEM Alle Einträge im Log haben folgendes Format: Type Timestamp Size Daten Type beschreibt den Typ des Log-Records, Timestamp gibt den Zeitpunkt an, zu dem Daten geändert bzw. im Falle des Backups ausgelesen wurden, und Size ist die Größe der nachfolgenden Daten im Log-Record in Bytes. Folgende Log-Records stehen für das Logging zur Verfügung: ServerTable: ServerTable Timestamp Size Version Servertabelle Enthält die Servertabelle, die die Zuordnung von Slices und Servern beschreibt. Servertabellen werden zu dem Zeitpunkt ins Log geschrieben, zu dem sie aktiv werden, also wenn bereits AckServertable von allen anderen Servern empfangen wurde, was garantiert, daß keine Nachrichten mehr unterwegs sind, die noch zur vorangegangenen Servertabelle gehören. DataTables: DataTables Timestamp Size n n× Name Index Beschreibt die im System vorhandenen Datentabellen. Create Create: Timestamp Size Name Index Create wird beim Anlegen und Drop Drop: Timestamp Size Name Drop beim Löschen von Datentabellen geschrieben. Update: Update Timestamp Size TX-ID Client-Adresse Tabelle Seite Daten Beschreibt Änderungen, die an Daten vorgenommen wurden. Änderungen werden erst nach einem Commit ins Log geschrieben. Die geänderten Daten werden durch den Index Tabelle der Datentabelle sowie durch die Seitennummer Seite identifiziert. Da ServerTable und DataTables als Verwaltungsdaten, die die Zuordnung der Daten festlegen, besonders wichtig sind, werden sie am Anfang jeder Log-Datei eingef ügt. Wichtig anzumerken ist, daß bei einem Ausfall eines Servers zwar die Daten neu repliziert werden, um die Redundanz wiederherzustellen, daß dieses jedoch nicht für Log-Dateien gilt. Da diese Dateien auf Festplatten gespeichert werden k önnen, 6.5. AUDIT-LOGGING UND BACKUP 99 gehen sie aber auch nicht so leicht verloren. Insgesamt muß aber mit einer geeigneten Backup-Strategie für eine ausreichende Sicherung und Archivierung dieser Log-Dateien gesorgt werden. Gehen die Log-Dateien verloren, so bedeutet dies keinen Verlust der Daten in der Datenbank. Es ist nur nicht mehr möglich nachzuvollziehen, welche Änderungen zu dem aktuellen Datenbestand geführt haben. Darin unterscheidet sich das Audit-Log von einem Transaktions-Log. Geht dieses bei einem Ausfall verloren, so gehen alle Änderungen verloren, die noch nicht in der Datenbank gespeichert sind. 6.5.2 Backup Ein gleichzeitiger Ausfall mehrerer Komponenten kann, wie bei anderen Datenbanken auch, zum Datenverlust führen. In HADES kann das Audit-Log als Backup verwendet werden, um die Daten zu rekonstruieren. Das Audit-Log enth ält alle Änderungen, die seit dem Start vorgenommen wurden, also alle Änderungen gegenüber einer leeren Datenbank. Ohne weitere Informationen würden alle jemals geschriebenen Log-Dateien benötigt, um den Datenbank-Inhalt rekonstruieren zu können. Dies ist nicht praktikabel, da das Einlesen aller Log-Dateien viel Zeit und das Speichern aller Log-Dateien viel Platz benötigt. Aus diesem Grund ermöglicht HADES, eine Kopie der Datenbank ins Log zu schreiben. Dies entspricht einem Checkpoint einer normalen Datenbank: Sobald der Checkpoint erfolgreich geschrieben wurde (bzw. zwei Checkpoints bei fuzzy-Checkpointing), wird nur noch der Teils des Logs zum Wiederherstellen der Daten benötigt, der auf den Checkpoint folgt. Im Unterschied zu einem Checkpoint werden die Daten aber nicht in die Datenbank auf Platte zurückgeschrieben, die gibt es in HADES nämlich nicht, sondern die komplette Datenbank wird ins Log kopiert. Die Strategie, die für das Backup eingesetzt wird, ähnelt dem Copy-on-Update Algorithmus (COU) aus [SGM89]. Aufgrund des eingesetzten Shadow-Paging liegen geänderte, noch nicht committete Daten, ohnehin in einem separaten Speicherbereich, so daß es sehr einfach ist, nur die bereits committete Daten ins Log zu schreiben. Anders als COU verwendet HADES jedoch einen Action-konsistenten Ansatz, so daß nicht auf das Ende von Transaktionen gewartet werden muß. Das Backup beginnt mit: BackupBegin Timestamp Size n n× Name Index 100 KAPITEL 6. VERTEILTES DATENMANAGEMENTSYSTEM Als Teil von BackupBegin wird die aktuelle Liste aller Datentabellen abgelegt, dazu wird die Anzahl n der Datentabellen sowie jeweils der Name und der Index der Tabelle geschrieben. Danach wird die aktuelle Servertabelle in einem ServerTable-Log-Eintrag abgelegt und anschließend werden alle Seiten ins Log geschrieben: BackupPage Timestamp Size Tabelle Seite Daten Es werden nur Daten geschrieben, die bereits mit Commit best ätigt wurden. Die Bearbeitung von Transaktionen wird nicht angehalten. Das normale Logging wird nicht angehalten und Änderungen werden weiterhin ins Log geschrieben. Das Backup läuft mit niedrigerer Priorität, so daß andere Log-Einträge bevorzugt geschrieben werden. Es wird jeweils der aktuelle Seiteninhalt geschrieben. Sind alle Seiten herausgeschrieben, so wird das Backup mit BackupEnd Timestamp 0 im Log abgeschlossen. Da nur Daten geschrieben werden, die bereits committet sind, repräsentiert das Log zwischen BackupBegin und BackupEnd zusammen mit den eingestreuten Update-Einträgen, die die Änderungen enthalten, die während des Backups committet wurden, den aktuellen Inhalt der Datenbank zum Zeitpunkt, an dem BackupEnd geschrieben wurde. Dies möchte ich an einem Beispiel verdeutlichen: Angenommen in der Datenbank seien 5 Seiten in der Tabelle 0 mit folgendem Inhalt vorhanden: Seite 1 2 3 4 5 Inhalt A B C D E ⇒ Seite 1 2 3 4 5 Inhalt A B G H E Während das Backup Seite 3 schreibt, ändert eine Transaktion Seiten 3 und 4 auf G bzw. H. Damit sind folgende Einträge im Audit-Log (zur Verbesserung der Übersichtlichkeit sind nur die wichtigsten Felder aufgeführt): BackupBegin BackupPage 1 A BackupPage 2 B BackupPage 3 C 6.6. ZUSAMMENFASSUNG 101 Update 3 G Update 4 H BackupPage 4 H BackupPage 5 E BackupEnd Interessant hierbei ist, daß der BackupPage-Record zu Seite 4 bereits die ge änderten Daten enthält. Zum Rekonstruieren der Datenbank können deshalb alle Log-Records zwischen BackupBegin und BackupEnd direkt in der Reihenfolge ausgewertet werden, in der sie im Log vorhanden sind. Mit dieser Erweiterung benötigt man zum Rekonstruieren der Datenbank nur noch die Log-Datei, die das Backup enthält, sowie die darauf folgenden Log-Dateien. LogDateien, die vor dem Backup geschrieben wurden, werden für die Wiederherstellung nicht mehr benötigt. Da Update-Einträge unabhängig von Backups geschrieben werden, kann aber auch mit einem beliebigen anderen Backup gestartet werden und alle darauf folgenden Updates eingespielt werden. Dieses wird verwendet, wenn das System ausfällt, bevor ein Backup vollständig geschrieben wurde. In diesem Fall wird einfach bis zum letzten vollständigen Backup zurückgegangen. 6.6 Zusammenfassung HADES stellt grundlegende Datenbank-Operationen zur Verfügung: Tabellen können angelegt und gelöscht werden, Daten gelesen und geschrieben werden. Transaktionen erlauben es, Zugriffe mehrerer Clients zu koordinieren, dabei werden Mechanismen bereitgestellt, die es ermöglichen Transaktionen übergreifend über mehrere Datenbanken aufzubauen. Komplexe Operationen wie das Selektieren und Sortieren verbessern die Nutzung der zur Verfügung stehenden Hauptspeicherbandbreite und ermöglichen durch Parallelisierung eine effiziente Nutzung der zur Verfügung stehenden Rechenleistung. Ein voll asynchrones Audit-Logging, das die Zugriffszeit nicht beeintr ächtigt, ermöglicht es, Änderungen am Datenbestand nachzuvollziehen, und es bietet eine Backup-Möglichkeit für den Fall eines gleichzeitigen Ausfalls mehrerer Server. Daten werden immer redundant auf zwei Servern gespeichert, so daß der Ausfall eines einzelnen Servers nicht zum Datenverlust führt. Die Redundanz wird nach einem Ausfall wieder hergestellt, so daß danach ein weiterer Ausfall auftreten darf. In Anhang C.2 findet sich eine Kurzreferenz für die von HADES zur Verfügung gestellten Methoden. 102 KAPITEL 6. VERTEILTES DATENMANAGEMENTSYSTEM Kapitel 7 Leistungsmessung Die von HADES erwarteten Vorteile konnten auch in der Praxis mit Messungen bestätigt werden. Je nach Datengröße konnten beim Schreiben Zugriffszeiten bis hinunter zu 0, 5ms erreicht werden, was etwa eine Größenordnung unter der Zugriffszeit von schnellen Festplatten liegt. Auch die Erwartungen an die Nutzbarkeit der Gesamtrechenleistung des Clusters für parallelisierbare Operationen wurden erfüllt und die nutzbare Rechenleistung skaliert dabei recht gut mit der Anzahl der Server im Cluster. In den folgenden Abschnitten möchte ich die detaillierten Messungen vorstellen sowie die Ergebnisse analysieren und bewerten. 7.1 Testumgebung Abbildung 7.1 zeigt die Topologie der eingesetzten Testumgebung. Alle Rechner sind identisch ausgestattet: • AMD Athlon XP2000+ (1667MHz) • 1 GBytes Hauptspeicher (DDR-RAM) • Fast-Ethernet Netzwerkkarte (Realtek 8139) • keine Festplatte (die Rechner werden über Netzwerk gebootet) 103 104 KAPITEL 7. LEISTUNGSMESSUNG Abbildung 7.1: Eingesetzte Test-Umgebung Als Betriebssystem wurde für die Messungen SuSE-Linux 8.0 eingesetzt, das zum Booten über Netzwerk angepaßt wurde. Die beiden eingesetzten Fast-Ethernet Switches waren: • Cisco Catalyst 2916 • Level One FSW-2108TX Somit stand für die Messungen kein redundantes Netzwerk zur Verfügung. Dieses sollte aber keine Auswirkungen auf die Messungen haben, da bei einer redundanten Auslegung die redundanten Verbindungen durch das von des Switches durchgef ührte Spanning-Tree-Protokoll abgeschaltet werden, so daß im Betrieb auch nur ein einfaches Netz vorhanden ist. Die redundanten Verbindungen werden nur im Fehlerfall wieder aktiviert, haben aber ansonsten keine Auswirkungen. 7.2 Testablauf Ein Rechner dieses Netzwerks wurde als Client verwendet. Von den anderen Rechnern wurden soviele als Server eingesetzt, wie für die jeweilige Messung erforderlich waren. 7.3. BENCHMARK 105 Jede Operation wurde 100-mal durchgeführt und die benötigte Zeit gemessen. Die Messungen wurden dabei zu einem Zeitpunkt durchgeführt, an dem das Netzwerk nur wenig ausgelastet war und keine anderen Programme nennenswert Rechenzeit auf den Rechnern beanspruchten. Ein Abtrennen des verwendeten Teilnetzes war nicht möglich, da der Fileserver, von dem die Rechner ihr Root-Dateisystem bezogen, nicht im gleichen Teilnetz lag und dieser Rechner während der Messungen auch noch für andere Aufgaben gebraucht wurde und daher nicht in dieses Teilnetz verschoben werden konnte. 7.3 7.3.1 Benchmark Lesen und Schreiben von Daten außerhalb von Transaktionen Abbildungen 7.2 und 7.3 geben einen Überblick über die gemessenen Werte beim Schreiben bzw. Lesen von Daten. Diese Werte wurden jeweils für Datenblöcke von 2 Bytes und 512 Bytes ermittelt, sowie mit einer unterschiedlichen Anzahl von Servern. Die Slice-Anzahl war in allen Fällen 13. Die dazugehörenden statistischen Werte finden sich in Tabelle 7.1. Die Messungen mit Datenblöcken von 2 Bytes dienen zum Ermitteln der Latenz, die von der übertragenen Datenmenge unabhängig ist und immer anfällt, da die Zeit, die zum Übertragen von 2 Bytes benötigt wird, vernachlässigt werden kann. Als zweite Blockgröße wurde 512 Bytes gewählt, da dieses die meistverwendete Blockgröße von Festplatten darstellt und somit die Zugriffszeiten direkt verglichen werden k önnen. Wie aus den Abbildungen und der Tabelle deutlich wird, liegen die Meßwerte f ür alle Kombinationen für Schreibzugriffe deutlich unter 1ms. Dabei liegen die Meßwerte sehr dicht zusammen, was auch am geringen Abstand zwischen 1. und 3. Quartil sichtbar wird, und nur einzelne Ausreißer weisen deutlich h öhere Meßwerte auf. Die Ausreißer lassen sich dabei durch folgende Effekte erklären: • Das Netzwerk war zwar nur wenig ausgelastet, aber einzelne Datenpakete im Netz können ausreichen einzelne Werte zu beeinflussen. • Die Rechner waren zwar nur wenig ausgelastet, jedoch schließt dies nicht aus, daß ein Systemprozeß kurzzeitig Rechenzeit benötigt. Insgesamt läßt sich ein leichtes Ansteigen der Zugriffszeit mit der Anzahl der Server feststellen. Dieses kann auf den Verwaltungsmehraufwand der gr ößeren Anzahl an KAPITEL 7. LEISTUNGSMESSUNG 3.0 106 1.5 0.0 0.5 1.0 Zeit in ms 2.0 2.5 512 Bytes 2 Bytes 2 3 4 5 6 7 8 9 Anzahl der Server 3.0 Abbildung 7.2: Schreiben außerhalb von Transaktionen 1.5 1.0 0.5 0.0 Zeit in ms 2.0 2.5 512 Bytes 2 Bytes 2 3 4 5 6 7 8 Anzahl der Server Abbildung 7.3: Lesen außerhalb von Transaktionen 9 7.3. BENCHMARK 107 Anzahl der Server 5 6 7 2 3 4 Schreiben (2 Bytes) Minimum 0,395 0,402 0,406 1. Quartil 0,397 0,410 0,415 Median 0,399 0,417 0,425 Mittelwert 0,402 0,478 0,488 3. Quartil 0,399 0,420 0,436 Maximum 0,541 0,286 2,636 Schreiben (512 Bytes) Minimum 0.566 0.589 0.590 1. Quartil 0.592 0.605 0.605 Median 0.597 0.611 0.618 Mittelwert 0.605 0.616 0.636 3. Quartil 0.604 0.614 0.629 Maximum 1.415 1.060 1.974 Lesen (2 Bytes) Minimum 0,195 0,217 0,203 1. Quartil 0,208 0,219 0,218 Median 0,209 0,221 0,220 Mittelwert 0,212 0,224 0,236 3. Quartil 0,212 0,225 0,224 Maximum 0,342 0,342 1,067 Lesen (512 Bytes) Minimum 0,282 0,293 0,303 1. Quartil 0,300 0,311 0,313 Median 0,301 0,313 0,317 Mittelwert 0,305 0,317 0,353 3. Quartil 0,304 0,317 0,321 Maximum 0,811 0,432 2,697 Alle Werte in 8 9 0,419 0,428 0,442 0,486 0,454 2,631 0,411 0,433 0,447 0,515 0,459 3,108 0,421 0,444 0,463 0,503 0,478 3,125 0,428 0,457 0,476 0,557 0,493 3,721 0,445 0,455 0,471 0,564 0,489 3,419 0.619 0.626 0.636 0.707 0.650 3.037 0.610 0.633 0.640 0.655 0.646 1.709 0.618 0.644 0.674 0.697 0.720 1.702 0.610 0.649 0.681 0.704 0.721 1.601 0.613 0.660 0.696 0.756 0.764 5.103 0,225 0,228 0,229 0,252 0,237 0,876 0,221 0,229 0,231 0,237 0,236 0,350 0,224 0,233 0,236 0,250 0,247 0,509 0,228 0,236 0,242 0,258 0,258 0,462 0,322 0,328 0,332 0,360 0,389 0,514 0,318 0,318 0,320 0,326 0,323 0,330 0,343 0,359 0,329 0,332 1,112 2,108 Millisekunden. 0,319 0,326 0,330 0,349 0,344 0,628 0,320 0,328 0,331 0,350 0,384 0,530 0,322 0,326 0,332 0,363 0,388 0,913 Tabelle 7.1: Lesen und Schreiben außerhalb von Transaktionen 108 KAPITEL 7. LEISTUNGSMESSUNG Servern zurückgeführt werden. Der Anstieg ist aber so gering, daß er in diesem Bereich keine Probleme für die Skalierbarkeit darstellt. Wie dies allerdings für sehr große Serverzahlen aussieht, kann aus diesen Messungen nat ürlich nicht extrapoliert werden. Lesezugriffe sind etwa doppelt so schnell wie Schreibzugriffe. Dies stimmt gut damit überein, daß zum Lesen insgesamt nur halb so viele Nachrichten ben ötigt werden, da beim Lesen außerhalb von Transaktionen keine Kommunikation zwischen Prim ärserver und Sekundärserver notwendig ist. Betrachtet man die Situation bei parallelen Zugriffen, wie sie in Kapitel 6.1 beschrieben wurden, so ergibt sich ein etwas anderes Bild. Abbildungen 7.4 und 7.5 sowie Tabelle 7.2 zeigen die Zeiten zum parallelen Schreiben bzw. Lesen von 9 aufeinanderfolgenden Seiten. Der Trend, daß die Zugriffszeiten mit der Serveranzahl ansteigen ist nur noch bei Lesezugriffen vorhanden, Schreibzugriffe k önnen nun von der größeren Anzahl an Servern profitieren, so daß die Zeit, die zum parallelen Schreiben von Seiten benötigt wird, mit der größeren Serveranzahl sinkt. Im Vergleich zu einfachen Zugriffen läßt sich durch parallele Zugriffe das Netzwerk wesentlich besser auslasten, da weniger Wartezeiten auftreten. Dies spiegelt sich in den Meßergebnissen der parallelen Zugriffe wider: Die Zugriffszeit w ächst sublinear mit der Anzahl der parallelen Zugriffe. Für 9 parallele Schreibzugriffe wird z.B. nur knapp die doppelte Zeit anstatt der 9-fachen Zeit gegenüber einem einfachen Zugriff benötigt. Abbildungen 7.6 und 7.7 zeigen die Abhängigkeit der Zugriffszeit von der Zahl der parallelen Zugriffe (jeweils mit 9 Servern). Als Seitenadressen wurden jeweils aufeinanderfolgende Adressen verwendet. 7.3.2 Lesen und Schreiben von Daten innerhalb von Transaktionen Zur Messung der Auswirkung von Transaktionen auf die Operationen wurden die gleichen Messungen wie im vorangegangenen Abschnitt durchgef ührt, nur mit dem Unterschied, daß die Schreib- bzw. Leseoperation in eine Transaktion eingebettet war. Bei der Messung wurde sichergestellt, daß keine Zugriffskonflikte mit anderen Transaktionen auftreten, die zu Wartezeiten hätten führen können. Abbildungen 7.8 und 7.9 sowie Tabelle 7.3 zeigen die Ergebnisse f ür einfache Schreibund Lesezugriffe. Die Meßergebnisse für Schreibzugriffe sind innerhalb der Meßgenauigkeit identisch zu den Ergebnissen ohne Transaktionen. Dies war auch zu erwarten, da auch ohne Transaktionen Änderungen auf den Sekundärserver kopiert werden müssen. Die Lock-Anforderung wird an diese Nachricht angehängt, so daß keine zusätzlichen Nachrichten benötigt werden. 109 3.0 7.3. BENCHMARK 1.5 0.0 0.5 1.0 Zeit in ms 2.0 2.5 512 Bytes 2 Bytes 2 3 4 5 6 7 8 9 Anzahl der Server 3.0 Abbildung 7.4: Schreiben außerhalb von Transaktionen (9 Zugriffe parallel) 1.5 0.0 0.5 1.0 Zeit in ms 2.0 2.5 512 Bytes 2 Bytes 2 3 4 5 6 7 8 9 Anzahl der Server Abbildung 7.5: Lesen außerhalb von Transaktionen (9 Zugriffe parallel) 110 2 3 Schreiben (2 Bytes) Minimum 1,061 0,988 1. Quartil 1,110 1,064 Median 1,162 1,103 Mittelwert 1,165 1,127 3. Quartil 1,191 1,173 Maximum 1,680 1,984 Schreiben (512 Bytes) Minimum 1,327 1,241 1. Quartil 1,404 1,331 Median 1,437 1,380 Mittelwert 1,449 1,392 3. Quartil 1,478 1,441 Maximum 1,923 1,941 Lesen (2 Bytes) Minimum 0,524 0,597 1. Quartil 0,572 0,634 Median 0,611 0,659 Mittelwert 0,612 0,667 3. Quartil 0,649 0,689 Maximum 0,801 0,806 Lesen (512 Bytes) Minimum 0,702 0,741 1. Quartil 0,727 0,771 Median 0,741 0,784 Mittelwert 0,741 0,798 3. Quartil 0,754 0,805 Maximum 0,799 1,181 Alle KAPITEL 7. LEISTUNGSMESSUNG 4 Anzahl der Server 5 6 7 8 9 0,951 0,988 1,016 1,021 1,050 1,256 0,910 0,948 0,967 1,000 1,000 2,143 0,903 0,934 0,954 0,973 0,979 1,951 0,851 0,900 0,924 1,126 0,951 14,360 0,872 0,911 0,930 0,970 0,950 3,929 0,810 0,839 0,853 0,873 0,875 1,800 1,190 1,244 1,277 1,288 1,304 2,473 1,153 1,209 1,240 1,281 1,275 2,429 1,136 1,181 1,203 1,211 1,229 1,607 1,101 1,178 1,212 1,218 1,252 1,425 1,138 1,179 1,201 1,225 1,227 2,223 1,085 1,144 1,180 1,280 1,199 6,036 0,570 0,630 0,644 0,669 0,685 1,764 0,612 0,696 0,714 0,721 0,750 0,856 0,628 0,699 0,727 0,734 0,764 0,870 0,665 0,727 0,749 0,749 0,774 0,849 0,681 0,746 0,768 0,776 0,791 1,337 0,769 0,793 0,800 0,819 0,820 1,853 0,767 0,792 0,808 0,814 0,820 1,224 0,767 0,787 0,801 0,814 0,821 1,295 0,758 0,779 0,792 0,834 0,809 2,576 0,750 0,763 0,753 0,780 0,785 0,779 0,797 0,797 0,790 0,812 0,810 0,794 0,830 0,813 0,803 1,277 1,387 0,952 Werte in Millisekunden. Tabelle 7.2: Paralleles Lesen und Schreiben außerhalb von Transaktionen (9 Zugriffe parallel) 111 3.0 7.3. BENCHMARK 1.5 0.0 0.5 1.0 Zeit in ms 2.0 2.5 512 Bytes 2 Bytes 2 4 6 8 Anzahl der parallelen Zugriffe 3.0 Abbildung 7.6: Paralleles Schreiben außerhalb von Transaktionen 1.5 0.0 0.5 1.0 Zeit in ms 2.0 2.5 512 Bytes 2 Bytes 2 4 6 8 Anzahl der parallelen Zugriffe Abbildung 7.7: Paralleles Lesen außerhalb von Transaktionen KAPITEL 7. LEISTUNGSMESSUNG 3.0 112 1.5 0.0 0.5 1.0 Zeit in ms 2.0 2.5 512 Bytes 2 Bytes 2 3 4 5 6 7 8 9 Anzahl der Server 3.0 Abbildung 7.8: Schreiben innerhalb von Transaktionen 1.5 1.0 0.5 0.0 Zeit in ms 2.0 2.5 512 Bytes 2 Bytes 2 3 4 5 6 7 8 Anzahl der Server Abbildung 7.9: Lesen innerhalb von Transaktionen 9 7.3. BENCHMARK 113 Anzahl der Server 5 6 7 2 3 4 Schreiben (2 Bytes) Minimum 0,400 0,406 0,414 1. Quartil 0,404 0,410 0,423 Median 0,406 0,417 0,426 Mittelwert 0,412 0,424 0,442 3. Quartil 0,408 0,423 0,442 Maximum 0,774 0,753 1,167 Schreiben (512 Bytes) Minimum 0,556 0,591 0,597 1. Quartil 0,575 0,606 0,615 Median 0,589 0,612 0,619 Mittelwert 0,592 0,614 0,626 3. Quartil 0,594 0,617 0,626 Maximum 1,211 0,732 0,930 Lesen (2 Bytes) Minimum 0,385 0,399 0,409 1. Quartil 0,391 0,408 0,419 Median 0,397 0,410 0,424 Mittelwert 0,402 0,428 0,430 3. Quartil 0,400 0,416 0,433 Maximum 0,743 1,450 0,647 Lesen (512 Bytes) Minimum 0,463 0,482 0,497 1. Quartil 0,481 0,503 0,511 Median 0,485 0,512 0,523 Mittelwert 0,498 0,518 0,525 3. Quartil 0,487 0,524 0,528 Maximum 1,214 0,800 0,765 Alle Werte in 8 9 0,417 0,430 0,440 0,448 0,450 0,731 0,418 0,431 0,439 0,445 0,452 0,621 0,414 0,445 0,459 0,484 0,484 1,437 0,435 0,444 0,457 0,667 0,487 4,972 0,428 0,457 0,471 0,502 0,490 2,739 0,607 0,622 0,628 0,638 0,636 0,943 0,613 0,622 0,632 0,636 0,641 0,816 0,615 0,635 0,656 0,683 0,700 1,790 0,616 0,640 0,671 0,695 0,722 1,132 0,625 0,659 0,693 0,864 0,763 5,842 0,404 0,422 0,429 0,442 0,444 0,721 0,395 0,423 0,432 0,437 0,445 0,583 0,413 0,440 0,455 0,461 0,468 0,774 0,434 0,445 0,459 0,474 0,483 0,771 0,424 0,443 0,460 0,471 0,479 0,713 0,496 0,505 0,519 0,531 0,531 0,538 0,542 0,544 0,538 0,543 1,530 0,870 Millisekunden. 0,508 0,540 0,562 0,573 0,602 0,793 0,529 0,550 0,564 0,590 0,608 1,198 0,534 0,558 0,584 0,597 0,623 0,935 Tabelle 7.3: Lesen und Schreiben innerhalb von Transaktionen KAPITEL 7. LEISTUNGSMESSUNG 3.0 114 1.5 0.0 0.5 1.0 Zeit in ms 2.0 2.5 512 Bytes 2 Bytes 2 3 4 5 6 7 8 9 Anzahl der Server 3.0 Abbildung 7.10: Schreiben innerhalb von Transaktionen (9 Zugriffe parallel) 1.5 0.0 0.5 1.0 Zeit in ms 2.0 2.5 512 Bytes 2 Bytes 2 3 4 5 6 7 8 9 Anzahl der Server Abbildung 7.11: Lesen innerhalb von Transaktionen (9 Zugriffe parallel) 7.3. BENCHMARK 115 Anzahl der Server 5 6 7 2 3 4 Schreiben (2 Bytes) Minimum 1,081 0,954 0,907 1. Quartil 1,185 1,020 0,976 Median 1,235 1,085 0,998 Mittelwert 1,230 1,088 1,010 3. Quartil 1,272 1,140 1,019 Maximum 1,567 1,482 2,184 Schreiben (512 Bytes) Minimum 1,363 1,221 1,154 1. Quartil 1,428 1,296 1,241 Median 1,455 1,330 1,265 Mittelwert 1,471 1,338 1,281 3. Quartil 1,499 1,375 1,298 Maximum 2,302 1,627 2,327 Lesen (2 Bytes) Minimum 1,085 0,948 0,905 1. Quartil 1,138 1,023 0,978 Median 1,215 1,084 1,004 Mittelwert 1,207 1,100 1,013 3. Quartil 1,262 1,153 1,029 Maximum 1,350 1,797 1,581 Lesen (512 Bytes) Minimum 1,182 1,147 1,096 1. Quartil 1,247 1,208 1,140 Median 1,291 1,243 1,168 Mittelwert 1,307 1,261 1,193 3. Quartil 1,341 1,287 1,201 Maximum 2,253 1,854 2,555 Alle Werte in 8 9 0,861 0,922 0,941 0,964 0,975 1,969 0,851 0,897 0,921 0,944 0,950 1,362 0,822 0,874 0,894 0,926 0,935 1,708 0,827 0,853 0,876 0,908 0,901 1,729 0,781 0,826 0,844 0,857 0,862 1,839 1,124 1,182 1,212 1,224 1,241 1,932 1,125 1,158 1,184 1,229 1,218 2,534 1,069 1,163 1,198 1,205 1,246 1,598 1,108 1,156 1,184 1,221 1,219 2,450 1,105 1,144 1,176 1,193 1,212 1,710 0,851 0,908 0,931 0,939 0,959 1,195 0,837 0,894 0,923 0,961 0,965 2,055 0,822 0,864 0,885 0,900 0,906 2,000 0,835 0,864 0,881 0,915 0,922 1,715 0,795 0,823 0,840 0,861 0,863 2,180 1,046 0,997 1,102 1,075 1,135 1,095 1,145 1,100 1,161 1,128 1,802 1,357 Millisekunden. 1,027 1,062 1,095 1,095 1,114 1,392 1,019 1,043 1,062 1,082 1,089 1,654 0,991 1,018 1,033 1,043 1,054 1,251 Tabelle 7.4: Paralleles Lesen und Schreiben innerhalb von Transaktionen (9 Zugriffe parallel) KAPITEL 7. LEISTUNGSMESSUNG 3.0 116 1.5 0.0 0.5 1.0 Zeit in ms 2.0 2.5 512 Bytes 2 Bytes 2 4 6 8 Anzahl der parallelen Zugriffe 3.0 Abbildung 7.12: Paralleles Schreiben innerhalb von Transaktionen 1.5 0.0 0.5 1.0 Zeit in ms 2.0 2.5 512 Bytes 2 Bytes 2 4 6 8 Anzahl der parallelen Zugriffe Abbildung 7.13: Paralleles Lesen innerhalb von Transaktionen 7.3. BENCHMARK 117 Lesezugriffe hingegen werden deutlich langsamer als ohne Transaktionen ausgef ührt. Sie benötigen mit Transaktionen fast genauso viel Zeit wie Schreibzugriffe mit kleinen Blockgrößen. Dieses war auch zu erwarten, da gesetzte Shared-Locks an den Sekundärserver übertragen werden müssen, damit diese bei einem eventuellen Ausfall bestehen bleiben. Der Geschwindigkeitsunterschied zwischen Lesen und Schreiben beim Arbeiten ohne Transaktionen kommt daher, daß zum Lesen keine Kommunikation zwischen Primär- und Sekundärserver benötigt wird. Dieser Vorteil entfällt, sobald Transaktionen verwendet werden und somit verringert sich auch der Geschwindigkeitsunterschied. Trotzdem bleibt ein geringer Vorteil von Lesezugriffen, da die eigentlichen Nutzdaten nicht zum Sekundärserver übertragen werden und somit weniger Zeit für die Datenübertragung zwischen Primär- und Sekundärserver benötigt wird. Das gleiche gilt auch für das parallele Schreiben und Lesen in Transaktionen (Abbildungen 7.10 und 7.11 sowie Tabelle 7.4): Die Geschwindigkeit von Schreibzugriffen ändert sich nicht, Lesezugriffe brauchen länger und nähern sich an die Geschwindigkeit von Schreibzugriffen an. Abbildungen 7.12 und 7.13 zeigen die Abh ängigkeit der Zugriffszeit von der Zahl der parallelen Zugriffe (jeweils mit 9 Servern). 7.3.3 Vergleich zwischen writePage/readPage und writePageSet/ readPageSet Sollen mehrere Seiten in das gleiche Slice geschrieben werden, so gibt es zwei M öglichkeiten: 1. Man verwendet mehrere parallele Zugriffe mittels writePage bzw. readPage. 2. Man verwendet writePageSet bzw. readPageSet, die mit einer Operation mehrere Seiten schreiben oder lesen können. Um zu untersuchen, wie die unterschiedlichen Möglichkeiten abschneiden, habe ich Messungen mit verschiedenen Seitengrößen durchgeführt. Es wurden jeweils 100 Seiten in ein Slice geschrieben oder gelesen und die insgesamt daf ür benötigte Zeit gemessen (ohne Transaktion). Da alle Zugriffe das gleiche Slice verwenden, spielt die Clustergröße keine Rolle. Abbildung 7.14 zeigt die Ergebnisse der Messung. Bei kleinen Seitengrößen bieten writePageSet und readPageSet aufgrund des geringeren Overhead Vorteile: Es muß je nur eine Nachricht versandt werden, so daß nur einmal Verwaltungsdaten anfallen und der Rest für Nutzdaten verwendet werden kann. Die Zugriffszeiten von writePage und readPage werden in diesem Bereich vom Overhead dominiert und sind von der Seitengröße nahezu unabhängig. 200.0 KAPITEL 7. LEISTUNGSMESSUNG 2.0 5.0 20.0 50.0 paralleles writePage paralleles readPage writePageSet readPageSet theoretisches Limit 0.5 Übertragungszeit für 100 Seiten in ms 118 20 50 100 200 500 2000 5000 20000 Seitengröße in Bytes Abbildung 7.14: Schreiben/Lesen von Daten im gleichen Slice Bei größeren Seitengrößen kehren sich die Verhältnisse aber um und writePage und readPage können aufgrund der besseren Parallelisierbarkeit Vorteile verbuchen. Während bei writePageSet der Primärserver zunächst die ganze Nachricht mit allen Seiten lesen muß, bevor die Nachricht an den Sekundärserver weitergeleitet werden kann, kann der Primärserver bei writePage bereits Seiten an den Sekundärserver senden, während er noch ausstehende Seiten bearbeitet. Daraus resultiert eine bessere Parallelverarbeitung zwischen Primärserver und Sekundärserver, die zu einer niedrigeren Übertragungszeit führt. Analog dazu gibt es beim Lesen eine bessere Parallelverarbeitung zwischen Primärserver und Client, da der Client früher mit dem Bearbeiten der Ergebnisse beginnen kann. In der Abbildung ist zusätzlich das theoretische Limit eingezeichnet, das durch die Übertragungsbandbreite der Ethernet-Schnittstelle vorgegeben ist. F ür große Seitengrößen wird dieses Limit mit writePage/readPage fast erreicht, die zur Verf ügung stehende Bandbreite wird gut genutzt. 7.3.4 Transaktionen Neben dem Schreiben und Lesen von Daten ist die Zeit, die ein Abort oder Commit benötigt, entscheidend für die Geschwindigkeit, mit der Transaktionen durchgeführt werden können. 119 3.0 7.3. BENCHMARK 1.5 0.0 0.5 1.0 Zeit in ms 2.0 2.5 dicht aufeinanderfolgend einzeln 2 3 4 5 6 7 8 9 Anzahl der Server Abbildung 7.15: Abort/Commit Während eine Lese- oder Schreiboperation abgeschlossen ist, sobald der Client die Antwort erhält, ist dies bei einem Abort oder Commit nicht der Fall. Es werden nach dem Absenden der Antwort an den Client noch weitere Nachrichten zwischen den Servern ausgetauscht, um Änderungen zu aktivieren und Locks freizugeben. Diese können die Geschwindigkeit von nachfolgenden Operationen beeinflussen. Aus diesem Grund wurden zwei Meßreihen durchgeführt: Die erste führte Operationen direkt hintereinander aus, ohne Pause zwischen den einzelnen Operationen. Auf diese Weise wurde der Effekt von vorangegangenen Transaktionen gemessen. Die zweite Meßreihe ließ jeweils eine kurze Pause zwischen den Operationen, damit keine Nachrichten von vorangegangenen Transaktionen mehr unterwegs waren. Um die Zeit für Abort bzw. Commit zu ermitteln, wurden für jede Serverkonfiguration 100 Messungen durchgeführt. Abbildung 7.15 zeigt die gemessenen Werte. Die Zeiten mit Pause zwischen den Operationen entsprechen ziemlich genau den Zeiten, wie sie beim Lesen von Seiten innerhalb von Transaktionen anfallen. Dieses war zu erwarten, da in beiden Fällen vier Nachrichten auf dem kritischen Pfad liegen, der die Zeit bestimmt. Beim Lesen sind dies die Nachrichten vom Client zum Primärserver, vom Primärserver zum Sekundärserver sowie die Antworten auf diese Nachrichten. Beim Abort und Commit sind dies die Nachrichten vom Client zum Koordinator, von diesem zum Backup-Koordinator sowie die Antworten auf diese Nachrichten. 120 KAPITEL 7. LEISTUNGSMESSUNG 2 gleiche Seite Minimum 1,191 1. Quartil 1,200 Median 1,202 Mittelwert 1,228 3. Quartil 1,253 Maximum 1,658 aufsteigende Seiten Minimum 1,191 1. Quartil 1,208 Median 1,235 Mittelwert 1,244 3. Quartil 1,264 Maximum 1,546 einzeln mit Pause Minimum 1,217 1. Quartil 1,223 Median 1,228 Mittelwert 1,295 3. Quartil 1,232 Maximum 6,231 Anzahl der Server 5 6 7 3 4 1,436 1,450 1,458 1,477 1,465 2,294 1,448 1,539 1,573 1,585 1,608 2,643 1,573 1,677 1,700 1,698 1,727 1,780 1,322 1,731 1,762 1,767 1,787 2,527 1,218 1,374 1,410 1,416 1,444 2,175 1,227 1,438 1,466 1,513 1,557 2,650 1,416 1,486 1,540 1,566 1,616 1,894 1,219 1,255 1,227 1,269 1,248 1,288 1,312 1,327 1,257 1,307 4,514 2,393 Alle Werte in 8 9 1,555 1,652 1,674 1,697 1,699 2,773 1,584 1,773 1,795 1,797 1,823 1,923 1,513 1,718 1,770 1,795 1,822 3,059 1,447 1,567 1,602 1,659 1,645 2,861 1,408 1,519 1,552 1,614 1,664 2,810 1,474 1,549 1,591 1,694 1,706 2,864 1,500 1,645 1,680 1,731 1,715 2,373 1,271 1,236 1,292 1,262 1,305 1,276 1,385 1,335 1,347 1,299 4,759 4,870 Millisekunden. 1,342 1,356 1,367 1,462 1,421 5,025 1,419 1,433 1,440 1,506 1,467 5,269 1,420 1,439 1,443 1,525 1,480 5,123 Tabelle 7.5: Transaktion: Lesen - Modifizieren - Zurückschreiben 121 3.0 7.3. BENCHMARK 2.0 1.0 1.5 Zeit in ms 2.5 gleiche Seite aufsteigende Seiten einzeln 2 3 4 5 6 7 8 9 Anzahl der Server Abbildung 7.16: Transaktion: Lesen - Modifizieren - Zurückschreiben Da eine Transaktion, die nur aus einem Abort oder Commit besteht, im normalen Einsatz nicht besonders sinnvoll ist, habe ich eine weitere Messung mit einer Transaktion durchgeführt, die einen Wert liest, ihn modifiziert und wieder zur ückschreibt und danach mit Commit beendet wird. Die Messung habe ich f ür drei Varianten durchgeführt: 1. Transaktionen, die durch eine kurze Pause voneinander getrennt sind. Dadurch ist das vorangegangene Commit abgeschlossen und hat keinen Einfluß auf die Dauer der aktuellen Transaktion. 2. Transaktionen, die verschiedene Seiten lesen. 3. Transaktionen, die die gleiche Seite lesen. In diesem Fall kann es vorkommen, daß noch nicht alle Locks von der vorangegangenen Transaktion freigegeben sind und so zusätzliche Wartezeiten auftreten. Abbildung 7.16 sowie Tabelle 7.5 zeigen die dabei erhaltenen Ergebnisse. Der Vergleich der hier gemessenen Zeiten mit den Messungen der einzelnen Operationen zeigt, daß für einzelne Transaktionen die Zeit mit der Summe der Einzeloperationen gut übereinstimmt. Für den Fall, daß mehrere Transaktionen direkt 122 KAPITEL 7. LEISTUNGSMESSUNG hintereinander ausgeführt werden, gilt dies jedoch nicht mehr. Hier liegt die Zeit, je nachdem welche der beiden Messungen von Commit (mit oder ohne Pause) man verwendet, über bzw. unter der Summe der Einzelzeiten. Bei Transaktionen deuten sich aber auch die Grenzen der Skalierbarkeit an. F ür Abort oder Commit muß der Koordinator an jeden anderen Server Nachrichten senden sowie von jedem anderen Server Nachrichten empfangen. Der Aufwand hierf ür wächst linear mit der Anzahl der Rechner. Während dies hier bei 9 Servern noch kein Problem darstellt, ist abzusehen, daß Transaktionen f ür eine große Zahl von Rechnern je nach Einsatzgebiet den Flaschenhals darstellen k önnen. 7.3.5 Verteilte Transaktionen Als Beispiel einer verteilten Transaktion habe ich zum Messen eine Transaktion verwendet, die einen Wert von einem Cluster liest, den Wert modifiziert und in einen zweiten Cluster schreibt. Auf diese Weise können Daten innerhalb einer verteilten Transaktion von einer Datenbank zu einer anderen weitergegeben werden, wobei garantiert werden kann, daß Daten nicht verloren gehen oder verdoppelt werden. Zum Messen wurden je zwei Cluster mit der gleichen Anzahl von Servern verwendet. Folgende Zeiten wurden für die Transaktion ermittelt: Anzahl der Server 2x2 2x3 2x4 Minimum 1,373 1,436 1,524 1. Quartil 1,394 1,450 1,551 Median 1,416 1,457 1,569 Mittelwert 1,421 1,464 1,565 3. Quartil 1,456 1,468 1,579 Maximum 1,482 1,548 1,599 Alle Werte in Millisekunden Verglichen mit einfachen Transaktionen sind die Zeiten somit nur ca. 15-20% schlechter. 7.3.6 Selektieren Selektieren läßt sich sehr gut parallelisieren, indem die Daten in gleich große Anteile unterteilt werden, die getrennt durchsucht werden, so daß sich damit die Rechenleistung eines Clusters sehr gut nutzen läßt. In HADES kann dafür die bereits vorhandene Unterteilung in Slices genutzt werden. Zum Messen der Geschwindigkeit habe 123 10 20 30 5 Slices 13 Slices 18 Slices 30 Slices 60 Slices 0 Mio. Einträge pro Sekunde 40 7.3. BENCHMARK 2 3 4 5 6 7 8 9 8 9 Anzahl der Server 5 4 3 2 1 5 Slices 13 Slices 18 Slices 30 Slices 60 Slices 0 Mio. Einträge pro Sekunde pro Server 6 Abbildung 7.17: Selektieren 2 3 4 5 6 7 Anzahl der Server Abbildung 7.18: Selektieren – Einträge pro Server pro Sekunde 124 KAPITEL 7. LEISTUNGSMESSUNG ich einen Testdatensatz mit Zufallseinträgen erzeugt. Folgendes Tabellenschema kam dabei zum Einsatz: Name Category ID Text Typ VARCHAR VARCHAR VARCHAR Größe 1 7 40 Die Tabelle wurde mit 2.000.000 zufälligen Einträgen gefüllt und anschließend eine Selektion durchgeführt, die äquivalent zu folgender SQL-Anweisung ist: SELECT COUNT(*) FROM benchmark WHERE Category=’a’; Von allen Datensätzen erfüllten 31970 das Selektionskriterium. Die Messung wurde mit verschiedenen Slice-Anzahlen durchgeführt, um die Auswirkung der Slice-Anzahl zu untersuchen. Abbildungen 7.17 zeigt die erreichte Geschwindigkeit und Abbildung 7.18 die pro Server erbrachte Leistung. Insgesamt skaliert HADES in diesem Bereich recht gut, solange die Anzahl der Slices hoch genug ist, damit die Slices m öglichst gleichmäßig auf die Rechner verteilt werden können. Ist dies nicht der Fall, wie in der Messung mit 5 Slices, so wird die Parallelisierung durch die Slice-Anzahl begrenzt und zusätzliche Server bringen keinen weiteren Leistungszuwachs. Ungleichm äßige Verteilungen machen sich als Stufen in Abbildung 7.17 bemerkbar. Sie entstehen dadurch, daß der Rechner mit der größten Last die Geschwindigkeit bestimmt. Aus diesem Grund ist für diese Operation eine hohe Slice-Anzahl von Vorteil, da sie im Durchschnitt eine gleichmäßigere Lastverteilung ermöglicht. 7.3.7 Auswirkung der Slice-Anzahl Wie eben gesehen bietet eine hohe Slice-Anzahl bei der Parallelisierung Vorteile. Deshalb habe ich einzelne Messungen mit einer Slice-Anzahl von 60 wiederholt. Einzelne Schreib- und Leseoperationen sind von der höheren Slice-Anzahl nicht betroffen, auch einzelne durch Pausen getrennte Commit-Operationen ben ötigen nicht mehr Zeit. Paralleles Schreiben auf aufeinanderfolgende Seiten kann langsamer werden, da hierbei öfter auf den gleichen Server zugegriffen wird, wie folgendes Beispiel zeigt: Es sollen 20 aufeinanderfolgende Seiten geschrieben werden. Es stehen 9 Server zur Verfügung. Bei 13 Slices werden in alle Slices Seiten geschrieben, d.h. die Schreibzugriffe verteilen sich auf alle Server. Bei 60 Slices wird nur in ein drittel aller Slices geschrieben, so daß sich je nach Verteilung der Slices auf Server die Schreibzugriffe u.U. nur auf einen Teil der Server konzentrieren. 7.3. BENCHMARK 125 Eine stichprobenartige Messung bestätigte diesen Effekt. Das parallele Schreiben von 9 Seiten benötigt ca. 25% mehr Zeit, wenn 60 statt 13 Slices verwendet werden. Dieser Effekt verschwindet allerdings, wenn mehr Seiten parallel geschrieben werden, und bei 100 parallelen Zugriffen ist bereits ein leichter Vorteil meßbar, der sich aus der gleichmäßigeren Verteilung der Daten ergibt. Dicht aufeinanderfolgende Commit-Operationen erreichen ebenso nur eine etwas schlechtere Geschwindigkeit, da die Wahrscheinlichkeit steigt, daß ein Server w ährend des Commit an alle anderen Server eine Nachricht senden muß, so daß insgesamt die Anzahl der Nachrichten ansteigt. Faßt man alle diese Auswertungen zusammen, so kann man zwei Ergebnisse ableiten: • Die optimale Anzahl von Slices ist anwendungsabhängig und hängt vom Mix der eingesetzten Operationen ab. • Mit einer Slice-Anzahl im Bereich der 3 − 4-fachen Anzahl der Server werden gute Ergebnisse erreicht, so daß diese Anzahl als Startpunkt f ür ein FineTuning verwendet werden kann. 7.3.8 Operationen während einer Datenneuverteilung Fällt ein Server aus, so werden Daten umkopiert. Während dieser Zeit steht so nicht die gesamte Netzwerkbandbreite und Rechenzeit für andere Operationen zur Verfügung. Um die Auswirkungen davon zu messen, wurde der Datensatz geladen, der auch zum Selektieren verwendet wurde. Danach wurden nacheinander Server aus dem System entfernt und in der Phase des Umkopierens die Geschwindigkeit von Schreib- und Lesezugriffen gemessen (ohne Transaktionen). Die Seitengröße für diese Daten war 2 Bytes. Abbildungen 7.19 und 7.20 zeigen die Meßwerte und Leistungseinbußen, die beim Lesen und Schreiben ermittelt wurden. Die Leistungseinbuße f ällt mit steigender Anzahl der Server schnell ab. Daß beim Schreiben ab 6 Servern ein Leistungsgewinn gemessen wurde, der bei 8 Servern knapp 13% erreicht, läßt sich aber durch die begrenzte Meßgenauigkeit erklären und kann nicht als Beleg dafür gewertet werden, daß die Geschwindigkeit tatsächlich zunimmt. Das Umkopieren benötigt je nach Zahl der verbliebenen Rechner unterschiedlich lange, da unterschiedlich viele Daten kopiert werden müssen: Beim Übergang von 8 KAPITEL 7. LEISTUNGSMESSUNG 3.0 126 1.5 0.0 0.5 1.0 Zeit in ms 2.0 2.5 Schreiben Lesen 2 3 4 5 6 7 8 Anzahl der Server 100 50 0 Geschwindigkeitseinbuße in % 150 Abbildung 7.19: Lesen und Schreiben während einer Datenumverteilung −50 Schreiben Lesen 2 3 4 5 6 7 8 Anzahl der Server Abbildung 7.20: Geschwindigkeitseinbußen während einer Datenumverteilung 7.4. VERGLEICH MIT POSTGRESQL 127 auf 7 Server dauert das Umkopieren etwa 45 Sekunden, beim Übergang von 3 auf 2 Server etwa 114 Sekunden. Insgesamt liefert HADES auch während einer Datenumverteilung noch gute Werte, die immer noch deutlich unter der Zugriffszeit von Festplatten liegen und die mit steigender Serverzahl schnell besser werden. Bereits mit 4 Servern liegt die Geschwindigkeitseinbuße deutlich unter 50%. Als weiterer Test wurde eine Selektion durchgeführt. Die Meßwerte für das Selektieren wurden durch das Umkopieren nicht meßbar beeinflußt. Dies war auch zu erwarten, da es sich beim Selektieren um eine lange dauernde Operation handelt, die nur wenig vom Kommunikations-Overhead betroffen ist, da während der Selektion keine weiteren Daten umkopiert werden. 7.3.9 Auswirkungen der Prozessorleistung Während des Entwicklungszeitraums von HADES fand ein Leistungssprung der Prozessoren statt. Erste Tests fanden auf einem System mit 400MHz Prozessor statt, während die abschließenden Tests auf Prozessoren mit 1667MHz (Athlon XP200+) durchgeführt wurden. Beim Übergang auf die schnelleren Prozessoren konnte ich einen deutlichen Geschwindigkeitssprung feststellen, wobei der Faktor im Bereich von 2-4 lag, was ziemlich genau dem Zuwachs an Prozessorleistung entspricht. Leider standen zum Zeitpunkt der ausführlichen Leistungstests nicht mehr genügend dieser Systeme zum Testen zur Verfügung, so daß ich keinen quantitativen Vergleich durchführen konnte. Trotzdem legt dies nahe, daß auch künftige Leistungssprünge der Rechenleistung und Netzwerkleistung auf die Leistung von HADES durchschlagen, schließlich sind dies die einzigen Faktoren, die die Leistung von HADES limitieren. 7.4 Vergleich mit PostgreSQL Mit PostgreSQL steht eine kostenlose Datenbank zur Verfügung, deren Lizenz insbesondere keine Einschränkungen für die Veröffentlichung von Benchmark-Ergebnissen enthält, wie dies leider bei verschiedenen anderen Datenbanksystemen der Fall ist. Aus diesem Grund habe ich PostgreSQL als Vergleichsplattform gew ählt. Für die Datenbank wurde folgender Rechner verwendet: • Pentium 4, 2,4GHz 128 KAPITEL 7. LEISTUNGSMESSUNG • 896 MBytes Hauptspeicher (DDR-RAM) • 40 GBytes Festplatte Maxtor 34098H4 (5400 U/min, φ Zugriffszeit 9, 5ms, φ Latenz 5, 55ms) Somit stellte dieser Rechner eine etwas höhere Rechenleistung zur Verfügung als die einzelnen Server im Cluster. Es wurden vergleichbare Tests wie oben durchgeführt. Um zu aussagekräftigen Ergebnissen zu kommen, mußte dabei der Festplattencache abgeschaltet werden. Ansonsten wird nur die Zeit gemessen, in der die Daten in den Cache geschrieben werden. Daten im Festplattencache können jedoch bei einem Ausfall verloren gehen, so daß es für das von PostgreSQL eingesetzte Logging nicht ausreichend ist, wenn die Daten nur im Cache stehen. Mit diesen Einstellungen habe ich folgende Zeiten gemessen, die Zeiten, die mit HADES ermittelt wurden, sind zum Vergleich mit angegeben: Lesen Einfügen Selektieren PostgreSQL 1,2ms ca. 100ms 0,9 Mio. Einträge/Sekunde HADES 0,2-0,6ms 0,9-1,6ms1 8-38 Mio. Einträge/Sekunde Ohne ausgeschalteten Festplattencache erhält man im Durchschnitt für das Einfügen von Daten eine Zeit von ca. 4ms, dies liegt unter der durchschnittlichen Latenz der Platte, so daß davon ausgegangen werden muß, daß die Daten zu diesem Zeitpunkt noch nicht dauerhaft geschrieben wurden. Somit ergibt sich insgesamt folgendes Bild: • Beim Lesen kann PostgreSQL davon profitieren, daß die Daten komplett in den Hauptspeicher geladen werden können und zum Lesen dann keine Zugriffe auf die Platte mehr nötig sind. Trotzdem hat HADES einen leichten Vorsprung, da es direkt auf die Daten zugreifen kann. Die Speicherstrukturen von PostgreSQL sind für Festplatten optimiert, so daß bei Zugriffen über den Buffer-Manager ein größerer Overhead anfällt. • Beim Schreiben dominiert die Zugriffszeit der Festplatte. Hier kann HADES einen sehr großen Vorteil verbuchen, da keine Festplattenzugriffe ben ötigt werden. Die Zugriffszeiten liegen so bis zu zwei Größenordnungen unter den mit PostgreSQL gemessenen Zeiten. Durch den Einsatz einer schnelleren Platte (z.B. 15.000U/min) kann dieser Unterschied verringert aber nicht aufgehoben werden, wie die gemessenen Werte mit eingeschaltetem Festplattencache zeigen. 1 Werte für Schreiben + Commit 7.5. BERKELEY DB 129 • Beim Selektieren kann HADES die Parallelisierbarkeit und den schnelleren Zugriff auf die Daten nutzen und so je nach eingesetzter Anzahl von Servern mehr als eine Größenordnung schneller die Anfrage bearbeiten. 7.5 Berkeley DB Berkeley DB [Mic99] weist eine ähnliche Programmierschnittstelle wie HADES auf. Wie HADES auch, kümmert sich Berkeley DB nicht um das Format des Inhalts der gespeicherten Daten sondern überläßt die Interpretation der gespeicherten Daten dem Anwendungsprogramm. Anders als in HADES werden Daten jedoch über frei wählbare Schlüssel adressiert, während HADES fortlaufende Seitennummern verwendet. Aufgrund der Ähnlichkeit ist es interessant, die Leistungsdaten von HADES mit denen von Berkeley-DB zu vergleichen. Zur Messung wurde der gleiche Rechner wie bei der Messung von PostgreSQL verwendet: Lesen Einfügen Selektieren Berkeley DB 2 0,76ms ca. 51ms 0,4 Mio. Einträge/Sekunde HADES 0,2-0,6ms 0,9-1,6ms3 8-38 Mio. Einträge/Sekunde Da Berkeley DB keine Selektion auf den Daten anbietet, mußte die Selektion durch Auslesen der kompletten Tabelle und Vergleichen im Anwenderprogramm realisiert werden. Wie bei PostgreSQL war es notwendig den Festplattencache auszuschalten, um sicherstellen zu können, daß Log-Daten auch wirklich geschrieben werden und nicht nur im Cache abgelegt werden, was zu einem Datenverlust im Falle eines Stromausfalls führen könnte. Ohne ausgeschalteten Festplattencache erhält man im Durchschnitt für das Einfügen von Daten eine Zeit von ca. 1,2ms, dies liegt unter der durchschnittlichen Latenz der Platte, so daß davon ausgegangen werden muß, daß die Daten zu diesem Zeitpunkt noch nicht dauerhaft geschrieben wurden. Wie schon im Vergleich mit PostgreSQL, bietet HADES auch im Vergleich mit Berkeley DB große Geschwindigkeitsvorteile beim Einfügen von Daten. 2 3 Mit den Einstellungen Pagesize = 1024 Bytes, Cachesize = 200 MBytes Werte für Schreiben + Commit 130 7.6 KAPITEL 7. LEISTUNGSMESSUNG Zusammenfassung Insgesamt haben die Messungen die Erwartungen an HADES erfüllt. HADES erreicht beim Schreiben Zugriffszeiten je nach Datengröße um 0, 5ms und liegt damit etwa eine Größenordnung unter der Zugriffszeit von sehr schnellen Festplatten. Der Vergleich mit PostgreSQL bestätigt diesen Geschwindigkeitsvorteil gegenüber normalen Datenbanken. HADES kann die parallele Rechenleistung der Server im Cluster nutzen und damit Selektionen sehr schnell durchführen. Die nutzbare Rechenleistung skaliert dabei recht gut mit der Anzahl der Server im Cluster. Abort und Commit skalieren jedoch weniger gut mit der Anzahl der Server und es ist abzusehen, daß Abort und Commit bei einer großen Zahl von Servern im Cluster zum Flaschenhals werden. Bei den bis zu 9 im Cluster eingesetzten Server ist dies jedoch kein Problem, so daß dies für den vorgesehenen Einsatzzweck als Datenbank für eventbasierte Systeme kein Hindernis darstellt. Kapitel 8 Vergleich mit anderen Systemen Es gibt viele Systeme und Lösungen, Daten zu speichern. Ein paar davon scheinen für die vorgestellten Anwendungen geeignet zu sein oder sie sehen HADES auf den ersten Blick sehr ähnlich, aber in den Details bestehen wichtige Unterschiede, aufgrund derer sie für die Anwendungen, für die HADES entwickelt wurde, ungeeignet sind. Die Kombination aus fehlertoleranter, persistenter Speicherung und niedriger Zugriffszeit wird von keinem anderen System erreicht. In diesem Kapitel m öchte ich detailliert auf die Unterschiede zu den anderen Systemen und L ösungen eingehen. 8.1 Bestehende Datenbanksysteme In der aktuellen Ausbaustufe stellt HADES einen Datenbank-Kern zur Verf ügung, der nur grundlegende Funktionen beherrscht. HADES unterst ützt derzeit noch keine Indizes oder Datenbanksprachen wie z.B. SQL. Dieses sind jedoch keine grunds ätzlichen Einschränkungen, sondern HADES kann um diese Eigenschaften erweitert werden. Dieses war jedoch nicht Ziel dieser Arbeit, da sie f ür den vorgesehenen Einsatzzweck nicht notwendig sind, sondern der Schwerpunkt lag darauf, schnelle Transaktionen zu erreichen und eine hohe Verfügbarkeit zur Verfügung stellen zu können. Die Vergleiche mit bestehenden Datenbanksystemen beziehen sich deshalb auf diesen Schwerpunkt. 131 132 8.1.1 KAPITEL 8. VERGLEICH MIT ANDEREN SYSTEMEN Konventionelle Datenbanksysteme Konventionelle Datenbanken speichern Daten auf Festplatte. Dies hat zun ächst den Vorteil, daß Daten durch die magnetische Speicherung unabh ängig von der Stromversorgung erhalten bleiben. Dieses ist derzeit die verbreiteteste M öglichkeit Daten dauerhaft zu speichern. Die Zugriffszeit für Daten, die auf Festplatte gespeichert sind, liegt jedoch um mehrere Größenordnungen höher als die Zugriffszeit für Daten im Hauptspeicher. Daher setzen Datenbanksysteme verschiedene Optimierungen ein, die helfen können, die Zugriffszeit zu minimieren bzw. die Zahl der n ötigen Festplattenzugriffe zu reduzieren. Die Zugriffszeit ist nicht für alle Zugriffe gleich hoch (siehe auch Anhang A.1). Sie hängt davon ab, wie die Daten auf der Festplatte verteilt sind: Auf benachbarte Daten kann schneller zugegriffen werden als auf Daten, die weit über die Platte verstreut sind. Datenbanksysteme versuchen daher die Daten auf Festplatte m öglichst günstig anzuordnen, um eine niedrige Zugriffszeit zu erreichen. Eine größere Ersparnis wird jedoch erreicht, wenn komplett auf Festplattenzugriffe verzichtet werden kann. Dies ist z.B. der Fall, wenn sie bereits vorher von anderen Operationen benötigt wurden und noch nicht wieder aus dem Speicher gelöscht wurden. Aktuelle Datenbanksysteme versuchen daher möglichst viele Daten im Hauptspeicher zu halten, und falls dies nicht für alle Daten möglich ist, versuchen sie die Daten im Hauptspeicher zu halten, die vermutlich in nächster Zeit benötigt werden. Es gibt hierfür verschiedene Ersetzungsstrategien wie z.B. least recently used, first in first out, most recently used oder auch random, die meistens versuchen, aus der bisherigen Verwendung der Daten die Wahrscheinlichkeit abzuleiten, mit der Daten wieder benötigt werden, um so die Daten im Hauptspeicher zu halten, die am wahrscheinlichsten wieder benötigt werden. Wurden Daten im Hauptspeicher verändert, so werden die Daten auf Festplatte gespeichert, bevor neue Daten in diesen Bereich geladen werden. Bei verschiedenen Operationen (z.B. Table-Scan) ist es m öglich das Zugriffsmuster auf die Daten vorherzusagen und Daten vorab in den Hauptspeicher zu laden (prefetching). Dies hat zwei Vorteile: Die Daten sind bereits verf ügbar, sobald sie benötigt werden, so daß keine Wartezeiten auftreten, und es besteht die Möglichkeit die Reihenfolge zu optimieren, in der die Daten eingelesen werden, so daß weniger Kopfbewegungen anfallen [RG00, S.211-213]. Auch beim Schreiben von Daten sind Optimierungen möglich: Veränderte Daten brauchen nicht sofort zurückgeschrieben werden. Falls die Daten erneut verändert werden, so reicht es aus, die letzte Änderung zurückzuschreiben, und man kann damit die dazwischenliegenden Zugriffe einsparen. Zus ätzlich können Schreibzugriffe gesammelt werden, so daß dann die Möglichkeit besteht, durch Umsortieren und Zusammenfassen von Schreibzugriffen die Zugriffszeit zu verringern. 8.1. BESTEHENDE DATENBANKSYSTEME 133 Je mehr Hauptspeicher zur Verfügung steht, desto stärker können diese Optimierungen eingesetzt werden, so daß sich durch den Einsatz von zus ätzlichem Hauptspeicher die Geschwindigkeit steigern läßt. Im Extremfall führt das dann dazu, daß eine Kopie der kompletten Datenbank im Hauptspeicher gehalten wird. Die beschriebenen Optimierungen sind jedoch nicht für das Logging anwendbar. Ein Commit kann erst dann abgeschlossen werden, wenn garantiert werden kann, daß die Änderungen dauerhaft gespeichert sind. Dies ist jedoch erst dann der Fall, wenn die dazugehörenden Log-Einträge auf Festplatte geschrieben wurden [GR93, S.557f]. Nur dann können nach einem Ausfall die Daten rekonstruiert werden. Auf diese Weise bestimmt die Zugriffszeit auf die Festplatte die minimale Zeit, die f ür ein Commit benötigt wird. Im Vergleich zu HADES ergeben sich so zwei unterschiedliche Einsatzgebiete: • Werden sehr große Datenmengen benötigt oder spielt die Geschwindigkeit keine allzu große Rolle, so spricht dies für den Einsatz von festplattenbasierten Datenbanken. Festplattenspeicher ist sehr preiswert und beim Einsatz geeigneter Speichersysteme kann nahezu eine beliebige Datenmenge gespeichert werden. • Werden schnelle Transaktionen benötigt, so hat HADES Vorteile, da es nicht durch die langsame Zugriffszeit einer Festplatte limitiert wird. 8.1.2 Main-Memory-Datenbanksysteme Festplattenbasierte Datenbanksysteme (Disk Resident Databases - DRDB) setzen Datenstrukturen und Methoden ein, die Zugriffe auf Festplatten optimieren. Hierf ür wird versucht die Anzahl der Festplattenzugriffe zu minimieren und Zugriffe in eine günstige Reihenfolge zu bringen. Der hierfür aufgebrachte Rechenaufwand lohnt sich in diesem Fall, da die Festplattenzugriffszeit um mehrere Gr ößenordnungen über der für die Optimierungen benötigten Rechenzeit liegt. Aber auch wenn die Datenbank komplett in den Hauptspeicher paßt, fällt der Overhead für die Optimierung an: Die Indexstrukturen (z.B. B-Bäume) sind für den Einsatz mit Festplatten optimiert, Zugriffe auf Daten müssen über den Puffer-Manager ausgeführt werden, usw. Main-Memory-Datenbanksysteme (MMDB) setzen dagegen komplett auf die Speicherung der Daten im Hauptspeicher und verwenden hierfür optimierte Datenstrukturen und Methoden. Auf diese Weise kann schneller auf Daten zugegriffen werden, wie das folgende Beispiel zeigt: 134 KAPITEL 8. VERGLEICH MIT ANDEREN SYSTEMEN Bei einem normalen Datenbanksystem muß jedes mal, wenn eine Applikation auf ein Tupel zugreifen möchte, die Adresse auf der Festplatte ermittelt werden und der Puffer-Manager aufgerufen werden, um zu prüfen, ob der dazu gehörende Block bereits im Hauptspeicher liegt. Nachdem der Block gefunden wurde wird das Tupel aus dem Block in einen Puffer der Applikation kopiert, wo es dann gelesen werden kann. Dieser Aufwand fällt immer an, also auch, wenn die komplette Datenbank im Cache gehalten werden kann. Im Falle einer MMDB ist dagegen immer gewährleistet, daß das Tupel im Hauptspeicher liegt, und es kann direkt die Adresse des Tupels im Speicher verwendet werden [GMS92]. Daten im Hauptspeicher sind flüchtig, bei einem Stromausfall oder Systemabsturz gehen sie verloren. Um die Daten nach einem Ausfall wiederherstellen zu k önnen, muß ein Backup existieren sowie ein Transaktions-Log, das die Änderungen seit dem letzten Backup enthält. Das Backup und das Transaktions-Log müssen dabei auf nichtflüchtigem Speicher abgelegt werden, damit diese Informationen bei einem Stromausfall oder Crash nicht verloren gehen. Werden hierfür Festplatten eingesetzt, so resultiert daraus aber wieder eine deutliche Verschlechterung der Geschwindigkeit, da jede Transaktion auf mindestens eine Schreiboperation warten muß [GMS92]. Somit kann das Logging zum Flaschenhals für die Leistungsfähigkeit werden. [CKKS89] schlägt dazu vor, Log-Daten in einem Batteriegepufferten Speicherbereich abzulegen, der sowohl intern im Rechner vorhanden oder auch extern angebunden sein kann, um so Zugriffszeiten auf Festplatten vermeiden zu k önnen. Die darin enthaltenen Daten werden im Hintergrund auf Festplatte geschrieben, so daß nur eine geringe Speichergröße nötig ist. [CKKS89] untersucht die Vorteile der externen Lösung, die im wesentlichen der Verwendung von einer Solid-State-Disk entspricht, wie sie im Anhang A.3 vorgestellt werden. Allerdings liegen die Kosten von SolidState-Disks sehr weit über den Zusatzkosten von 70$, die vom Autor für das Jahr 1993 vorhergesagt wurden. Pre-committing und Group-Commit ([DKO+ 84]) sind zwei weitere Optimierungsmöglichkeiten: Pre-committing gibt alle Locks frei, sobald der Log-Record ins Log gestellt wurde, ohne abzuwarten, bis die Informationen auf Festplatte geschrieben wurden. Auch wenn dadurch nicht die Antwortzeit der betreffenden Transaktion verringert wird, da vor der Antwort das Schreiben auf Platte abgewartet werden muß, wird dadurch jedoch die Zeit reduziert, die andere Transaktionen blockiert werden. Group-Commit faßt mehrere Log-Records mehrerer Commits unterschiedlicher Transaktionen zusammen und schreibt sie zusammen in einer Operation in die Logdatei. Dadurch werden weniger I/O-Operationen benötigt, was zu einem höheren Durchsatz führt, aber die Antwortzeit verlängert. 8.1. BESTEHENDE DATENBANKSYSTEME 135 Auch für Main-Memory-Datenbanksysteme begrenzen also Zugriffe auf die Festplatten zum Schreiben der Log-Information die erreichbaren Antwortzeiten und MainMemory-Datenbanksysteme alleine ermöglichen noch nicht besonders niedrige Antwortzeiten. Der zusätzliche Einsatz von Solid-State Disks kann die Antwortzeiten verbessern, jedoch liegen die Zusatzkosten sehr hoch. Im Unterschied dazu bietet HADES auch ohne den Einsatz teurer Spezialhardware niedrige Antwortzeiten. HADES erreicht dies durch die Nutzung von Redundanz in einem Cluster, wobei es aber nicht nur die Redundanz des Clusters verwendet, sondern auch die parallele Rechenleistung des Clusters zur Beschleunigung von aufwendigen Operationen nutzen kann. 8.1.3 PRISMA/DB PRISMA/DB [AvdBF+ 92] ist ein paralleles Main-Memory-Datenbanksystem. Es verwendet dabei paralleles Logging [AD85] auf Festplatten. Somit unterliegt PRISMA/DB den gleichen Einschränkungen wie andere Main-Memory-Datenbanksysteme auch: Antwortzeiten für Transaktionen werden von Festplatten limitiert. Darüberhinaus gibt es aber weitere interessante Unterschiede aber auch Gemeinsamkeiten zwischen PRISMA/DB und HADES: Sowohl PRISMA/DB als auch HADES laufen auf einem Cluster von Rechnern. Während HADES dies nutzt, um durch Redundanz die Datensicherheit zu gew ährleisten und die Verfügbarkeit zu verbessern, und die Parallelisierung nur an zweiter Stelle stand, kennt PRISMA/DB keine Redundanz. Hier dient der Cluster nur der Beschleunigung der Berechnung. Beide verwenden eine horizontale Fragmentierung der Daten, um die Daten im System zu verteilen. Unterschiede gibt es jedoch in der Zuteilung von Fragmenten an Prozessoren. Während PRISMA/DB eine freie Zuteilung vorsieht, bei der die Anwendung die Zuteilung festlegen kann, erlaubt HADES keine freie Zuteilung. Daf ür teilt es die Fragmente (Slices) jeweils zwei Servern zu und kann somit garantieren, daß die Daten auch beim Ausfall eines Servers erhalten bleiben. Abgesehen von diesem kleinen Unterschied können aber die gleichen Algorithmen zur Bearbeitung (z.B. Join) der fragmentierten Daten verwendet werden. Betrachtet man die Verfügbarkeit bestehen allerdings größere Unterschiede: Fällt ein Rechner aus, so gehen bei PRISMA/DB die auf diesem Rechner gespeicherten Daten verloren und müssen aus dem Backup und dem dazugehörenden Log rekonstruiert werden. Während dieser Phase kann nicht auf die betroffenen Daten 136 KAPITEL 8. VERGLEICH MIT ANDEREN SYSTEMEN zugegriffen werden. Im Unterschied dazu speichert HADES Daten redundant. F ällt ein Rechner aus, so kann, wenn auch mit Leistungseinbußen, weiterhin ohne Unterbrechung auf die Daten zugegriffen werden, während im Hintergrund die Redundanz wiederhergestellt wird. 8.1.4 TimesTen TimesTen ist ein Real-Time Event Processing System, das es Applikationen erlaubt, Informationen zu erfassen, zu speichern und zu verteilen [Tim, Tim03]. TimesTen basiert auf In-Memory-Datenbanktechnologie und speichert Daten ebenso wie HADES im Hauptspeicher, um schnelle Antwortzeiten zu erreichen. Ebenso wie HADES unterstützt TimesTen Replikation von Daten, um die Verfügbarkeit zu erhöhen. Allerdings unterscheiden sich HADES und TimesTen sehr deutlich darin, wie dies geschieht. TimesTen unterstützt drei Arten von Replikation: Hot Standby: Es gibt einen Master-Data-Store, der alle Änderungen auf einen oder mehrere Subscriber-Data-Store kopiert, der dann als Hot-Standby fungiert. Änderungen an Daten können nur auf dem Master durchgeführt werden. Split Workload: Während bei Hot-Standby nur ein Data-Store als Master für alle Tabellen fungiert, werden in dieser Konfiguration für verschiedene Tabellen verschiedene Data-Stores als Master verwendet, wobei aber pro Tabelle nur ein Master existiert. Wie bei Hot-Standby auch, werden Änderungen nur auf dem Master durchgeführt. Distributed Workload: In diesem Fall können Änderungen auf beliebigen Data-Stores durchgeführt werden, aber die Applikation muß die Operationen so aufteilen, daß keine Kollisionen bei der Replikation auftreten, d.h. das gleiche Datum darf nicht gleichzeitig in verschiedenen Data-Stores modifiziert werden. Für den Fall, daß dies trotzdem passiert, stellt TimesTen ein zeitstempelbasiertes Verfahren zur Erkennung von Kollisionen bereit. Während HADES Fail-Over und Replikation vollständig transparent für den Client durchführt, ist dies in TimesTen nicht der Fall: Die Applikation muß sich selbst um die Ausfallerkennung und das Fail-Over kümmern. Wird Distributed Workload verwendet, um die Zugriffe über alle Data-Stores verteilen zu können, so muß die Applikation sicherstellen, daß keine Kollisionen auftreten. Damit ist es nicht m öglich, daß zwei unterschiedliche Applikationen, die unabhängig voneinander arbeiten, gleichzeitig auf Daten zugreifen, da hierbei die Kollisionsfreiheit der Zugriffe nicht garantiert 8.2. DISTRIBUTED HASH-TABLES 137 werden kann. In HADES spielt dies aufgrund der vollständigen Transparenz der Replikation keine Rolle. Auch in der Art, wie Transaktionen behandelt werden gibt es Unterschiede: TimesTen verwendet Logging, um Transaktionen zu implementieren und stellt 3 LoggingModi zur Verfügung: Logging disabled: In diesem Modus wird kein Logging unterstützt. Dies hat zur Folge, daß kein Rollback für Transaktionen zur Verfügung steht. Ein Ausfall führt zum Verlust der Daten. Deshalb ist dieser Modus nur f ür Operationen vorgesehen, die von Anfang an neu gestartet werden können wie z.B. BulkLoading von Daten. Diskless logging: Dieser Modus loggt Daten nur in den Transaction-Log-Buffer im Hauptspeicher, die Einträge werden jedoch nicht auf Platte geschrieben. Damit läßt sich eine hohe Geschwindigkeit erreichen, jedoch gehen Transaktionen bei einem Ausfall verloren. Disk-based logging: Hierbei werden Log-Einträge als Teil des Commit auf Platte geschrieben, wobei allerdings auch die Antwortzeit absinkt. Nur im Modus Disk-based logging kann TimesTen garantieren, daß keine Transaktionen bei einem Ausfall verloren gehen. In diesem Modus wird die Antwortzeit aber durch die Geschwindigkeit der Platte limitiert und kann daher nicht die Antwortzeiten von HADES erreichen, das ohne Festplattenzugriffe auskommt. 8.2 Distributed Hash-Tables Distributed Hash-Tables setzen auf dem Peer-to-Peer Gedanken auf und versuchen Daten verteilt und fehlertolerant im WAN zu speichern. Es gibt verschiedene Arbeiten auf dem Gebiet wie z.B.: Chord [SMK+ 01], Pastry [RD01], Tapestry [ZKJ01] oder CAN [RFH+ 01]. Der Schwerpunkt dieser Arbeiten liegt im Routing, das notwendig ist, um auf die Daten zuzugreifen. Typische Routen-L ängen liegen im Bereich 1 O(log N ) oder O(N d ). Am Beispiel von Chord möchte ich die Unterschiede zu HADES aufzeigen. Chord ordnet jedem Knoten und Schlüssel im System eine m-Bit ID zu, die mit einer Hash-Funktion wie z.B. SHA-1 aus der Knoten-ID (z.B. IP-Adresse) oder aus dem Schlüssel generiert wird. Die m-Bit Adressen von Knoten und Schl üsseln werden wie in Abbildung 8.1 auf einem Ring angeordnet. Daten werden auf dem Knoten 138 KAPITEL 8. VERGLEICH MIT ANDEREN SYSTEMEN 6 1 0 successor(6)=0 successor(1)=1 1 7 successor(2)=3 2 6 3 5 2 4 Abbildung 8.1: Chord-Ring 8.2. DISTRIBUTED HASH-TABLES 139 mit der gleichen Adresse oder der Adresse, die im Ring im Uhrzeigersinn nachfolgt gespeichert. Chord bezeichnet diese Knoten als successor node. Abbildung 8.1 zeigt ein Beispiel für ein Netz aus 3 Knoten mit den Knotenadressen 0, 1 und 3 sowie den drei Schlüsseln 1, 2 und 6. Für die Schlüssel wird jeweils der Nachfolger bestimmt und die Daten an der erhaltenen Adresse gespeichert. Im Beispiel sind das Knoten 1, 3 und 0. Um den Nachfolger im Ring schneller bestimmen zu k önnen, speichert jeder Knoten O(log N ) Adressen von Nachfolgern im Abstand von 20 , 21 , 22 , . . . im Ring ab. Zusammen mit dieser Information ist die Anzahl der Knoten, die in einem Netz von N Knoten kontaktiert werden muß, um Daten zu lokalisieren, mit hoher Wahrscheinlichkeit in O(log N ). Aus diesem Aufbau ergeben sich einige Unterschiede zu HADES. HADES konzentriert sich auf eine schnelle Zugriffszeit im LAN und ben ötigt für einen Zugriff im Normalfall höchstens 4 Nachrichten (Client → Primärserver → Sekundärserver → Primärserver → Client) und nur nach einem Ausfall eines Rechners werden mehr Nachrichten benötigt, bis eine neue Servertabelle an alle Rechner verteilt ist. Dagegen ist das Routing in Chord für das WAN mit einer großen Zahl von Rechnern optimiert: Die Rechner benötigen keine globale Sicht auf das Speichernetzwerk für das Routing, dafür werden aber für einen Zugriff O(log N ) Nachrichten benötigt. Auf diese Weise wird die Skalierbarkeit auf Kosten der Zugriffszeit verbessert. Auch in der Lastverteilung bestehen deutliche Unterschiede. In Chord erfolgt die Lastverteilung probabilistisch: Durch die Hashfunktion werden die Knoten pseudozufällig im Ring verteilt. Im Durchschnitt ergibt sich für eine große Anzahl von Servern eine gleichmäßige Verteilung der Daten, im Einzelfall kann es aber auch dazu führen, daß auf manchen Rechnern keine Daten gespeichert werden. Aus diesem Grund führt Chord virtuelle Knoten ein, d.h. ein Rechner registriert sich unter mehreren Adressen auf dem Ring, um die Last gleichmäßiger zu verteilen. Eine probabilistische Lastverteilung funktioniert aber schlecht f ür eine kleine Anzahl von Servern, da sie u.U. auch zu einer sehr ungleichmäßigen Verteilung führen kann. In HADES wird die Last deshalb explizit von einem Koordinator gleichm äßig verteilt, so daß dieses Problem in HADES nicht besteht. HADES speichert alle Daten auf zwei Rechnern. Dagegen speichert Chord Daten auf bis zu r Knoten ab, um bei Rechnerausfällen einen Datenverlust zu verhindern. Chord kann auf diese Weise den Ausfall von mehr als einem Rechner maskieren, was aufgrund der größeren Rechneranzahl im Chord aber auch notwendig ist. HADES kann dafür bessere Garantien für die Datenkonsistenz bieten: Schreibzugriffe sind in HADES atomar und HADES bietet zusätzlich Transaktionen. Beides wird von Chord nicht unterstützt. 140 8.3 KAPITEL 8. VERGLEICH MIT ANDEREN SYSTEMEN LH* Schema – Scalable Distributed Data Structure Das LH* Schema ist als Verallgemeinerung aus dem Linearen Hashing (LH) hervorgegangen und erweitert dieses zu einer skalierbaren verteilten Datenstruktur (Scalable Distributed Data Structure – SDDS) [LNS96]. Lineares Hashing z ählt zu den dynamischen Hashverfahren, die im Gegensatz zu statischen Hashverfahren keine Hashtabelle konstanter Größe voraussetzen, sondern die Größe der Hashtabelle durch wechselnde Hashfunktionen variieren können, um sich an stark wachsende oder schrumpfende Datenbestände anzupassen und gleichzeitig eine gute Effizienz zu wahren [OW90, S. 216][ED88]. Wie bei statische Hashverfahren auch, werden die Daten in Datenblöcken (Buckets) gespeichert, die einen oder mehrere Einträge aufnehmen können. Der Bucket-Faktor gibt dabei an, wie viele Einträge in einen Datenblock passen. Lineares Hashing verwendet hierfür mehrere Hashfunktionen hi . Die aktuelle Größe der Hashtabelle legt fest, welche der Hashfunktionen für welche Bereiche der Hashtabelle verwendet werden, wobei das Lineare Hashing so ausgelegt ist, daß nie mehr als zwei Hashfunktionen (hi und hi+1 ) gleichzeitig benötigt werden. Wird die Größe der Hashtabelle vergrößert, so ändert sich die verwendete Hashfunktion für einen Datenblock. Die darin befindlichen Daten müssen dann gemäß der neuen Hashfunktion neu in die Hashtabelle eingefügt werden. Beim Linearen Hashing verteilen sich die in einem Datenblock enthaltenen Daten dabei auf zwei Datenbl öcke. Dieses wird als Split bezeichnet. Beim Schrumpfen der Hashtabelle wird dies wieder r ückgängig gemacht und Datenblöcke werden wieder zusammengemischt. Die Entscheidung, ob Datenblöcke gesplittet oder gemischt werden, wird über die Belegung der Hashtabelle gesteuert. Steigt die Belegung beim Einfügen von Daten zu stark an, so wird sie durch splitten wieder gesenkt, sinkt sie beim Löschen von Daten zu stark ab, wird sie durch mischen wieder angehoben. Auf dieser Basis setzt LH* auf. Es verwendet Objekt-IDs (OID) als Schl üssel für das Hashing, um damit Objekte (Daten) innerhalb der Hashtabelle zu adressieren. Die Datenblöcke der Hashtabelle, in denen die Daten gespeichert werden, werden dabei über mehrere Rechner verteilt. Im einfachsten Fall gibt es pro Datenblock einen eigenen Rechner. Die Speicherung der Daten erfolgt jeweils im RAM, so daß eine niedrige Zugriffszeit erreicht werden kann. Splits werden wie beim Linearen Hashing durchgeführt, nur daß hier ein Split dazu führt, daß ein Teil der Daten auf einem neuen Rechner gespeichert wird. Die Buckets werden mit 0,1,. . . durchnumeriert, wobei die Nummer die BucketAdresse darstellt, und diese Adressen werden auf statisch oder dynamisch zugeordnete Rechneradressen abgebildet. Möchte ein Client Daten adressieren, so berechnet er mit den zuletzt verwendeten Hashing-Parametern (verwendete Hashing-Funktionen 8.3. LH* SCHEMA – SCALABLE DISTRIBUTED DATA STRUCTURE 141 hi und Größe der Hashtabelle) die Bucket-Adresse und sendet die Anfrage an den dazugehörenden Rechner. Es ist dabei nicht garantiert, daß es sich bei diesem Rechner bereits um den richtigen Rechner handelt. Falls der Client über veraltete Parameter verfügt, so berechnet er die falsche Adresse, es tritt ein Adressierungsfehler auf. Der Rechner überprüft deshalb die Adresse mit den eigenen Parametern und leitet die Anfrage ggf. an den korrekten Rechner weiter. In [LNS96] wird gezeigt, daß spätestens nach zwei Weiterleitungen der korrekte Rechner gefunden wird. In den folgenden Abschnitten möchte ich auf die Eigenschaften der unterschiedlichen Varianten des LH*-Schemas eingehen. 8.3.1 LH* Das ursprüngliche LH*-Schema fungiert als reiner verteilter Speicher. Es stellt weder Transaktionen noch Redundanz zur Verfügung. Fällt ein Rechner aus, so sind die auf diesem Rechner gespeicherten Daten verloren. Bei den Eigenschaften der Adressierung gibt es Unterschiede: W ährend sich die Adressierung in HADES nur ändert, wenn Server ausfallen oder hinzukommen, ändert sich im LH*-Schema die Adressierung auch dann, wenn beim Einf ügen ein Bucket-Split auftritt oder beim Löschen zwei Buckets wieder vereinigt werden. Durch Bucket-Splits entstehen so nicht nur Adressierungsfehler, sondern es m üssen auch im normalen Betrieb Daten über das Netzwerk umkopiert werden. Dieses ergibt eine signifikante Belastung des Netzwerks, wie folgende Rechnung belegt: Sollen n Datensätze gespeichert werden, so benötigt dies bei einem Bucket-Faktor von s und einem Belegungsfaktor β insgesamt b = n/(βs) Buckets. Damit b Buckets aus dem einen bei einer leeren Hashtabelle vorhandenen Bucket entstehen, sind b − 1 BucketSplits erforderlich. Bei einem Bucket-Split werden durchschnittlich die H älfte aller in einem Bucket enthaltenen Datensätze kopiert, das sind βs/2. Damit müssen insgesamt n βs n − βs βs =( − 1) = k = (b − 1) 2 βs 2 2 Datensätze kopiert werden. Nimmt man an, daß der Bucket-Faktor s klein gegen über der Anzahl n der Datensätze ist, so ergibt sich: k≈ n 2 Im Durchschnitt wird also jeder zweite Datensatz bei einem Bucket-Split einmal umkopiert. Das heißt, daß durch Bucket-Splits die benötigte Netzwerk-Gesamtbandbreite im Durchschnitt um 50% steigt. Dieses ist unabh ängig vom Bucket-Faktor und unabhängig vom Belegungsfaktor. 142 KAPITEL 8. VERGLEICH MIT ANDEREN SYSTEMEN Auch bei der Lastverteilung gibt es Unterschiede: Während HADES Daten sofort über mehrere Rechner verteilt, so füllt das LH*-Schema zunächst einmal das Bucket auf dem ersten Server auf. Erst nach einem Bucket-Split werden Daten auf einem zweiten Server gespeichert. Hierbei ist der Bucket-Faktor, der festlegt, wie viele Datensätze in ein Bucket passen, eine kritische Größe. Wählt man ihn zu groß, so wird die Last sehr unterschiedlich verteilt und bei großen Datens ätzen gibt es u.U. Probleme mit den verfügbaren Ressourcen auf dem Server. Wählt man ihn zu klein, so treten häufig Bucket-Splits und damit auch Adressierungsfehler auf. 8.3.2 LH*m Fällt bei LH* ein Rechner aus, so gehen die auf diesem Rechner gespeicherten Daten verloren. Das Risiko hierfür steigt mit der Anzahl der eingesetzten Rechner. Somit ist LH* nicht geeignet, um Daten dauerhaft zu speichern. LH*m (Mirroring, [LN96]) führt aus diesem Grund wie HADES Mirroring ein, das jeweils zus ätzlich eine Kopie speichert, so daß auch beim Ausfall eines Rechners Daten nicht verloren gehen. Die weitere Vorgehensweise ist dann aber komplett anders: LH*m teilt die Server F2 Clients F1 Clients F1 F2 Abbildung 8.2: LH*m Schema in zwei Sites F1 und F2 ein. F2 ist dabei eine Kopie von F1 und umgekehrt. Zur Lastverteilung werden Clients ebenso in zwei Gruppen eingeteilt. Die erste Gruppe hat Server in F1 als primäre Server und Server in F2 als sekundäre Server. Clients in dieser Gruppe werden als F1-Clients bezeichnet. Clients in der zweiten Gruppe verwenden Server in F2 als primäre und Server in F1 als sekundäre Server und werden als F2-Clients bezeichnet (Abbildung 8.2). F1-Clients greifen dabei auf F1 Server zu und nur in Ausnahmefällen, falls ein Server in F1 nicht erreichbar ist, greifen sie auf Server in F2 zu. Analog gilt das gleiche für F2-Clients: Sie greifen 8.3. LH* SCHEMA – SCALABLE DISTRIBUTED DATA STRUCTURE 143 normalerweise nur auf Server in F2 zu und nur im Fehlerfall auf Server in F1. Auf diese Weise wird eine Lastverteilung zwischen den Servern in F1 und F2 erreicht. Werden Daten auf einem Server geändert, so wird die Änderung auf den jeweils anderen Server propagiert. Dabei gibt es jedoch die Möglichkeit, daß Inkonsistenzen auftreten. Möchten z.B. ein F1 und ein F2 Client gleichzeitig Daten mit der gleichen OID schreiben, so kann es unter ungünstigen zeitlichen Bedingungen auftreten, daß die beiden Mirrors unterschiedliche Daten speichern. Dieser Fall tritt dann auf, wenn sich die Nachrichten zur Herstellung der Kopie auf dem jeweils anderen Mirror kreuzen. Abbildung 8.3 zeigt den Fall, daß Client 1 den Wert 10 schreibt w ährend Client 2 gleichzeitig den Wert 42 auf das gleiche Objekt mit der OID 0 schreibt (Notation: 0,10 bzw. 0,42). Die Nachrichten zur Erzeugung der Kopie kreuzen sich und überschreiben jeweils den vorher gespeicherten Wert. Am Ende sind zur OID 0 auf den beiden Mirrors zwei verschiedene Werte gespeichert. Nachfolgende LesezuClient 1 Server 1 0,10 0,10 0,42 Server 2 Client 2 0,42 0,42 0,10 Abbildung 8.3: Beispiel für Race-Condition im LH*m Schema griffe liefern unterschiedliche Werte, je nachdem ob ein F1-Client oder ein F2-Client den Lesezugriff ausführt. Somit ist LH*m nicht zur sicheren Speicherung von Daten geeignet. Dieser Effekt kann in HADES nicht auftreten, da Clients immer auf den gleichen Server schreiben, solange die Seitenadresse identisch ist. Somit k önnen sich keine zwei Nachrichten kreuzen, die dann zu dieser Dateninkonsistenz f ühren könnten. Die Einteilung in zwei Sites F1 und F2, die bei LH*m zur Lastverteilung verwendet wird, ist in HADES nicht notwendig. Lastverteilung wird dadurch erreicht, daß Server beides sind: sowohl Primärserver und Sekundärserver. Welche Rolle sie aktuell einnehmen wird darüber festgelegt, auf welches Slice zugegriffen wird. 8.3.3 LH*s LH*m kopiert Daten auf einen zweiten Server und benötigt so gegenüber LH* den doppelten Speicher. LH*s (Striping, [LNL+ 97]) verwendet zur Verringerung des 144 KAPITEL 8. VERGLEICH MIT ANDEREN SYSTEMEN R 53 011011001110.............0101110 Parity− segment LH*s client s1 s4 s2 53 0001..... 53 1101..... s3 53 1110..... 53 0010..... Abbildung 8.4: Schreiben von Daten für k = 3 Segmente Speicherbedarfs eine Prüfsumme statt einer kompletten Kopie. Datensätze bestehen wie bei LH* aus einem Schlüssel c und den eigentlichen Daten, die als Bitfolge aufgefaßt werden können: b1 . . . bk bk+1 . . . b2k b2k+1 . . . bmk . Die Daten umfassen somit mk Bits und werden gegebenenfalls bis zu diesem Wert aufgef üllt. Möchte ein Client Daten speichern, so führt er folgende Schritte aus (Abbildung 8.4): • Er erzeugt k > 1 Segmente. Segment si besteht dabei aus dem Schlüssel c und den Bits s0i : s‘i = bi bk+i b2k+i . . . • Der Client erzeugt das Parity-Segment sk+1 , das ebenfalls aus dem Schlüssel c besteht und zusätzlich die Parity-Bits s0k+i enthält: s0k+1 = s01 ⊕ s02 ⊕ . . . ⊕ s‘k • Die so entstandenen Daten werden in eine Familie von k + 1 LH*-Files geschrieben, so daß die Daten auf unterschiedlichen Rechnern liegen. Solange nur ein Rechner ausfällt, können die Daten aus der Prüfsumme rekonstruiert werden. Auf diese Weise beträgt der zusätzliche Speicherbedarf gegenüber LH* nur 1/k (plus evtl. benötigte Füll-Bits). Allerdings gibt es auch hier die Möglichkeit von Race-Conditions, wenn zwei Clients gleichzeitig Daten schreiben. Zum Beispiel: • Zwei Clients schreiben unterschiedliche Daten mit der gleichen OID. Dazu teilen sie jeweils die Daten in k=3 Segmente auf und senden die 3 Segmente und das Parity-Segment an 4 Server. 8.3. LH* SCHEMA – SCALABLE DISTRIBUTED DATA STRUCTURE 145 • Es gibt keine Garantie darüber, in welcher Reihenfolge die Nachrichten empfangen werden, deshalb kann es passieren, daß für die Segmente 1 und 2 die Nachricht vom 1. Client zuletzt empfangen wird und für die anderen Segmente die Nachrichten von Client 2. • Segmente 1 und 2 enthalten somit die Daten von Client 1, während Segment 3 und das Parity-Segment von Client 2 stammen. Werden nun Daten gelesen, so wird eine Mixtur der Daten von Client 1 und 2 gelesen und eine möglicherweise stattfindende Überprüfung der Prüfsumme schlägt fehl. Fällt ein Rechner aus, so werden falsche Daten rekonstruiert, wobei der Fehler unerkannt bleibt. Somit ist auch LH*s nicht zur sicheren Speicherung von Daten geeignet. 8.3.4 LH*g und LH*RS LH*g (Record-Grouping [LR02]) verwendet wie LH*s eine Prüfsumme, um Datenverluste bei einem Rechnerausfall zu verhindern. Im Unterschied zu LH*s wird die Prüfsumme jedoch nicht innerhalb eines Eintrags berechnet, sondern mehrere Einträge werden zu einer Gruppe zusammengefaßt und innerhalb dieser Gruppe wird die Prüfsumme berechnet und als zusätzlicher Eintrag in die Gruppe eingetragen. Die einzelnen Einträge einer Gruppe werden auf verschiedenen Rechnern gespeichert. Die Einträge enthalten die folgenden Daten: Daten-Record: Schlüssel (OID), Gruppen-ID, Daten Parity-Record: Guppen-ID, Schlüssel 1, Schlüssel 2, .... Schlüssel n, Parity-Daten Die Gruppen-ID wird beim ersten Einfügen eines Datensatzes bestimmt und bleibt danach auch bei Änderungen am Eintrag erhalten. Werden Daten geschrieben, so berechnet der Primärserver, welche Änderungen an den Parity-Daten vorgenommen werden müssen und schickt diese Änderungen an den Parity-Server. Fällt ein Server aus, so können die Daten auf diesem Rechner mit Hilfe des Parity-Eintrags wieder rekonstruiert werden. Allerdings kann es auch hierbei zu Problemen kommen: Wird während ein Rechner ausgefallen ist, ein Datensatz geschrieben und liest ein anderer Client Daten, so kann es passieren, daß der bereits veränderte Datensatz aber noch der unveränderte Parity-Datensatz gelesen wird. Aus diesen Daten läßt sich der ursprüngliche Datensatz nicht rekonstruieren, stattdessen werden unbemerkt falsche Daten rekonstruiert (Abbildung 8.5). Somit kann auch LH*g keine sichere Speicherung von Daten garantieren. 146 KAPITEL 8. VERGLEICH MIT ANDEREN SYSTEMEN Datensatz−Format key group data Parity−Datensatz−Format group key 1 key n parity k1 g1 0001 k2 g1 0100 k3 g1 0010 g1 k1 k2 k3 0111 k1 g1 1000 k2 g1 0100 k3 g1 0010 g1 k1 k2 k3 0111 Rekonstruktion des Datensatzes k2 upd ate k1 g1 100 k2 g1 1101 1 k1 g1 1000 k2 g1 0100 k3 g1 0010 g1 k1 k2 k3 1110 Abbildung 8.5: Beispiel für eine Race-Condition im LH*g Schema LH*RS [LS00] verwendet das gleiche Schema wie LH*g, allerdings ersetzt es die einfache Parity-Prüfsumme durch Reed-Solomon Codes. Reed-Solomon Codes sind ursprünglich fehlerkorrigierende Codes, die hier aber nicht zur Fehlererkennung und Fehlerkorrektur von vorhandenen Daten eingesetzt werden, sondern nur zur Rekonstruktion von nach einem Ausfall nicht mehr verfügbaren Daten. Dies ist ein einfacherer Fall, da bereits bekannt ist, welche Rechner nicht erreichbar sind und somit welche Daten fehlen. Dadurch können im Vergleich zur Fehlererkennung und Korrektur doppelt soviele Ausfälle rekonstruiert werden. Je nachdem wie viele Bits für die Fehlerkorrektur zur Verfügung stehen, kann durch den Einsatz von ReedSolomon Codes mehr als ein Ausfall aufgefangen werden, was die Zuverl ässigkeit steigert. Da LH*RS aber auf LH*g basiert ist es für die gleiche Race-Condition anfällig: Werden während eines Ausfalls Daten geschrieben und liest ein Client zwar bereits die neu geschriebenen Daten aber noch teilweise veraltete Fehlerkorrektur-Bits des Reed-Solomon Codes, so können wie bei LH*g falsche Daten rekonstruiert werden. 8.4. DISTRIBUTED SHARED MEMORY 8.4 147 Distributed Shared Memory Es gibt zwei Typen von Parallelrechnern: Parallelrechner mit eng gekoppeltem gemeinsamen Speicher (Shared-Memory) und Parallelrechner mit verteiltem Speicher (Distributed-Memory). Erstere sind einfacher zu programmieren, da sie eine einfache Erweiterung von Rechnern mit einer CPU darstellen. Allerdings wird der Zugriff auf den gemeinsam genutzten Speicher schnell zum Flaschenhals, der eine weitere Skalierung verhindert und die mögliche Zahl der eingesetzten Prozessoren limitiert. Parallelrechner mit verteiltem Speicher besitzen diesen Nachteil nicht: Sie bestehen aus einzelnen unabhängigen Rechnern, die über ein schnelles Netzwerk verbunden sind. Jeder Rechner besitzt eigenen Speicher und braucht die Speicherbandbreite nicht mit anderen Rechnern zu teilen. Allerdings kann jeder Rechner nur auf den eigenen Speicher zugreifen, Daten, die auf einem anderen Rechner ben ötigt werden, müssen über das Netzwerk transportiert werden (Message-Passing), was besonders für komplizierte Datenstrukturen nicht einfach zu realisieren ist, da diese zur Übertragung serialisiert und in Nachrichten verpackt werden müssen. Distributed Shared Memory [NL91] (DSM) wird in diesen Systemen verwendet, um einen gemeinsamen Speicher zu emulieren und so die Programmentwicklung zu erleichtern. Auf diese Weise möchte man die gute Skalierbarkeit von DistributedMemory Systemen mit der der einfacheren Programmierbarkeit von Shared-Memory Systemen kombinieren. Auf den ersten Blick scheint es große Gemeinsamkeiten zwischen DSM und HADES zu geben: Beide speichern Daten verteilt in einem Cluster, beide verwenden Replikation und beide müssen die Konsistenz von Daten gewähren. In den Details gibt es jedoch große Unterschiede. Um diese zu beleuchten, möchte ich zuerst einen kurzen Überblick über DSM geben. 8.4.1 Shared Virtual Memory Es gibt ein großes Spektrum unterschiedlicher Lösungen für Shared-Memory Systemen, diese reichen von Hardware-Implementierungen über Realisierungen auf Basis von virtuellem Speicher zu Compiler-Lösungen, die Zugriffe automatisch in Nachrichtenbasierte Kommunikation umwandeln. Unterschiede gibt es auch in der zur Verfügung gestellten Datenkonsistenz, sie reicht von Strict consistency, die garantiert, daß Lesezugriffe immer den zuletzt geschriebenen Wert zur ückliefern, bis zu der schwächeren Release consistency, die Eingriffe durch den Programmierer erfordert. 148 KAPITEL 8. VERGLEICH MIT ANDEREN SYSTEMEN CPU 1 CPU 2 CPU N Memory 1 Memory 2 Memory N Mapping Manager Mapping Manager Mapping Manager Shared Virtual Memory Abbildung 8.6: Shared Virtual Memory Da eine Beschreibung aller unterschiedlichen Varianten zu umfangreich w äre und für das Verständnis auch nicht erforderlich ist, möchte ich mich auf die genauere Beschreibung einer Variante beschränken: Shared Virtual Memory [LH89]. Shared Virtual Memory stellt einen gemeinsamen Adreßraum zur Verf ügung. Jeder Prozessor kann direkt auf jede Adresse zugreifen. Memory Mapping Managers übernehmen dabei das Mapping zwischen lokalem Speicher und dem Shared Virtual Memory (Abbildung 8.6). Dazu kann wie bei virtuellem Speicher die MMU der CPU verwendet werden, die es erlaubt Zugriffsrechte für Speicherseiten festzulegen und bei deren Verletzung einen Page-Fault auszulösen. Virtueller Speicher verwendet diesen Mechanismus, um beim Auftreten eines PageFaults ausgelagerte Speicherseiten wieder in den Speicher zu laden und dem Prozeß zur Verfügung zu stellen. Shared Virtual Speicher verwendet diesen Mechanismus darüberhinaus, um Zugriffsrechte auf Speicherseiten anzupassen und Daten mit anderen Rechnerknoten abzugleichen. Dabei muß das Memory Coherence Problem gelöst werden: Ein Lesezugriff liefert immer den Wert, der beim letzten Schreibzugriff auf die gleiche Adresse geschrieben wurde. Eine Lösung hierfür ist Invalidation. Falls beim Schreiben von Prozeß P auf Seite s ein Zugriffsfehler auftritt, so führt der Page-Fault-Handler folgende Operationen durch: • Alle Kopien von s werden invalidiert (d.h. die Zugriffsrechte werden entzogen) 8.4. DISTRIBUTED SHARED MEMORY 149 • Die Zugriffsrechte für s werden auf schreiben“ gesetzt. ” • Falls P noch keine Kopie von s besitzt, so wird diese angelegt. • Die Ausführung von P wird mit der Operation fortgesetzt, die den Fehler ausgelöst hat. Danach besitzt“ Prozeß P die Seite s und kann Schreib- und Lesezugriffe durch” führen, bis der Besitz an einen anderen Prozeß abgegeben wird. Tritt beim Lesen ein Zugriffsfehler auf, so werden folgende Operationen durchgef ührt: • Die Zugriffsrechte des Prozesses mit Schreibrechten für s werden auf lesen“ ” gesetzt. • Es wird eine Kopie von s angelegt und P erhält Leserechte. • Die Ausführung wird fortgesetzt. Ein (zentraler oder verteilter) Manager verwaltet dabei die Information, welche Prozesse über Kopien einer Seite verfügen und welcher Prozeß Besitzer einer Seite ist, damit eine Seite lokalisiert werden kann und die Zugriffsrechte der anderen Prozesse entsprechend angepaßt werden können. Vergleicht man dieses Vorgehen mit HADES, so werden größere Unterschiede deutlich: HADES versucht Daten zur Lastverteilung gleichmäßig über alle Rechner zu verteilen. Dies ist bei DSM nicht der Fall. Daten bewegen sich zu Prozessen, die auf diese Daten zugreifen wollen. Die Verteilung wird implizit durch die Berechnung vorgegeben und kann sich während der Berechnung sehr stark ändern. DSM legt Kopien von Daten an, um Lesezugriffe beschleunigen zu k önnen. Lesezugriffe auf die lokale Kopie sind um Größenordnungen schneller als das Versenden von Nachrichten, um die benötigten Daten anzufordern. Sobald jedoch Schreibzugriffe stattfinden, werden alle anderen Kopien verworfen und es gibt nur noch ein Origi” nal“. Somit ist keine durchgehende redundante Speicherung gew ährleistet und bei einem Ausfall können Daten verloren gehen. Im Unterschied dazu dient Replikation von Daten in HADES nicht zur Geschwindigkeitssteigerung sondern zur Fehlertoleranz. Es gibt immer eine zusätzliche Kopie, damit bei einem Ausfall des Originals keine Daten verloren gehen. 150 KAPITEL 8. VERGLEICH MIT ANDEREN SYSTEMEN 8.4.2 Fehlertolerantes DSM Fällt in einem DSM ein Rechner aus, so gehen Daten verloren. Damit sind alle (Zwischen-) Ergebnisse einer u.U. sehr aufwendigen Berechnung verloren. Aus diesem Grund gibt es Bestrebungen DSM mit Fehlertoleranz auszustatten. Das in [Ker97] beschriebene recoverable DSM (RDSM) legt dazu in regelmäßigen Abständen Recovery-Points an. Ein Recovery-Point stellt in diesem System eine konsistente Momentaufnahme des Speichers dar, die zwar ebenfalls im Hauptspeicher abgelegt wird, aber redundant auf mehreren Rechnern liegt. Die Momentaufnahme wird in einem Two-Phase-Commit-Protocol-ähnlichen Protokoll angefertigt: Im ersten Schritt wird die Berechnung unterbrochen und eine Kopie angefertigt. Dabei wird jede Seite auf zwei unterschiedlichen Rechnern gespeichert. Nachdem dieser Schritt erfolgreich durchgeführt wurde wird in einem zweiten Schritt der vorangegangene Recovery-Point freigegeben. Das Anlegen der Momentaufnahme geschieht inkrementell: zur Optimierung werden nur die Seiten kopiert, die seit dem letzten Recovery-Point ver ändert wurden. Die anderen Seiten können direkt vom vorangegangenen Recovery-Point übernommen werden und brauchen nicht erneut kopiert zu werden. Fällt ein Rechner aus, so kann zum letzten Recovery-Point zurückgegangen werden und die Berechnung ab diesem Punkt wiederholt werden. Damit ist RDSM zwar ähnlich zu HADES, aber die so erreichte Datensicherheit ist noch nicht f ür Transaktionen ausreichend. Änderungen, die seit dem letzten Recovery-Point vorgenommen wurden, gehen bei einem Ausfall verloren. Um dieses zu verhindern, m üßte als Teil des Commits jeweils ein neuer Recovery-Point angelegt werden. Hierf ür ist RDSM aber nicht ausgelegt: • Während ein Recovery-Point angelegt wird, werden alle Berechnungen angehalten. • Das Kopieren findet auf Basis von Speicherseiten der MMU statt. Dies nimmt keine Rücksicht auf die darin gespeicherten Daten. Liegt ein Datensatz auf der Grenze zwischen zwei Seiten und wird verändert, so müssen beide Seiten kopiert werden. Für den Pentium 4 liegt die typische Größe dieser Seiten bei 4KBytes. Gegenüber kleinen Datensätzen muß damit ein Vielfaches kopiert werden. • Daten werden unabhängig von der Transaktion repliziert: Es werden immer alle Daten kopiert. 8.5. CHECKPOINTING MIT PARITY 151 Der Letzte Punkt sorgt dafür, daß ein Abort nicht einfach zum letzten RecoveryPoint zurückkehren kann, da dieses auch alle anderen Änderungen, die von anderen Transaktionen durchgeführt wurden, rückgängig machen würde. Insgesamt fehlen RDSM also im Vergleich zu HADES wichtige Datenbank Eigenschaften und diese lassen sich auch nicht mit geringem Aufwand nachr üsten. 8.5 Checkpointing mit Parity [PL94] stellt ein Verfahren vor, das in regelmäßigen Abständen Checkpoints anlegt, um auf diese Weise die Berechnungen beim Ausfall eines Knotens beginnend mit diesem Checkpoint fortsetzen zu können und nicht alle Zwischenergebnisse zu verlieren. Ein Checkpoint entspricht einem Recovery-Point in RDSM. Damit unterliegt Checkpointing mit Parity den gleichen Einschränkungen wie RDSM und ist genausowenig für die vorgesehenen Anwendungsgebiete geeignet. 8.6 Distributed Reliable Cache LND [MZWZ02] verwendet einen verteilten, zuverlässigen Cache, um Schreibzugriffe beschleunigen zu können. Das System besteht aus folgenden Komponenten (Abbildung 8.7): LND Client: Der Client, der den Cache nutzen möchte. LND Data-Server: Dieser Rechner stellt Cache zur Verfügung und Plattenplatz, der eine Kopie der Client-Daten aufnimmt. LND Cache-Server: Diese Rechner stellen Hauptspeicher als Cache f ür LND zur Verfügung. Sollen Daten vom Client geschrieben werden, so werden keine synchronen Schreibzugriffe auf die Festplatte des Clients ausgeführt, sondern die Daten werden zuerst in den verteilten, zuverlässigen Schreibcache geschrieben und danach asynchron auf die lokale Festplatte geschrieben. Der verteilte zuverlässige Cache spiegelt diese gecachten Daten auf allen Rechnern, die an LND teilnehmen. Während der Cache einer einzelnen Maschine flüchtig ist und bei einem Ausfall verloren geht, geht der verteilte Cache nur dann verloren, wenn alle Maschinen gleichzeitig ausfallen. LND unterscheidet zwei Konsistenz-Protokolle: 152 KAPITEL 8. VERGLEICH MIT ANDEREN SYSTEMEN Application Distributed Reliable Cache User Space Remote Memory Remote Memory Remote Memory LND Daemon LND Daemon LND Daemon NIC Driver NIC Driver NIC Driver Kernel Space Buffer File Cache System LND Driver NIC Driver async IO async IO LAN Local Remote Disk Disk LND Client Cache Server Cache Server Data Server Abbildung 8.7: LND-Architektur Strict Consistency Protocol (SCP): Der Schreibzugriff wird als abgeschlossen signalisiert, wenn alle Cache-Server eine Bestätigung gesendet haben Loose Consistency Protocol (LCP): Der Schreibzugriff wird als abgeschlossen signalisiert, wenn irgendeiner der Server eine Bestätigung gesendet hat. Während die Zuverlässigkeit von SCP höher ist, da Daten nur dann verloren gehen, wenn alle Server ausfallen, hat LCP den Vorteil, daß die Mitgliedschaft aller Server im System nicht verwaltet werden muß und daß weniger Wiederholungen der Übertragung nötig sind. Fällt der Client oder der Data-Server aus, so kann der Ausfallzeitpunkt nicht exakt bestimmt werden, und die gespeicherten Daten müssen neu abgeglichen werden. Um den Aufwand hierfür zu limitieren, unterteilt LND die Schreibzugriffe chronologisch in Phasen. Am Ende einer Phase schreiben sowohl Client als auch Data-Server alle veränderten Daten zurück auf Festplatte. Auf diese Weise kann die Phase bestimmt werden, in der ein Ausfall eingetreten ist, und damit kann ermittelt werden, welche Daten bereits geschrieben wurden und welche Daten noch nicht geschrieben wurden. Die Dauer der Phase beeinflußt die Performance sowohl des Betriebs als auch die der Wiederherstellung der Daten. Kurze Phasen erfordern ein h äufiges Leeren der Caches, was die Antwortzeit erhöhen kann, während lange Phasen große Puffer erfordern und zu einer langen Recovery-Zeit führen. 8.7. MIDDLEWARE MEDIATED TRANSACTIONS UND X2 TS Ausfall Cache-Server Data-Server Daten sicher ja ja Client arbeitet ja ja Alle Server / Netzwerkfehler Client ja ja ja nein Client Platte ja nein Alle Rechner nein nein 153 Performance Recovery-Zeit kein Einfluß kein Einfluß 0 Transferzeit für verlorene Phasen Transferzeit für verlorene Phasen Transferzeit einer Phase Transferzeit aller Daten nicht möglich Rückgang auf Plattengeschwindigkeit System nicht verfügbar System nicht verfügbar System nicht verfügbar Tabelle 8.1: Zuverlässigkeit und Verfügbarkeit des LND-Systems Die Tabelle 8.1 gibt die Zuverlässigkeit und Verfügbarkeit für die verschiedenen Möglichkeiten eines Ausfalls an. Im Unterschied zu HADES bietet diese Architektur keine ununterbrochene Verfügbarkeit. Mit Ausnahme des Ausfalls eines CacheServers wird immer Zeit für das Recovery benötigt, das nach einem Ausfall die Daten zwischen Client und Data-Server abgleichen muß. Besonders nach einem Ausfall der Client-Platte tritt ein längerer Ausfall auf, da in diesem Fall die kompletten Daten vom Data-Server zum Client kopiert werden müssen. Es ist nicht vorgesehen, mehrere Clients parallel betreiben zu k önnen. Damit wird der einzige Client zum Single-Point-of-Failure. Eine schnelle Übernahme durch einen anderen Client ist nicht möglich, da in diesem Fall ebenfalls die Daten komplett neu vom Data-Server rekonstruiert und auf die Platte des neuen Clients übertragen werden müßten. 8.7 Middleware Mediated Transactions und X2 TS X2 TS integriert verteilte Objekt-Transaktionen mit Publish/Subscribe Systemen. Der Fokus von X2 TS liegt darin, verschiedene Möglichkeiten zur Verfügung zu stellen, wie verschiedene Operationen und Teiltransaktionen zu einer verteilten Gesamttransaktion verbunden werden können und stellt dabei Methoden zur Verfügung, die über einfache Transaktionen und Nested-Transactions hinaus gehen ([LMB00], [LT01]). 154 KAPITEL 8. VERGLEICH MIT ANDEREN SYSTEMEN Microsoft TerraServer AlphaServer 4100 4 x 400 MHz 2048 MB 1100 MB/s 320 SCSI Festplatten 320 x 25 MHz 320 x 1 MB 320 x 10 MB/s 1600 MIPS 2048 MB 1100 MB/s 3125 MIPS 320 MB 3200 MB/s Compaq ProLiant TPC-C 4 Pentium Pro 4 x 200 MHz 4096 MB 540 MB/s 113 SCSI Festplatten 113 x 25 MHz 113 x 1 MB 113 x 10 MB/s 800 MIPS 4096 MB 540 MB/s 2800 MIPS 113 MB 1130 MB/s Tabelle 8.2: Zwei Beispielsysteme mit hohen Speicherbedarf [RG97] X2 TS selbst garantiert aber keine persistente Speicherung. Hierf ür greift es auf externe Datenbanksysteme zurück, wenn Daten auch noch nach einem Rechnerausfall zur Verfügung stehen müssen. Damit wird die Leistungsfähigkeit durch die Leistungsfähigkeit des Datenbanksystems begrenzt. Auf diese Weise stehen HADES und X2 TS in keiner Konkurrenz zueinander, sondern HADES kann in X2 TS als schnelles, hochverfügbares Datenbanksystem verwendet werden, um die Leistung zu steigern. 8.8 Active Disks Active Disks versuchen brachliegende Rechenleistung von Festplattencontrollern zu nutzen [Rie99]. Während die Rechenleistung einer einzelnen Festplatte zwar mehrere Generationen hinter der Rechenleistung einer aktuellen Workstation hinterherhinkt, kann in größeren Installationen die akkumulierte Rechenleistung eines Speichersystems die Rechenleistung des Rechners übertreffen (Tabelle 8.2, [RG97]). Active Disks versuchen diese Rechenleistung nutzbar zu machen, indem Daten bereits von der Festplatte vorverarbeitet werden. Dazu wird die Firmware der Festplatte erweitert, um neben dem Lesen und Schreiben zusätzliche Operationen zur Verfügung stellen zu können. Der Download von ausführbarem Code ermöglicht es beliebige Operationen in der Festplatte ablaufen zu lassen und somit z.B. beim Filtern von Daten die Speicherschnittstelle zu entlasten, indem nur Daten übertragen werden brauchen, die dem Filterkriterium entsprechen. Active Disks bieten somit zwar keine Vorteile für die Zugriffszeit, da sie auf der gleichen Speichertechnologie wie Festplatten basieren, aber beim Einsatz von Active 8.9. ISIS TOOLKIT 155 Disks zur Datenverarbeitung entstehen ähnliche Probleme wie in HADES: Daten liegen nicht am Stück“ vor, sondern sind über mehrere Speicherorte (Active Disks) ” verteilt. [Rie99] untersucht Algorithmen für folgende Anwendungsbereiche: Data Mining: Nächster-Nachbar Suche, Frequent Sets, Klassifikation Multimedia: Kantenerkennung, Bildausrichtung Sortierverfahren: Merge Sort, Key Sort, lokale Sortieralgorithmen Datenbank: Select, Aggregation, Join Aufgrund der Ähnlichkeit in der Speicherung von Daten können Erkenntnisse, die bei der Entwicklung dieser Algorithmen gewonnen wurden, zur Integration von weiteren Operationen in HADES verwendet werden. 8.9 ISIS Toolkit Das ISIS Toolkit [BR93, Bir93] stellt Process-Groups zur Verfügung, die zur Verwaltung von Knoten in einem verteilten System sowie zur Synchronisierung und Kommunikation verwendet werden. Damit befindet sich ISIS im gleichen Anwendungsbereich wie die Kommunikationsschicht von HADES, und viele Eigenschaften der in HADES verwendeten Kommunikationsschicht sind ähnlich zu denen von ISIS, allerdings setzen beide deutlich unterschiedliche Schwerpunkte: W ährend ISIS seinen Schwerpunkt im Bereich Gruppen-Multicast und Synchronisierung setzt, konzentriert sich HADES auf niedrige Latenzen und gute Portierbarkeit. In HADES steht z.B. ein spezielle Modus zur Verfügung, in dem die erfolgreiche Übertragung einer Nachricht bereits von der Kommunikationsschicht quittiert wird, ohne daß darauf gewartet werden muß, daß die Anwendung die Anfrage bearbeitet und beantwortet. ISIS unterstützt zwei Arten von Multicast: Strikt synchrone Multicasts (ABCAST), die garantieren, daß die Empfangsreihenfolge aller Multicasts auf allen beteiligten Rechnern des verteilten Systems identisch ist, sowie virtuell synchrone Multicasts (CBCAST), die eine globale Reihenfolge nur zwischen kausal abh ängigen Multicasts zur Verfügung stellen, während die Empfangsreihenfolge zwischen nicht kausal abhängigen Multicasts nicht festgelegt ist. Vorausgesetzt daß der so erreichte Grad an Synchronisation für die Anwendung ausreichend ist, erlauben virtuell synchrone Multicasts einen größeren Anteil an asynchroner Ausführung und helfen, Latenzen zu verringern und die Geschwindigkeit der Anwendung zu steigern. 156 KAPITEL 8. VERGLEICH MIT ANDEREN SYSTEMEN HADES verwendet kein Multicast, die eingesetzten Mechanismen verfolgen aber die gleiche Philosophie wie sie ISIS mit den virtuell synchronen Multicasts verfolgt: Aus Geschwindigkeitsgründen werden Operationen möglichst asynchron ausgeführt und nur dann eine Synchronisierung erzwungen, wenn dies für die korrekte Durchführung nötig ist. ISIS basiert nicht auf TCP, sondern setzt ein komplett eigenes Kommunikationssystem auf Kernel-Ebene ein und muß dazu weite Teile der Fehlerkorrektur von TCP nachbauen. Während dies Vorteile bei der Nutzung von Multicast bietet, das von HADES nicht unterstützt wird, so hat dies gleich mehrere Nachteile: Es kann nicht von Implementierungsverbesserungen des sehr weit verbreiteten TCPProtokolls profitieren, und vor allem bringt die Implementierung auf Kernel-Ebene deutliche Nachteile für die Portierbarkeit auf neue Systeme mit sich. Die Kommunikationsschicht von HADES läuft komplett im Anwenderbereich des Betriebssystems und kennt dieses Problem deshalb nicht. HADES kann daher ohne Änderungen sowohl unter Linux als auch unter Solaris betrieben werden und auch auf weiteren Unix-ähnlichen Betriebssystemen sollte es direkt oder mit nur geringen Änderungen einsetzbar sein. 8.10 Zusammenfassung In diesem Kapitel habe ich die Unterschiede zu anderen bestehenden L ösungen aufgezeigt. Keine der anderen Lösungen ist für die Anwendungsgebiete geeignet, für die HADES entwickelt wurde: Die Kombination aus fehlertoleranter Speicherung und niedriger Zugriffszeit wird von keiner der anderen Lösungen erreicht. Kapitel 9 Zusammenfassung und Ausblick In diesem Kapitel möchte ich die Anforderungen und Ergebnisse dieser Arbeit zusammenfassen und einen Ausblick auf weitere Arbeiten und Verbessungsm öglichkeiten geben. Darüberhinaus möchte ich einen Ausblick auf das Gebiet Sensornetzwerke geben, für das die HADES-Mechanismen sehr interessant sein könnten, um den Energieverbrauch zu senken und damit die Lebensdauer der Batterien und damit des Gesamtnetzes zu erhöhen. 9.1 Anforderungen und Ergebnisse Das Beispiel der eventbasierten Systeme sowie das Beispiel der IP- Übernahmemechanismen aus Kapitel 1 zeigen, daß es Anwendungen gibt, bei denen die Zugriffszeit eine dominierende Rolle spielt. Diese wird bei aktuellen Systemen von der Festplattenzugriffszeit dominiert. Wie Anhang A zeigt, sind RAID-Mechanismen, die zur Verbesserung der Zuverlässigkeit und Bandbreite eingesetzt werden, nicht geeignet die Zugriffszeit zu verbessern und auch Solid-State Disks sind nur bedingt eine Alternative, wenn es um einen bezahlbaren Festplattenersatz f ür den Einsatz in Datenbanksystemen geht. HADES verzichtet aus diesem Grund auf Festplatten zur persistenten Speicherung der Daten und setzt stattdessen eine redundante Speicherung in einem verteilten System bzw. Cluster ein. Alle Daten werden auf mindestens zwei Rechnern gespeichert, so daß bei einem Ausfall eines Rechners keine Daten verloren gehen. 157 158 KAPITEL 9. ZUSAMMENFASSUNG UND AUSBLICK Um dieses zu realisieren, waren verschiedene Probleme zu l ösen: • Verwaltung des Clusters, Erkennung von ausgefallenen Rechnern • Zugriff auf Daten im Cluster • Verteilung der Daten im Cluster • Datenkonsistenz • Transaktionen zur Unterstützung mehrerer Clients • Fehlertoleranz • Sicherstellen der Redundanz und Wiederherstellung der Redundanz nach einem Ausfall HADES setzt dazu zwei Schichten ein. Eine eigene Kommunikationsschicht (Kapitel 4) dient als Basis für die Fehlertoleranz. Sie verwaltet die Rechner im Cluster und überwacht sowohl die Rechner als auch die Kommunikation zwischen Rechnern. Sie stellt eine konsistente Sicht zur Verfügung, welche Rechner aktiv sind und welche als ausgefallen betrachtet werden. Der Datenbank-Kern, der in Kapitel 6 vorgestellt wurde, bildet die zweite Schicht. Daten werden in zwei Stufen adressiert. Die erste Stufe bildet Seitenadressen auf eine konstante Anzahl von Slice-Adressen ab, die in der zweiten Stufe auf die aktuell im Cluster vorhandenen Rechner verteilt werden. Dabei wird darauf geachtet, daß alle Daten jeweils auf mindestens zwei Rechnern gespeichert werden, so daß bei einem Ausfall keine Daten verloren gehen. Nach einem Ausfall wird diese Redundanz wieder hergestellt und danach kann ein weiterer Rechner ausfallen, ohne daß ein Datenverlust auftritt. HADES stellt nicht nur einfache Lese- und Schreiboperationen zur Verf ügung, sondern mit der Operation zum Selektieren und Sortieren steht auch eine komplexe Operation zur Verfügung, die die parallele Rechenleistung des Clusters ausnutzen kann. Analog zu dieser Operation können weitere komplexe Operationen ergänzt werden. Durch die Unterstützung von Transaktionen ist es möglich, die gleichzeitige Operation mehrerer Clients zu koordinieren. Verteilte Transaktionen erlauben es, Daten sicher von einer Datenbank zur anderen weiterzureichen, wie dies f ür eventbasierte Systeme benötigt wird. 9.2. WEITERE ARBEITEN UND VERBESSERUNGEN Bandbreite (max) Latenz (kurze Nachrichten) SCI 326 MBytes/s 2,7 µs Myrinet 246 MByte/s 6,7 µs 159 Infiniband 822 MBytes/s 7,6 µs Tabelle 9.1: Leistungsdaten von SCI und Myrinet (Herstellerangaben) Die Leistungsmessungen, die in Kapitel 7 präsentiert wurden, belegen, daß HADES die gesteckten Ziele erreicht und die Zugriffszeit im Vergleich zu Festplatten etwa um eine Größenordnung niedriger ausfällt. Gleichzeitig zeigen die Ergebnisse für das Selektieren, daß komplexe Operationen die parallele Rechenleistung des Clusters effizient nutzen können. Auch bei einem Ausfall eines Rechners sinkt die Leistung von HADES nur wenig, und ein Einsatz in Systemen, die eine hohe Verf ügbarkeit erfordern, wird dadurch möglich. 9.2 Weitere Arbeiten und Verbesserungen Auch wenn HADES bereits sehr gute Ergebnisse erreichen konnte, bleibt noch Raum für weitere Leistungssteigerungen: HADES verwendet als zugrundeliegende Netzwerktechnologie Ethernet, da dieses ohnehin vorhanden war, um die Rechner zu vernetzen. Ethernet ist zwar weit verbreitet, jedoch gibt es mit SCI (www.dolphinics. com), Myrinet (www.myrinet.com) und Infiniband (www.infinibandta.com) andere Lösungen, die deutlich niedrigere Latenzzeiten zur Verfügung stellen können (Tabelle 9.1). Eine Verwendung dieser Technologien in HADES verspricht eine weitere deutliche Leistungssteigerung. Aber auch auf Softwareebene sind noch Erweiterungen möglich. HADES stellt derzeit keine Datenbanksprache zur Verfügung. Eine Unterstützung von SQL zum Beispiel könnte somit den Einsatz von HADES anstelle von anderen Datenbanksystemen vereinfachen und die Unterstützung von Indizes könnte den gezielten Zugriff auf einzelne Datensätze beschleunigen. 9.2.1 Sensornetze Die in HADES eingesetzten Mechanismen sind aber auch in einem ganz anderen Einsatzgebiet interessant: Sensornetze [ASSC02]. Sensoren k önnen immer kleiner gebaut werden und zusammen mit Prozessoren, Speicher, drahtlosen Kommunikationsmitteln und Energieversorgung in Sensorknoten integriert werden, bis sie schließlich so 160 KAPITEL 9. ZUSAMMENFASSUNG UND AUSBLICK klein und billig sind, daß sie in großer Zahl zur Datenerfassung eingesetzt bzw. sogar aus einem Flugzeug verstreut werden können [ASSC02, EGHK99]. Anwendungsgebiete für diese Art von Sensoren werden z.B. in der Medizin, Sicherheits überwachung, Automation oder auch in der Erfassung von Umweltdaten gesehen. Durch die große Zahl der Sensoren können nicht einfach nur mehr Meßwerte erfaßt werden, sondern Sensoren können näher am zu überwachenden Phänomen plaziert werden, wenn vorher nicht bekannt ist, wo das Phänomen auftreten wird (z.B. Erdbeben). Auf diese Weise können genauere Messungen als bei einer größeren Distanz zum nächsten Sensor vorgenommen werden [EGPS01]. Gleichzeitig wird durch die hohe Zahl der Sensorknoten die Robustheit des Gesamtsystems erh öht: Fällt ein Knoten aus, so führt das nicht zu einem Totalausfall des Systems, sondern die verbliebenen Sensoren können weiterhin Meßergebnisse liefern. Sensornetzwerke aus billigen Sensorknoten können damit auch in gefährlichen Gebieten eingesetzt werden, wo mit einer hohen Wahrscheinlichkeit gerechnet werden muß, daß einzelne Knoten zerstört werden (z.B. Überwachung von Vulkanen). Drahtlose Sensornetzwerke benötigen keine bestehende Infrastruktur: Die einzelnen Sensorknoten verfügen über eine eigene Energieversorgung (z.B. Batterien oder Solarzellen) und kommunizieren per Funk. Dies vereinfacht die Installation auch in unwegsamem oder unwirtlichem Gelände. Im Extremfall können die Sensorknoten einfach aus einem Flugzeug abgeworfen werden und so auch sehr abgelegene Gebiete erreicht werden. Bis zu einem großflächigen Einsatz von Sensornetzwerken sind aber noch verschiedene Probleme zu lösen, auch wenn bereits erste Versuche mit kleinen Sensornetzwerken stattgefunden haben1 . Aktuelle Prototypen sind noch aus Standardkomponenten aufgebaut und erreichen deshalb mit ein paar Kubikzentimetern Volumen noch nicht die anvisierte Größe von wenigen Kubikmillimetern, die mit speziellen Komponenten erreicht werden soll. Eines der wichtigsten Probleme ist der Energieverbrauch. Werden die Sensorknoten von Batterien gespeist, so h ängt die Lebensdauer eines Knotens direkt vom Energieverbrauch ab. Aus diesem Grund m üssen sowohl die Algorithmen als auch die Datenübertragung für einen niedrigen Energieverbrauch optimiert werden. Hierbei reicht es nicht aus nur den Energieverbrauch des einzelnen Knotens zu reduzieren, sondern der Energieverbrauch des gesamten Netzes muß minimiert werden. Spezielle Routing-Verfahren wie z.B. Directed Diffusion [IGE00], SMECN [LH01], LEACH [HCB00] oder auch Publish/Subscribe [HGM01] werden verwendet, um die im gesamten Netz benötigte Energie für die Datenübertragung gegenüber Flooding oder einer direkten Verbindung zu jedem Sensorknoten zu verringern. 1 z.B. www.greatduckisland.net 9.2. WEITERE ARBEITEN UND VERBESSERUNGEN 161 Eine der wichtigsten Maßnahmen zur Energieeinsparung ist aber die Aggregation von Daten. Das Auswerten und Zusammenfassen von Daten reduziert die zu übertragende Datenmenge und hilft damit gleichzeitig mit der zur Verf ügung stehenden Bandbreite auszukommen, die nur wenige 100kBit/s beträgt [MR03]. Auch wenn die Datenaggregation zunächst Energie benötigt, überwiegt in der Regel die Einsparung. In [PK00] wird abgeschätzt, daß die gleiche Energie, die benötigt wird, um 1000 Bit 100m weit zu übertragen, ausreicht um 3 Mio. Rechenoperationen durchzuführen. Die Datenaggregation muß nicht auf einen einzelnen Sensorknoten begrenzt bleiben, sondern sie kann auch über mehrere Knoten hinwegreichen. Wird ein Sensornetz z.B. eingesetzt, um die Temperatur aufzuzeichnen, so reicht es u.U. aus für einen ganzen Bereich nur einen Durchschnittswert zu übertragen, wenn die Temperaturunterschiede innerhalb dieses Bereiches gering sind. Gleichzeitig stellt die Aggregation aber auch besondere Anforderungen an die Fehlertoleranz. Damit Daten aggregiert werden können, müssen sie bis zur Aggregation im Netz gespeichert werden. Dies verlängert die Zeit, die sich Daten im Sensornetz befinden. Mit dieser Zeit steigt aber auch das Risiko, daß sie aufgrund eines Ausfalls des Sensorknotens, auf dem sie gespeichert sind, verloren gehen. Gleichzeitig repräsentieren Aggregate potentiell sehr viele einzelne Meßwerte, so daß der Verlust eines einzelnen Aggregats einen großen Verlust darstellt. Je nach Anwendung müssen Aggregate daher besonders vor Verlust geschützt werden. Bei Sensorknoten, die z.B. aus einem Flugzeug verstreut werden, ist ein Einsatz von klassischen Speichermedien wie z.B. Festplatten oder Flash-Speichern sinnlos. Denn selbst wenn ein Sensorknoten nur aufgrund einer leeren Batterie ausgefallen und nicht z.B. bei der Überwachung eines Vulkans geschmolzen ist, so ist es ziemlich aussichtslos jeden einzelnen ausgefallenen Sensorknoten zu suchen, um die gespeicherten Daten zu retten. Aus diesen Gründen ist Redundanz die einzige Möglichkeit, im Netz gespeicherte Informationen vor einem Verlust zu schützen. Aufgrund der besonderen Anforderungen an niedrigen Energieverbrauch und niedrige Hardwareanforderungen müssen natürlich Anpassungen vorgenommen werden: Die Kommunikationsschicht, die derzeit TCP/IP verwendet, muß u.U. an ein anderes Protokoll angepaßt werden und Operationen, die in HADES aus Geschwindigkeitsgründen immer möglichst parallel ausgeführt werden, müssen serialisiert werden, um einen niedrigeren Speicherverbrauch zu erzielen. Da Nachrichten von allen Knoten mitgelesen werden können, ist außerdem zu untersuchen, wie dies zur Reduktion der zu übertragenden Datenmenge verwendet werden kann und ob man z.B. beim Kopieren der Daten vom Primärserver zum Sekundärserver auf ein erneutes Übertragen der Daten verzichten kann und stattdessen die Daten referenzieren kann, die der Sekundärserver mitlesen konnte, als sie vom Client zum Primärserver übertragen wurden. 162 KAPITEL 9. ZUSAMMENFASSUNG UND AUSBLICK Abbildung 9.1: Beispiel-Cluster aus Sensorknoten Sensorcluster mit minimalen Hardwareanforderungen Die Knoten eines Sensornetzes bringen bereits alle wichtige Eigenschaften mit, wie sie für einen Einsatz in einem redundanten Cluster nötig sind, so daß an den Knoten selbst keine Änderungen notwendig sind (Abbildung 9.1): Die Knoten verf ügen über eine eigene Stromversorgung (Batterie) und solange sich alle Sensorknoten jeweils in der Funkreichweite aller anderen Knoten des Clusters befinden ist auch sichergestellt, daß keine Teilung des Netzwerkes auftreten kann. Die Bedingung, daß sich alle Knoten eines Clusters jeweils in der Funkreichweite aller anderen Knoten desselben Clusters befinden, läßt sich im Sensornetz dadurch erreichen, daß dieses mit einem geeigneten Verfahren wie z.B. LEACH [HCB00] in viele kleine Einzelcluster eingeteilt wird. Anhang A Grundlagen von Speichertechnologien In diesem Kapitel möchte ich eine kurze Übersicht über existierende Speichertechnologien und deren Eigenschaften geben. Besonders möchte ich auf die Eignung für die Verwendung in einem DBMS, das niedrige Zugriffszeiten bieten soll, eingehen. A.1 Festplatten Festplatten sind derzeit das verbreiteteste Speichermedium zur Speicherung von Daten, auf die häufig zugegriffen werden muß. Um die Probleme und Grenzen zu verstehen, die bei der Benutzung von Festplatten entstehen, lohnt es sich, die Funktionsweise von Festplatten zu vergegenwärtigen (Abbildung A.1). Daten werden in konzentrischen Datenspuren magnetisch gespeichert. Diese Datenspuren werden in Sektoren jeweils gleicher Größe (typischerweise 512 oder 1024 Bytes) unterteilt. Soll auf Daten zugegriffen werden, so wird zuerst der Schreib-/Lesekopf über die zu lesende Datenspur bewegt. Die Zeit, die hierfür benötigt wird, wird als Positionierzeit (seek time) bezeichnet. Sie ist davon abhängig, wie weit der Kopf dabei bewegt werden muß. Anschließend muß gewartet werden, bis sich der gesuchte Sektor unter dem Kopf hindurchbewegt. Hierfür ist im Durchschnitt eine halbe Umdrehung der Platte nötig. Diese Verzögerung wird als Latenz (auch: rotational delay) bezeichnet. Zusätzlich fällt die Transferzeit für die Übertragung der zu lesenden bzw. schreibenden Daten an. Somit ergibt sich für die Zugriffszeit: Zugrif f szeit = P ositionierzeit + Latenz + T ransf erzeit 163 164 ANHANG A. GRUNDLAGEN VON SPEICHERTECHNOLOGIEN Datenspuren Sektoren .. Schreib−/Lesekopfe Abbildung A.1: Aufbau einer Festplatte Positionierzeit und Latenz sind unabhängig von der Menge der übertragenen Daten und fallen immer an. Tabelle A.1 enthält für diese Zeiten Beispiele aktueller Festplatten. Besonders Zugriffe auf kleine Datenmengen sind hiervon betroffen, da bei diesen Zugriffen die Transferzeit sehr klein im Vergleich zu den anderen beiden Komponenten ausfällt. Dieser Effekt ist selbst bei den schnellsten Festplatten sehr ausgeprägt. Zum Beispiel könnte in der gleichen Zeit von 5, 4ms, die von der schnellsten Platte durchschnittlich für Positionierung und Latenz benötigt wird, etwa 556 Sektoren (von je 512 Bytes), das sind mehr als 1/4 Megabytes, übertragen werden! Erhöht man zum Erreichen von größeren Speichergrößen die Schreibdichte auf der Magnetschicht, so profitiert dadurch automatisch der Datendurchsatz von der Erhöhung, da durch die kompaktere Speicherung nun pro Umdrehung mehr Daten gelesen werden können. Positionierzeit und Latenz werden dadurch aber nicht verbessert. Um die Positionierzeit zu verbessern, muß die Beschleunigung des Kopfes erhöht werden, um die Latenz zu verbessern die Drehzahl. Dieses ist aufgrund der auftretenden Beschleunigungs- und Fliehkräfte nur begrenzt möglich und der Grund, warum für Geschwindigkeit optimierte Festplatten Magnetscheiben mit geringerem Durchmesser einsetzen, um so die Positionierwege verringern und h öhere Rotationsgeschwindigkeiten erreichen zu können. A.2 RAID Für verschiedene Anwendungen reicht die Leistungsfähigkeit und Zuverlässigkeit einer einzelnen Festplatte nicht aus. Anstatt nun die Geschwindigkeit und Zuverl ässigkeit der einzelnen Festplatte zu verbessern, steht hinter RAID die Idee, dieses Ziel durch den Einsatz mehrerer Festplatten zu erreichen, um so kosteng ünstige, massenproduzierte Festplatten nutzen zu können. Je nach Lesart steht RAID für redun- A.2. RAID Maxtor MaXLine (Plus) II Größe 320GB 250GB Drehzahl 5400U/min 7200U/min Datenrate bis 44M B/sec bis 54M B/sec Latenz φ 5, 56ms 4, 17ms Positionierzeit φ < 11ms < 9ms min max 165 IBM Ultrastar 36Z15 36, 7GB 18, 4GB 15.000U/min 36, 6 − 52, 8M B/sec 2, 00ms 4, 2ms 3, 4ms 0, 65ms 8, 9ms 6, 7ms Tabelle A.1: Technische Daten aktueller Festplatten [Max][IBM] dant array of inexpensive disks [PGK88] – Redundante Anordnung von preiswerten Platten – oder redundant array of independent disks – Redundante Anordnung von unabhängigen Platten1 . Es gibt verschiedene Varianten von RAID, diese werden als RAID-Level bezeichnet. Im folgenden möchte ich auf die wichtigsten eingehen und die Zugriffszeit f ür kleine Datenmengen beleuchten. A.2.1 RAID-Level 0 Dieser RAID-Level trägt den Namen eigentlich zu unrecht, da er keine Redundanz kennt. Der Adreßraum wird in gleich große Bereiche (Stripes) unterteilt, wobei die Anzahl der Stripes einem ganzzahligen vielfachen der Festplattenanzahl entspricht. Diese Bereiche werden zyklisch auf die verfügbaren Festplatten verteilt. Dieses als Striping bezeichnete Verfahren steigert den Datendurchsatz, da Daten gleichzeitig von mehreren Festplatten gelesen oder geschrieben werden können. Bei zwei Festplatten und einer Stripe-Größe von einem Sektor wird z.B. jeder 2. Sektor auf die jeweils andere Platte geschrieben. Sollen nun 10 Sektoren gelesen werden, so k önnen nun gleichzeitig jeweils 5 Sektoren von jeder Platte gelesen werden. Auf diese Weise läßt sich die Transferzeit halbieren. Betrachtet man jedoch die Zugriffszeit für kleine Datenmengen, so fallen die Vorteile geringer aus. In diesem Fall dominieren Positionierzeit und Latenz und diese werden 1 Ursprünglich stand das I in RAID für inexpensive (preiswert), da mehrere preiswerte Platten verwendet wurden, um große, teure Platten zu ersetzen. Als später zunehmend teure Festplatten in RAID-Systemen verbaut wurden, um noch größere Datenmengen zu speichern, wurde es in independent (unabhängig) umgedeutet. Mit dem Aufkommen von IDE-RAIDs, die billige IDE Platten statt teurer SCSI-Platten verwenden, ist die ursprüngliche Bezeichnung aber durchaus wieder gerechtfertigt. 166 ANHANG A. GRUNDLAGEN VON SPEICHERTECHNOLOGIEN nicht reduziert. Soll z.B. nur ein Sektor gelesen werden, so wird nur eine Platte angesprochen, und es ist in diesem Fall unerheblich, ob diese nun Teil eines Level 0 RAIDs ist oder nicht. Die Zugriffszeit fällt identisch aus. Die Zugriffszeit für einen einzelnen Zugriff wird nicht reduziert, allerdings können mehrere Zugriffe parallel ausgeführt werden, falls die Daten auf verschiedenen Platten liegen. A.2.2 RAID-Level 1 RAID-Level 1 speichert alle Daten auf zwei Platten. Die zweite Platte ist eine exakte Kopie, ein Spiegel, der ersten Platte, daher wird dieses Verfahren auch als Mirroring bezeichnet. Fällt die erste Platte aus, so kann die zweite deren Aufgabe übernehmen, ohne daß Daten verlorengehen oder eine Unterbrechung auftritt. Beim Schreiben von Daten müssen die gleichen Daten jeweils auf zwei Platten geschrieben werden. Da dies parallel geschehen kann erwächst daraus aber zumindest kein größerer Nachteil gegenüber einer einzelnen Platte. Beim Lesen ergibt sich aber der Vorteil, daß zwei Lesezugriffe auf den beiden Platten, die ja die gleichen Daten enthalten, parallel ausgeführt und die Lesezugriffe jeweils an die Platte gerichtet werden k önnen, die vermutlich die geringere Zugriffszeit bietet. Betrachtet man die Zugriffszeit, so fallen mehrere Effekte auf: Schreibzugriffe sind erst dann abgeschlossen, wenn die Daten auf beide Platten geschrieben wurden. Somit bestimmt die jeweils langsamste Platte die Zugriffszeit [PGK88]. Falls die Platten synchronisiert sind und damit eine identische Kopfposition und relative Position der Sektoren zum Schreib-/Lesekopf aufweisen, ist dies kein Problem, da bei jedem Zugriff sowohl Positionierzeit als auch Latenz identisch ausfallen. Bei unabhängigen Platten ist dies jedoch nicht der Fall. Dies möchte ich am Beispiel der Latenz demonstrieren. Der benötigte Anteil X einer vollen Umdrehung kann als gleichverteilte Zufallsvariable aufgefaßt werden: 0≤X<1 Da X beliebig nahe an 1 liegen kann, möchte ich zur Vereinfachung annehmen: 0≤X≤1 Berechnet man hierfür den Erwartungswert für den Anteil einer Umdrehung erhält man: Z 1 1 E(X) = x dx = 2 0 Betrachtet man hingegen zwei Platten, so gilt für die Latenz: 0 ≤ max(X1 , X2 ) ≤ 1 A.2. RAID 167 Somit erhält man für den Erwartungswert: Z 1Z 1 2 max(x1 , x2 ) dx2 dx1 = E(max(X1 , X2 )) = 3 0 0 Die Latenz steigt somit im Durchschnitt um 33% im Vergleich zu einer einzelnen Platte: 2 4 3 ≈ 1, 33 1 = 3 2 Betrachtet man die Positionierzeit, so erhält man einen ähnlichen Effekt: Die durchschnittliche Entfernung für die Positionierung des Kopfes steigt bei insgesamt n Spuren von n/3 bei einer Platte auf 0, 46n für zwei Platten [BG88]. Dies entspricht einer Erhöhung von ca. 38%. Für lesende Zugriffe ergibt sich ein Vorteil, da der Lesezugriff an die Platte geleitet werden kann, deren Kopf einen geringeren Weg zu der Spur zurücklegen muß, auf der die Daten gespeichert sind. Die mittlere Entfernung für die Positionierung sinkt dadurch von n/3 auf n/5 Spuren [BG88]. Die Entfernung geht zwar nicht linear in die Positionierzeit ein, da der Kopf bei aktuellen Festplatten nicht mit konstanter Geschwindigkeit bewegt wird, gibt aber trotzdem einen groben Richtwert vor. Dieser Vorteil kann aber nicht für die Latenz erreicht werden, da die aktuelle (Winkel-) Position der Platte auf Controllerseite nicht bekannt ist, so daß sie nicht f ür eine Optimierung ausgewertet werden kann. Die Betrachtungen für die Positionierzeit gelten jedoch nur, solange man annehmen kann, daß die Kopfpositionen nicht korreliert sind. Bei einem Schreibzugriff werden jedoch Daten auf beiden Platten an identische Positionen geschrieben. Damit f ällt nach einem Schreibzugriff die gleiche durchschnittliche Positionierzeit wie bei einer einzelnen Platte an. Im allgemeinen Fall mit gemischten Schreib- und Lesezugriffen k önnen sich so je nach Zugriffs-Mix sowohl geringe Vorteile als auch geringe Nachteile gegen über einer einzelnen Platte ergeben, wobei Lesezugriffe beschleunigt und Schreibzugriffe in der Zugriffszeit verlangsamt werden. Unter Vernachlässigung eines konkreten Zugriffsmusters aus Lese- und Schreibzugriffen kann man folgende durchschnittlichen Zugriffszeiten f ür das Lesen und Schreiben abschätzen: ØZugrif f szeitLesen = ØZugrif f szeitSchreiben = 3 × ØP ositionierzeit + ØLatenz + T ransf erzeit 5 1, 38 × ØP ositionierzeit + 1, 33 × ØLatenz +T ransf erzeit 168 ANHANG A. GRUNDLAGEN VON SPEICHERTECHNOLOGIEN A.2.3 RAID-Level 5 RAID-Level 5 faßt mehrere Sektoren zu einer Gruppe zusammen und speichert f ür jede Gruppe einen Parity-Sektor. Jeder Sektor dieser Gruppe und der Parity-Sektor wird jeweils auf einer eigenen Platte gespeichert. Um zu vermeiden, daß der Zugriff auf den Parity-Sektor zum Flaschenhals wird, wird dieser für verschiedene Gruppen auf verschiedene Platten gespeichert [PGK88]. Beim Ausfall einer Platte kann deren Inhalt aus den Parity-Daten rekonstruiert werden. Ähnlich wie bei RAID-Level 0 können Daten parallel auf mehrere Platten geschrieben bzw. von mehreren Platten gelesen werden, um eine höhere Bandbreite zu erreichen. Die erreichbare Bandbreite liegt bei n Platten bei dem n − 1-fachen einer einzelnen Platte, da effektiv eine Platte für Parity-Daten benötigt wird. Betrachtet man aber die Zugriffszeit für kleine Datenmengen, so sieht die Sache nicht mehr so günstig aus. Beim Lesen eines Sektors, wird nur auf eine Platte zugegriffen, so daß hier die gleiche Zugriffszeit wie bei einer einzelnen Platte anf ällt. Genauso wie bei RAID-Level 0 können jedoch mehrere Zugriffe parallel ausgeführt werden, um die Zeit für mehrere Zugriffe zu reduzieren. Da Daten jeweils nur auf einer Platte direkt vorliegen, können die Optimierungen, die bei RAID-Level 1 möglich sind, hier nicht angewendet werden. Kleine Schreibzugriffe erfordern bei RAID-Level 5 einen hohen Aufwand: Es m üssen nicht nur die Daten geschrieben werden, sondern auch der Parity-Sektor muß angepaßt werden. Hierzu wird zunächst der alte Inhalt des Datenblocks und des ParitySektors gelesen und der Parity-Sektor neu berechnet [PGK88]: parityneu = (datenalt xor datenneu ) xor parityalt Danach werden die Daten und der neuberechnete Parity-Sektor geschrieben. Insgesamt sind somit zwei Lese- und zwei Schreibzugriffe erforderlich, wobei jeweils die Lesezugriffe und Schreibzugriffe parallel ausgeführt werden können. Bei den Schreibzugriffen entfällt die Positionierzeit, da sich der Kopf nach den Lesezugriffen bereits über der richtigen Spur befindet, allerdings wird nach dem Lesen eine volle Umdrehung der Platte benötigt, bis sich der Sektor wieder unter dem Kopf befindet, um die Daten schreiben zu können. Somit ergibt sich insgesamt für die Zugriffszeit: Zugrif f szeit = P ositionierzeit + Latenz + Rotationszeit + T ransf erzeit Die Transferzeit zum Lesen der Daten fällt parallel zur Rotationszeit an und fällt geringer aus als diese, so daß sie hier keine Rolle spielt. Da die durchschnittliche Latenz gleich der Zeit für eine halbe Umdrehung der Platte ist, ergibt sich für die durchschnittliche Zugriffszeit: ØZugrif f szeit = ØP ositionierzeit + 3 × ØLatenz + T ransf erzeit A.3. SOLID STATE DISKS A.2.4 169 RAID-Fazit RAID Systeme sind nicht geeignet, die Zugriffszeit wesentlich zu verbessern. RAIDLevel 5 führt bei Schreibzugriffen sogar zu einer deutlichen Erhöhung der Zugriffszeit. Rechnet man einmal die Werte für die schnellste Platte aus Tabelle A.1 hoch, so kommt man auf folgende Werte für die durchschnittliche Zugriffszeit (ohne Transferzeit): Lesen Schreiben einzelne Platte 5, 4ms 5, 4ms RAID 0 5, 4ms 5, 4ms RAID 1 4, 0ms 7, 4ms RAID 5 5, 4ms 9, 4ms Aufgrund der Nichtlinearität zwischen zurückgelegter Entfernung und Geschwindigkeit der Kopfbewegung sind diese Zahlen nicht allzu genau und k önnen nur einen groben Richtwert vorgeben. Nach dieser Tabelle ist RAID-Level 1 die beste L ösung bezüglich Zugriffszeit, falls man auf Redundanz angewiesen ist, andernfalls kann je nach Anwendung eine einzelne Platte die bessere Wahl sein. A.3 Solid State Disks Solid State Disks (SSD) verzichten auf mechanische Komponenten und speichern Daten in akkugepuffertem Speicher oder Flash-Speicher. Sie werden über die gleichen Schnittstellen angeschlossen, über die auch Festplatten angeschlossen werden und können so direkt als Festplattenersatz verwendet werden, ohne daß die Anwendung geändert werden müßte. Produkt Altec† Athena-2 plus 800MB Athena-2 plus 200MB Full Metal Card 175MB Memtech‡ AT3500 Mammoth 1GB SC3500 Mastodon 1GB Typ Bus DRAM SCSI DRAM SCSI Flash PCMCIA Flash Flash IDE SCSI Bandbreite Zugriffsz. Preis∗ 24M B/s 24M B/s 16M B/s burst 0, 15ms 0, 15ms 1, 25ms 8379,- EUR 3479,- EUR 605,- EUR 0, 75 − 3, 5M B/s 0, 65 − 1, 5M B/s 0, 3ms 1, 0ms 1425,- EUR 1424,- EUR ∗ Stand 3/2003 Anbieter: Lieske Elektronik ‡ Anbieter: www.storesys.com † Tabelle A.2: Solid State Disks: Technische Daten DRAM basierte Solid State Disks sind sehr teuer (Tabelle A.2). Eine 800MB Platte kostet derzeit etwa soviel wie 6-8 komplette Rechner mit insgesamt 6-8 GBytes 170 ANHANG A. GRUNDLAGEN VON SPEICHERTECHNOLOGIEN Hauptspeicher. Wird Redundanz benötigt, um eine höhere Verfügbarkeit zu erreichen, so sind zwei Platten erforderlich. Für preiswerte Systeme kommt deshalb die Verwendung von DRAM basierten SSDs nicht infrage. Flash-ROM basierte Solid State Disks bieten im Vergleich zu DRAM basierten Lösungen eine schlechtere Bandbreite und sind auch in der Zugriffszeit unterlegen. Dafür sind sie deutlich billiger als DRAM-basierte Solid State Disks. Ein gr ößeres Problem ist aber, daß Flash-ROMs nicht beliebig oft beschrieben werden k önnen. Intel gibt z.B. für seine StrataFlash Memory (J3) Speicher 100.000 Lösch-Schreib Zyklen an [Int]. Dies ist ein Problem, wenn immer wieder der gleiche Speicherbereich geschrieben wird. Wird z.B. in einer Log-Datei eine Verwaltungsinformation, die an einer festen Stelle liegt, einmal alle 10 Sekunden auf einen aktuellen Wert gebracht, so wird bereits nach knapp 12 Tagen die minimale Zyklen-Anzahl erreicht. Interessanterweise hilft an dieser Stelle auch nicht der Einsatz von RAID-Methoden, da in diesem Fall auch nach der Umsetzung der Zugriffe der Schreibzugriff immer auf den gleichen Bereich erfolgt. RAID kann also den Verschleiß nicht verhindern, sondern nur einem möglichen daraus resultierenden Datenverlust vorbeugen. Somit sind Flash-ROM basierte Solid State Disks für einen schreibintensiven Einsatz also als Speichermedium zum Speichern des Transaktions-Logs ungeeignet. A.4 Zusammenfassung Alle hier vorgestellten Speichertechnologien sind nicht f ür den Einsatz in einem preiswerten DBMS geeignet, das niedrige Antwortzeiten garantieren soll. Festplatten sind aufgrund der Zugriffszeit zu langsam. Dies ändert sich auch nicht, wenn man aus mehreren Festplatten ein RAID-System aufbaut, da dies f ür diesen Anwendungszweck die Zugriffszeit nicht verringern kann. Flash-ROM basierte SSDs sind aufgrund der limitierten L ösch-Schreib Zyklen für den Einsatz zum Speichern des schreibintensiven Transaktions-Logs ungeeignet und DRAM-basierte SSDs scheiden aufgrund ihres hohen Preises f ür erschwingliche Systeme aus. Anhang B Algorithmen Dieses Kapitel enthält die Algorithmen der vorangegangenen Kapitel in einer ausführlichen Darstellung in Pseudocode. B.1 Gesamtalgorithmus zur Erkennung ausgefallener Knoten (Anhang zu Abschnitt 4.4) 1: constant HeartbeatInterval 2: constant HostT imeout 3: constant F inalT imeout 4: constant KillT imeout {* Zeit zwischen zwei Heartbeats *} {* normaler Timeout *} {* Timeout für ausgefallene Verbindungen *} 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: process Heartbeat loop sleep HeartbeatInterval {* Ping senden *} call P ing() at cluster nodes \ {self } for all node ∈ cluster nodes \ {self } do node.timeout ← node.timeout + 1 {* Timeout-Zähler hochzählen *} if node > HostT imeout then dest nodes ← {x ∈ cluster nodes | x 6= self ∧ x.timeout ≤ HostT imeout} timeout ← node.timeout 171 172 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: ANHANG B. ALGORITHMEN if node < self then {* Symmetriebrechung *} timeout ← timeout − HostT imeout/2 end if if dest nodes 6= ∅ then call RequestKillHost(node, timeout) at dest nodes else cluster nodes ← {self } end if end if end for for all i ∈ kill wait do i.timeout ← i.timeout − 1 if i.timeout = 0 then if i.node ∈ cluster nodes then call KillHost(i.node) at cluster nodes \ {i.node} end if kill wait ← kill wait \ {i} end if end for end loop 36: 37: 38: 39: 40: procedure P ing() begin call P ingAck() at caller end procedure 41: 42: 43: 44: 45: procedure P ingAck() begin caller.timeout ← 0 end procedure 46: 47: 48: 49: 50: 51: 52: 53: procedure RequestKillHost(node, timeout) begin cluster nodes ← cluster nodes \ {node} if (node.timeout > HostT imeout) or (timeout > F inalT imeout) then call KillHost(node) at cluster nodes \ {self } end if end procedure 54: 55: 56: procedure KillHost(node) begin B.2. BERECHNUNG DER SERVERTABELLE 57: 58: 59: 60: 61: 173 if node ∈ cluster nodes then cluster nodes ← cluster nodes \ {node} call KillHost(node) at cluster nodes end if end procedure 62: 63: 64: 65: 66: 67: procedure EOF () begin { * EOF auf Verbindung zu caller *} cluster nodes ← cluster nodes \ {caller} kill wait ← kill wait ∪ {(caller, KillT imeout)} end procedure B.2 Berechnung der Servertabelle (Anhang zu Abschnitt 5.4) 1: constant SliceCnt {* Anzahl der Slices *} 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: array primary[0 . . . SliceCnt − 1] array secondary[0 . . . SliceCnt − 1] array ternary[0 . . . SliceCnt − 1] {* function usage(host) begin Hilfsfunktionen {* Servertabelle aufgeteilt *} {* in primäre, sekundäre *} {* und ternäre Server *} *} {* Auslastung berechnen *} {* jeder Server taucht pro Slice max. einmal auf*} return | {i ∈ [0, SliceCnt[ | primary[i] = host ∨ (ternary[i] = none ∧ secondary[i] = host) ∨secondary[i] = host} | end function 15: 16: 17: 18: 19: function p usage(host) begin {* Auslastung als primärer Server*} return | {i ∈ [0, SliceCnt − 1] | primary[i] = host} | end function 20: 21: 22: 23: 24: function min used(exclude) begin {* pass. Server mit min. Auslastung *} h ← all hosts \ exclude return i | i ∈ h ∧ ∀j ∈ h : i ≤ usage(j) 174 25: ANHANG B. ALGORITHMEN end function 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: hier folgt der eigentliche Algorithmus {* procedure update tables begin for i ← 0 to SliceCnt − 1 do {* ausgefallene Server ersetzen if primary[i] not alive then {* primärer Server ausgefallen if secondary[i] alive then {* durch intakten sekundären primary[i] ← secondary[i] {* Server ersetzen secondary[i] ← none ternary[i] ← min used(primary[i]) {* neuer Ternärserver else fatal error {* mehr als ein Server ausgefallen end if else if secondary[i] not alive then {* Sekundärserver ausgefallen secondary[i] ← none {* neuer Ternärserver ternary[i] ← min used(primary[i]) end if end for *} *} *} *} *} *} *} *} *} 44: 45: 46: 47: 48: 49: 50: 51: 52: for i ← 0 to SliceCnt − 1 do {* Auslastung angleichen *} if ternary[i] =none then {* nur falls Eintrag noch nicht geändert *} if (usage(primary[i]) > usage(secondary[i]) and usage(primary[i]) > min used(∅) + 1) or p usage(primary[i]) > p usage(secondary[i]) then swap primary[i] ↔ secondary[i] end if 53: 54: 55: 56: 57: 58: 59: 60: 61: {* niedrig ausgelastete Server als ternäre Server einfügen *} h ← min used({primary[i], secondary[i]}) if usage(secondary[i]) > usage(h) + 1 then ternary[i] ← h end if end if end for end procedure Anhang C Schnittstellenkurzreferenz C.1 Programmierschnittstelle zur Kommunikationsschicht Hier möchte ich eine kurze Dokumentation über die wichtigsten Methoden der Klasse MessagePort geben, um einen Eindruck der zur Verfügung stehenden Methoden der Kommunikationsschicht zu vermitteln: MessagePort(int port =0, int hostclass =0) legt einen MessagePort an und bindet ihn an die angegebenen Portnummer. Per hostclass wird die Hostklasse spezifiziert, zu der der Knoten geh ören soll. int join(const IPAddress &ip) verbindet diesen Knoten mit dem Cluster, in dem sich der Knoten mit der Adresse ip befindet. void leave() dient zum Verlassen des Clusters. int hosttableChanged() liefert true, falls sich die Hosttabelle geändert hat. 175 176 ANHANG C. SCHNITTSTELLENKURZREFERENZ const Array<MessagePort::HostNode> &getHosttable(); liefert die Hosttabelle. MessageID sendMessage(const Buffer &buf, const IPAddress &ip, AckType ack=None) sendet die Nachricht buf an ip. Per ack wird festgelegt, ob ein Acknowledge oder eine Antwort erwartet wird oder nicht. Die zurückgelieferte MessageID kann verwendet werden, um Anworten zu dieser Nachricht zuzuordnen. int readMessage(MessageHeader &sender, Buffer &buffer) liest eine Nachricht aus dem Eingangspuffer und speichert sie in buffer. Die MessageID und IP-Adresse im MessageHeader, der in sender gespeichert wird, k önnen verwendet werden, um Antworten auf diese Nachricht zu senden. void sendReply(const Buffer &buf, const IPAddress &ip, const MessageID &msg id) sendet eine Anwort auf die Nachricht mit der ID msg id. int readResult(MessageHeader &mh, Buffer &reply) liest eine Nachricht aus dem Antwortenpuffer. Antworten können über die im MessageHeader vorhandene MessageID der Anfrage zugeordnet werden. void sendSignal(int signal, const IPAddress &ip) sendet das Signal signal an die Adresse ip. int checkSignal(int signum) liest das Signal aus und liefert true, falls das Signal gesetzt ist. Signale werden beim auslesen nicht zurückgesetzt. void resetSignal(int signum) setzt das Signal zurück. int messageFD() int resultFD() C.2. HADES PROGRAMMIERSCHNITTSTELLE int int int int 177 sig0FD() sig1FD() sig2FD() hosttableChangedFD() liefern den jeweiligen File-Deskriptor, der dazu verwendet werden kann, per select() oder poll() auf das Eintreffen einer Nachricht oder eines Signals zu warten. void flush() erzwingt einen Taskwechsel. Damit wird erreicht, daß Pakete schneller gesendet werden. void removeMessages(const IPAddress &ip) entfernt alle Nachrichten von ip aus den Empfangspuffern. IPAddress getSelf() liefert die eigene Adresse (IP-Adresse und Portnummer) zur ück. C.2 HADES Programmierschnittstelle Hier möchte ich eine kurze Dokumentation über die wichtigsten Methoden der Klasse PageClient geben, die die Client-Programmierschnittstelle zur Verf ügungstellt, um einen Eindruck der zur Verfügung stehenden Methoden zu vermitteln: int openDatabase(const IPAddress &member) stellt eine Verbindung zur Datenbank her, die durch einen beliebigen Server member repräsentiert wird. int openTable(const String &name, int create=0) int createTable(const String &name) öffnen eine bestehende Tabelle bzw. legen eine neue Tabelle an. Als R ückgabewert wird die Tabellen-ID geliefert. int dropTable(const String &name) löscht eine bestehende Tabelle. Im Erfolgsfall wird P ageClient :: OK zur ückgeliefert. 178 ANHANG C. SCHNITTSTELLENKURZREFERENZ int listTable(Array<String> &table) holt ein Array mit der Liste der Namen der Datentabellen. MessageID beginWritePage(int table, int page, const Array<char> &data, TXID tx id=TXID(), int timeout msec=0) int endWritePage(const MessageID &id, int *insertpos=NULL) MessageID beginReadPage(int table, int page, TXID tx id=TXID(), int timeout msec=0) int endReadPage(const MessageID &id, Array<char> &data) beginW riteP age startet das Schreiben einer Seite. Auf die Beendigung der Operation wird mit endW riteP age gewartet. Dazu wird die MessageID verwendet, die von beginW riteP age geliefert wurde. Genauso funktionieren beginReadP age und endReadP age, nur daß hier Daten gelesen statt geschrieben werden. Optional kann eine Transaktions-ID per tx id mit angegeben werden, um die Operation innerhalb einer Transaktion ausführen zu lassen. Mit timeout msec kann eine Zeit in Millisekunden angegeben werden, die eine Operation auf die Freigabe eines eventuell bestehenden Locks warten soll. MessageID beginWritePageSet(int table, const Array<int> &page, const Array<Array<char> > &data, TXID tx id=TXID(), int timeout msec=0) int endWritePageSet(const MessageID &id, Array<int> *insertpos=NULL) MessageID beginReadPageSet(int table, const Array<int> &page, TXID tx id=TXID(), int timeout msec=0) int endReadPageSet(const MessageID &id, Array<Array<char> > &data) funktionieren wie die einfachen Lese- und Schreiboperationen, nur daß hier mehrere Seiten, die zum gleichen Slice gehöhren müssen, gleichzeitig gelesen bzw. geschrieben werden können. MessageID beginReadSize(int table, int slice, TXID tx id=TXID(), int timeout msec=0) int endReadSize(const MessageID &id, int &size) dienen zum Lesen der Größen eines Slices. MessageID beginMatchSort(int table, int slice, C.2. HADES PROGRAMMIERSCHNITTSTELLE 179 const Array<MatchOpcode> &match, const Array<CmpOpcode> &cmp, TXID tx id=TXID(), int timeout msec=0) int endMatchSort(const MessageID &id, Array<int> &data) Mit beginM atchSort kann ein Table-Scan auf jeweils einem Slice durchgef ührt werden und das Ergebnis vorsortiert werden. match nimmt dabei das Suchkriterium und cmp das Sortierkriterium auf. int sliceCnt() liefert die Anzahl der Slices im System. Diese wird z.B. f ür readPageSet() benötigt. TXID beginTransaction() TXextID beginExtTransaction() beginnen eine (externe) Transaktion und liefern die Transaktions-ID zur ück. MessageID beginChainTransaction(TXID tx id, TXextID append) int endChainTransaction(const MessageID &id) hängen eine externe Transaktion an eine andere Transaktion, um auf diese Weise verteilte Transaktionen aufzubauen. MessageID beginCommit(TXID) MessageID beginCommit(TXextID tx id) void endCommit(const MessageID &id) MessageID beginAbort(TXID) MessageID beginAbort(TXextID tx id) void abortTXOp(const TXID &tx id) void endAbort(const MessageID &id) dienen zum Ausführen eines Commit oder Abort. Stehen noch Operationen aus, f ür die noch keine Antwort vorliegt, und soll die Transaktion abgebrochen werden, so muß vor beginAbort zusätzlich abortT XOp ausgeführt werden, das diese Operationen abbricht. void commit(TXID tx id) abort(TXID tx id) Diese beiden Methoden sind jeweils eine Abkürzung für beginCommit/endCommit bzw. beginAbort/endAbort. 180 ANHANG C. SCHNITTSTELLENKURZREFERENZ Literaturverzeichnis [AD85] Agrawal, Rakesh und David J. DeWitt: Recovery architectures for multiprocessor database machines. In: Proceedings of the 1985 ACM SIGMOD international conference on Management of data, Seiten 131–145. ACM Press, 1985. [AGP98] Abdallah, Maha, Rachid Guerraoui, and Philippe Pucheral: One-Phase Commit: Does It Make Sense? In Proceedings of the International Conference on Parallel and Distributed Systems, pages 182–192, December 1998. [AHCL97] Al-Houmaily, Yousef J., Panos K. Chrysanthis, and Steven P. Levitan: Enhancing the performance of presumed commit protocol. In Selected Areas in Cryptography, pages 131–133, 1997. [ASSC02] Akyildiz, Ian F., Weilian Su, Yogesh Sankarasubramaniam, and Erdal Cayirci: A survey on sensor networks. IEEE Communications Magazine, pages 102–114, August 2002. [AvdBF+ 92] Apers, Peter M. G., Carel A. van den Berg, Jan Flokstra, Paul W. P. J. Grefen, Martin L. Kersten, and Annita N. Wilschut: PRISMA/DB: A parallel main memory relational DBMS. Knowledge and Data Engineering, 4(6):541–554, 1992. [BBC+ 04] Buchmann, A., C. Bornhövd, M. Cilia, L. Fiege, F. Gärtner, C. Liebig, M. Meixner, and G. Mühl: Web Dynamics (to appear), chapter DREAM: Distributed Reliable Event-based Application Management. Springer, 2004. [BG88] Bitton, Dina and Jim Gray: Disk shadowing. In Bancilhon, François and David J. DeWitt (editors): Fourteenth International 181 182 LITERATURVERZEICHNIS Conference on Very Large Data Bases, August 29 - September 1, 1988, Los Angeles, California, USA, Proceedings, pages 331–338. Morgan Kaufmann, 1988. [BHG87] Bernstein, Philip A., Vassos Hadzilacos, and Nathan Goodman: Concurrency Control and Recovery in Database Systems. Addison Wesley, 1987. [Bir93] Birman, Kenneth P.: The process group approach to reliable distributed computing. Commun. ACM, 36(12):37–53, 1993. [BR93] Birman, Kenneth P. and Robbert Van Renesse: Reliable Distributed Computing with the ISIS Toolkit. IEEE Computer Society Press, 1993. [BWKI03] Basile, Claudio, Long Wang, Zbigniew Kalbarczyk, and Ravi Iyer: Group Communication Protocols under Errors. In Proceedings of the 22nd International Symposium on Reliable Distributed Systems (SRDS’03), pages 35–44, October 2003. [Cen] Center, IBM T. J. Watson Research: Gryphon: Publish/subscribe over public networks. [CFC+ 03] Cilia, M., L. Fiege, C.Haul, A.Zeidler, and A. P. Buchmann: Looking into the Past: Enhancing Mobile Publish/Subscribe Middleware. In Proceeding of the SSecond International Workshop on Distributed Event-Based Systems (DEBS’03), 2003. [CKKS89] Copeland, George, Tom Keller, Ravi Krishnamurthy, and Marc Smith: The Case For Safe RAM. In Proceedings of the Fifteenth International Conference on Verly Large Data Bases, pages 327–335, 1989. [CNF01] Cugola, G., E. Di Nitto, and A. Fuggetta: The JEDI eventbased infrastructure and its application to the development of the OPSS WFMS. In IEEE Transactions on Software Engineering, volume 27, pages 827–850, Sept 2001. [CNRW98] Carzaniga, Antonio, Elisabetta Di Nitto, David S. Rosenblum, and Alexander L. Wolf: Issues in supporting event-based architectural styles. In Proceedings of the third international workshop on Software architecture, pages 17–20. ACM Press, 1998. LITERATURVERZEICHNIS 183 [CRW01] Carzaniga, Antonio, David S. Rosenblum, and Alexander L. Wolf: Design and evaluation of a wide-area event notification service. ACM Transactions on Computer Systems (TOCS), 19(3):332– 383, 2001. [DKO+ 84] DeWitt, David J, Randy H Katz, Frank Olken, Leonard D Shapiro, Michael R Stobebraker, and David Wood: Implementation Techniques for Main Memory Database Systems. In Proceedings of the ACM SIGMOD Conference, pages 1–8, 1984. [Dou99] Dougall, Richard Mc: Availability – What It Means, Why It’s Important, and How to Improve It. Sun Microsystems, Inc., 1999. [ED88] Enbody, R. J. and H. C. Du: Dynamic hashing schemes. Computing Surveys (CSUR), 20(2):850–113, 1988. [EGHK99] Estrin, Deborah, Ramesh Govindan, John S. Heidemann, and Satish Kumar: Next Century Challenges: Scalable Coordination in Sensor Networks. In Mobile Computing and Networking, pages 263– 270, 1999. [EGPS01] Estrin, Deborah, Lewis Girod, Greg Pottie, and Mani Srivastava: Instrumenting the world with wireless sensor networks. In International Conference on Acoustics, Speech and Signal Processing (ICASSP 2001), Salt Lake City, Utah,, May 2001. [FHS03] Fetzer, Christof, Karin Högstedt, and Neeraj Suri: Practical Aspects of Designing an IP Take Over Mechanism. In Proceedings of WORDS, 2003. [FLP85] Fischer, Michael J., Nancy A. Lynch, and Michael S. Paterson: Impossibility of distributed consensus with one faulty process. Journal of the ACM (JACM), 32(2):374–382, 1985. [GMS92] Garcia-Molina, Hector and Kenneth Salem: Main Memory Database Systems: An Overview. IEEE Transactions on Knowledge and Data Engineering, 4:509–516, dec. 1992. [GR93] Gray, Jim and Andreas Reuter: Transaction Processing: Concepts and Techniques. Morgan Kaufmann, 1993. [Gra79] Gray, J.N.: Notes on Data Base Operating Systems. In Operating Systems: An Advanced Course, pages 393–481. Springer-Verlag, 1979. ACM 184 LITERATURVERZEICHNIS [GS00] Gray, Jim and Prashand Shenoy: Rules of thumb in date engineering. In Proceedings of the sixteenth International Conference on Data Engineering, pages 3–9. IEEE Computer Society, March 2000. [Gär01] Gärtner, Felix Christoph: Formale Grundlagen der Fehlertoleranz in verteilten Systemen. Dissertation, Fachbereich Informatik, TU Darmstadt, 2001. [HCB00] Heinzelman, Wendi Rabiner, Anantha Chandrakasan, and Hari Balakrishnan: Energy-efficient communication protocol for wireless microsensor networks. In HICSS, 2000. [HGM01] Huang, Yongqiang and Hector Garcia-Molina: Publish/Subscribe in a Mobile Environment. In International Workshop on Data Engineering for Wireless and Mobile Access, pages 27–34, 2001. [HHB96] Helal, Abdelsalam A., Abdelsalam A. Heddaya, and Bharat B. Bhargava: Replication Techniques in Distributed Systems. Kluwer Academic Publishers, 1996. [HR83] Haerder, Theo and Andreas Reuter: Principles of transactionoriented database recovery. ACM Comput. Surv., 15(4):287–317, 1983. [IBM] IBM: Ultrastar 36Z15 hard disk drive. data sheet. [IEE] IEEE Computer Society: IEEE Standard for Information Technology Telecommunications and Information Exchange between Systems Local and Metropolitan Area Networks Common Specifications Part 3: Media Access Control (MAC) Bridges, 1998 edition. Adopted by the ISO/IEC and redesignated as ISO/IEC 15802-3:1998. [IGE00] Intanagonwiwat, Chalermek, Ramesh Govindan, and Deborah Estrin: Directed diffusion: a scalable and robust communication paradigm for sensor networks. In Mobile Computing and Networking, pages 56–67, 2000. [Int] R Intel: 3 Volt Intel StrataFlash Memory (J3). ftp://download.intel.com/design/flcomp/datashts/29066712.pdf. data sheet. LITERATURVERZEICHNIS 185 [Ker97] Kermarrec, Anne-Marie: Replication For Efficiency And Fault Tolerance In A DSM System. In Proceedings of PDCS’97Ninth International Conference on Parallel and Distributed Computing and Systems, October 1997. [Lap92] Laprie, J.C.: Dependability: Basic Concepts and Terminology in English, French, German, Italian and Japanese, volume 5. SpringerVerlag Wien New York, 1992. [LH89] Li, Kai and Paul Hudak: Memory coherence in shared virtual memory systems. ACM Transactions on Computer Systems (TOCS), 7(4):321–359, 1989. [LH01] Li, L. and J. Halpern: Minimum energy mobile wireless networks revisited, 2001. [LMB00] Liebig, C., M. Malva, and A. Buchmann: Integrating Notifications and Transactions: Concepts and X2TS Prototype. In 2nd Int. Workshop on Engineering Distributed Objects (EDO 2000), November 2000. [LN96] Litwin, Witold and Marie-Anne Neimat: High-availability LH* schemes with mirroring. In Conference on Cooperative Information Systems, pages 196–205, 1996. [LNL+ 97] Litwin, W., M. Neimat, G. Levy, S. Ndiaye, and T. Seck: Lh*s : a high-availability and high-security scalable distributed data structure, 1997. [LNS96] Litwin, Witold, Marie-Anne Neimat, and Donovan A. Schneider: Lh* - a scalable, distributed data structure. ACM Transactions on Database Systems (TODS), 21(4):480–525, 1996. [Lor77] Lorie, Raymond A.: Physical integrity in a large segmented database. ACM Trans. Database Syst., 2(1):91–104, 1977. [LR02] Litwin, Witold and Tore Risch: LH*g: a High-availability Scalable Distributed Data Structure by Record Grouping. In IEEE Transactions on Knowledge and Data Engineering, volume 14(4), pages 923–927, July/August 2002. [LS00] Litwin, Witold and Thomas Schwarz: Lh*rs: a high-availability scalable distributed data structure using reed solomon codes. In Proceedings of the 2000 ACM SIGMOD international conference on Management of data, pages 237–248. ACM Press, 2000. 186 LITERATURVERZEICHNIS [LSP82] Lamport, Leslie, Robert Shostak, and Marshall Pease: The byzantine generals problem. ACM Trans. Program. Lang. Syst., 4(3):382–401, 1982. [LT01] Liebig, C. and S. Tai: Middleware Mediated Transactions. In 3rd Intl. Symposium on Distributed Objects and Applications (DOA 2001), September 2001. [Max] Maxtor: MaXLineII. http://www.maxtor.com/en/documentation /data sheets/maxline data sheet.pdf. data sheet. [McK] McKinley, David: Introduction to High Availability Systems. [Mic99] Michael A. Olson and Keith Bostic and Margo Seltzer: Berkeley DB - White Paper. In Proceedings of the 1999 Summer Usenix Technical Conference, Monterey, California, June 1999. [MLO86] Mohan, C., B. Lindsay, and R. Obermarck: Transaction management in the r* distributed database management system. ACM Trans. Database Syst., 11(4):378–396, 1986. [MR03] Mattern, F. und K. Römer: Drahtlose Sensornetze. Spektrum, 26(3), June 2003. [MZWZ02] Mao, Yun, Youhui Zhang, Dongsheng Wang, and Weimin Zheng: Lnd: a reliable multi-tier storage device in now. SIGOPS Oper. Syst. Rev., 36(1):70–80, 2002. [Müh02] Mühl, Gero: Large-Scale Content-Based Publish/Subscribe Systems. PhD thesis, Fachbereich Informatik, TU Darmstadt, 2002. [NL91] Nitzberg, Bill and Virginia Lo: Distributed shared memory: A survey of issues and algorithms. Computer, 24(8):52–60, 1991. [OW90] Ottmann, Thomas und Peter Widmayer: Algorithmen und Datenstrukturen. BI-Wissenschaftsverlag, 1990. [PB02] Pietzuch, P. and J. Bacon: Hermes: A Distributed Event-Based Middleware Architecture, Jul 2002. [PGK88] Patterson, David A., Garth Gibson, and Randy H. Katz: A case for redundant arrays of inexpensive disks (raid). In Proceedings of the 1988 ACM SIGMOD international conference on Management of data, pages 109–116. ACM Press, 1988. Informatik LITERATURVERZEICHNIS 187 [PK00] Pottie, G.J. and W.J. Kaiser: Wireless Integrated Network Sensors. Communications of the ACM, 43(5):51–58, May 2000. [PL94] Plank, James S. and Kai Li: Faster Checkpointing with N+1 Parity. In 24th Annual International Symposium on Fault-Tolerant Computing, pages 288–297, June 1994. [Pow92] Powell, David: Failure Mode Assumptions and Assumption Coverage. In Pradhan, Dhiraj K. (editor): Proceedings of the 22nd Annual International Symposium on Fault-Tolerant Computing (FTCS ’92), pages 386–395, Boston, MA, July 1992. IEEE Computer Society Press. [RD01] Rowstron, Antony I. T. and Peter Druschel: Pastry: Scalable, decentralized object location, and routing for large-scale peer-to-peer systems. In Proceedings of the IFIP/ACM International Conference on Distributed Systems Platforms Heidelberg, pages 329–350. SpringerVerlag, 2001. [RFH+ 01] Ratnasamy, Sylvia, Paul Francis, Mark Handley, Richard Karp, and Scott Schenker: A scalable content-addressable network. In Proceedings of the 2001 conference on Applications, technologies, architectures, and protocols for computer communications, pages 161– 172. ACM Press, 2001. [RG97] Riedel, E. and G. Gibson: Active Disks - Remote Execution for Network-Attached Storage, 1997. [RG00] Ramakrishnan, Raghu and Johannes Gehrke: Database Management Systems. Mc Graw Hill, second edition, 2000. [Rie99] Riedel, Erik: Active Disks - Remote Execution for Network-Attached Storage. PhD thesis, CMU-CS-99-117, Carnegie Mellon University, 1999. [SC90] Stamos, James W. and Flaviu Cristian: A low-cost atomic commit protocol. In proceedings of the 9th IEEE Symp. on Reliable Distributed Systems(SRDS’90), pages 66–75, 1990. [SGM89] Salem, K and H. Garcia-Molina: Checkpointing memory-resident databases. In Fifth International Conference on Data Engineering, pages 452–462, February 1989. [SK98] Silberschatz, Abraham and Henry F. Korth: Database System Concepts. Mc Graw Hill, third edition, 1998. 188 LITERATURVERZEICHNIS [Ske81] Skeen, Dale: Nonblocking commit protocols. In Proceedings of the 1981 ACM SIGMOD international conference on Management of data, pages 133–142. ACM Press, 1981. [SMK+ 01] Stoica, Ion, Robert Morris, David Karger, M. Frans Kaashoek, and Hari Balakrishnan: Chord: A scalable peer-topeer lookup service for internet applications. In Proceedings of ACM SIGCOMM, pages 149–160. ACM Press, 2001. [Spi00] Spiteri, Mark David: An Architecture for the Notification, Storage and Retrieval of Events. PhD thesis, Darwin College, University of Cambridge, 2000. [SS83] Schlichting, Richard D. and Fred B. Schneider: Fail-stop processors: an approach to designing fault-tolerant computing systems. ACM Trans. Comput. Syst., 1(3):222–238, 1983. [Ste98] Stevens, W. Richard: UNIX Network Programming, Networking APIs: Sockets and XTI, volume 1. Prentice Hall, second edition, 1998. [TCP] RFC 793 – Transmission Control Protocol. [Tim] TimesTen, Inc.: TimesTen Real-Time Event Processing System, White Paper. [Tim03] TimesTen, Inc.: TimesTen Architectural Overview Release 5.0, 2003. [TvS02] Tanenbaum, Andrew S. and Maarten van Steen: Distributed Systems, Principles and Paradigms. Prentice Hall, 2002. [UDP] RFC 768 – User Datagram Protocol. [Vau95] Vaughan Pratt: Anatomy of the Pentium Bug. In Mosses, P. D., M. Nielsen, and M. I. Schwartzbach (editors): TAPSOFT’95: Theory and Practice of Software Development, pages 97–107. Springer Verlag, 1995. [ZKJ01] Zhao, B. Y., J. D. Kubiatowicz, and A. D. Joseph: Tapestry: An infrastructure for fault-tolerant wide-area location and routing. Technical Report UCB/CSD-01-1141, UC Berkeley, April 2001. Lebenslauf Persönliche Daten Matthias Meixner geboren im Januar 1971 in Fulda Ausbildung und beruflicher Werdegang 1977 - 1981 Grundschule Schwarzbach 1981 - 1990 Freiherr vom Stein Gymnasium in Fulda 1990 - 1991 Grundwehrdienst als Wehrpflichtiger 1991 - 1997 Studium der Informatik an der TH Darmstadt Juli 1997 - Dezember 1997 Wissenschaftlicher Mitarbeiter im Fachgebiet Mikroelektronische Systeme der TU Darmstadt Januar 1998 - Januar 1999 Stipendiat im Graduiertenkolleg Intelligente Systeme für die Informations- und Automatisierungstechnik (ISIA) der TU Darmstadt Februar 1999 - Januar 2002 Wissenschaftlicher Mitarbeiter in der Rechnerbetriebsgruppe (RBG) der TU Darmstadt Februar 2002 - Mai 2004 Wissenschaftlicher Mitarbeiter im Fachgebiet Datenbanken und Verteilte Systeme (DVS) der TU Darmstadt Seit Juni 2004 Softwareentwickler bei Thales-e-Transactions in Bad Hersfeld. 189