Ein hochverfügbares, verteiltes Main-Memory - DVS

Werbung
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
Herunterladen