Erweiterung des verteilten Datenspeichersystems Cassandra um

Werbung
Leibniz Universität Hannover
Fakultät für Elektrotechnik und Informatik
Institut für Praktische Informatik
Fachgebiet Datenbanken und Informationssysteme
Erweiterung des verteilten
Datenspeichersystems Cassandra um eine
Indexunterstützung
Bachelorarbeit
im Studiengang Informatik
Jan Kantert
Erstprüfer:
Prof. Dr. Udo Lipeck
Zweitprüfer:
Dr. Hans Hermann Brüggemann
Betreuer:
Prof. Dr. Udo Lipeck
Abgabedatum: 12 August 2010
2
Inhaltsverzeichnis
1 Einleitung
5
1.1
Übersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5
1.2
Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6
1.3
Aufgabenstellung . . . . . . . . . . . . . . . . . . . . . . . . . . .
6
1.4
Aufbau der Arbeit . . . . . . . . . . . . . . . . . . . . . . . . . .
7
2 Grundlagen
9
2.1
Cassandra . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
9
2.2
Datenmodell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
11
2.3
Konfigurationsdatei . . . . . . . . . . . . . . . . . . . . . . . . . .
13
2.4
Datenverteilung . . . . . . . . . . . . . . . . . . . . . . . . . . . .
14
2.5
CAP-Theorem . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
16
2.6
Konsistenz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
17
2.7
Operationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
20
2.7.1
24
Beispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3 Konzept
27
3.1
Übersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
27
3.2
Index-Speicherformat . . . . . . . . . . . . . . . . . . . . . . . . .
28
3.3
Korrektheit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
30
3.4
Index definieren . . . . . . . . . . . . . . . . . . . . . . . . . . . .
32
3.5
Index aktualisieren . . . . . . . . . . . . . . . . . . . . . . . . . .
33
3.6
Index abfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . .
34
3.7
Index (neu-)aufbauen . . . . . . . . . . . . . . . . . . . . . . . . .
35
3.8
Index überprüfen/reparieren . . . . . . . . . . . . . . . . . . . . .
35
3
4
4 Implementierung
4.1 Externes Interface . . . . . . .
4.2 Internes Interface . . . . . . .
4.3 Beispiele . . . . . . . . . . . .
4.4 Konfigurationsinterface . . . .
4.5 Datenstrukturen zur Laufzeit
4.6 Schreiboperationen . . . . . .
4.7 Leseoperationen . . . . . . . .
4.8 Wartungsoperationen . . . . .
INHALTSVERZEICHNIS
.
.
.
.
.
.
.
.
37
38
39
41
42
44
45
47
49
.
.
.
.
.
.
.
.
51
51
53
53
54
54
55
55
55
6 Fazit
6.1 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.2 Vergleich zu relationalen Datenbankmanagementsystemen . . . . .
6.3 Ausblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
57
57
58
59
Literaturverzeichnis
62
Abbildungsverzeichnis
63
Eidesstattliche Erklärung
65
A Konfigurationsdatei cassandra.yaml
67
B Thrift Interface
75
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
5 Experimente und Tests
5.1 Benchmarks . . . . . . . . . . . . . . . . . .
5.1.1 Schreiben ohne Index . . . . . . . . .
5.1.2 Schreiben mit Index . . . . . . . . .
5.1.3 Lesen auf Datensäte . . . . . . . . .
5.1.4 Lesen mit Index auf einen Datensatz
5.1.5 Lesen mit Index auf 10 Datensätze .
5.1.6 Bewertung der Ergebnisse . . . . . .
5.2 Simulation hoher Latenz . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Kapitel 1
Einleitung
1.1
Übersicht
Cassandra ist ein hoch-verfügbarer, strukturierter, versionierter Schlüssel-WertSpeicher. Er ist stark auf Geschwindigkeit optimiert und geht davon aus, dass
Ausfälle die Regel und nicht die Ausnahme sind. Die Konsistenz der Daten wird
zugunsten von Verfügbarkeit und Partitionstoleranz nicht immer garantiert. Ein
Cassandra-Datenspeicher besteht aus mehreren Knoten und alle Datensätze werden anhand ihres Schlüssels über eine Hashfunktion einem Knoten zugewiesen. Datensätze bestehen aus einer beliebigen Anzahl von Attributen, welche nicht durch
ein Schema definiert sind, und sie werden auf N Knoten repliziert. Unterstützt
werden lediglich grundlegende Operationen: Lesen eines Datensatzes, Schreiben
eines (Teil-)Datensatzes und Lesen von Bereichen von Datensätzen anhand des
Schlüssels. Alle Operationen werden vom Benutzer an einen Koordinator-Knoten
gesendet, welcher sich um die Ausführung auf den richtigen Knoten kümmert.
Dabei ist es möglich, anzugeben, auf wie viele Knoten direkt zugegriffen werden soll, um verschieden starke Konsistenzen zu erreichen. Es ist nicht möglich,
einen Datensatz über etwas anderes als den Schlüssel abzufragen. Mehrere Datensätze werden in einer ColumnFamily zusammengefasst, welche das schemalose
Äquivalent zu einer Tabelle in einem RDMS1 ist.
1
Relationales Datenbank Management System
5
6
KAPITEL 1. EINLEITUNG
1.2
Motivation
In relationalen Datenbanksystemen ist es im Unterschied zu Cassandra möglich
Anfragen nach beliebigen Spalten zu stellen. In vielen Anwendungsfällen ist es
sinnvoll, Datensätze effizient über den Wert eines Attributes abfragen zu können.
Dabei ist in den meisten Fällen vorher bekannt um welche Attribute es sind handeln
wird. Aktuell ist das nur durch einen ineffizienten Scan über alle Datensätze zu
realisieren. Zielsetzung der Arbeit ist es daher, einen effizienten und flexiblen
Mechanismus zu entwickeln und zu implementieren, der einen verteilten Index über
ein oder mehrere Attribute aufbaut und nutzt. Dem Benutzer soll es ermöglicht
werden effizient alle Datensätze zu einem Wert eines vorher definierten Attributs
zu finden.
1.3
Aufgabenstellung
Als Ziel der Arbeit sollen folgende Dinge erreicht werden:
• Es soll ein geeignetes Konfigurationsinterface geschaffen werden, in welchem
von einem Benutzer ein Index über eine ColumnFamily, also eine Gruppe von
Datensätzen, definiert werden kann. Dabei können die beteiligten Attribute
und der Name des Indexes gewählt werden.
• Es soll eine Methode entwickelt werden, um einen Index initial aufzubauen
und zu validieren. Eine Validierung soll die normale Nutzung des Indexes
nicht einschränken.
• Es soll ein Verfahren entworfen werden, welches bei jeder schreibenden
Operation auf ein Attribut in einer ColumnFamily den entsprechenden
Index aktualisiert. Dazu wird überprüft, ob die geschriebenen Attribute im
Index enthalten sind. Dann wird gegebenen Falles der Index aktualisiert.
Ein besonderes Augenmerk wird dabei auf die Architektur von Cassandra
gelegt.
• Es soll dem Benutzer ermöglicht werden, lesende Operationen mit Hilfe des
Indexes auszuführen. Dabei soll es möglich sein, einzelne Indexwerte oder
auch Bereiche von Werten abzufragen.
1.4. AUFBAU DER ARBEIT
7
• Der Index soll, wie andere Datensätze auch, per Hashfunktion über alle
Knoten verteilt und repliziert werden.
• Es sollen die gleichen Qualitätsanforderungen wie für normale Schreibzugriffe
auch für den Index gelten. Für jeden Zugriff wird angegeben, auf wie vielen
Knoten er mindestens erfolgen soll. Auf entsprechend vielen Knoten wird
auch der Index geschrieben. In jedem Fall soll der Index konsistent zu
den indexierten Datensätzen bleiben. Das gilt besonders bei Ausfällen von
Knoten oder von Verbindungen zwischen Knoten.
Die Implementierung erfolgt als Teil des Cassandra-Codes in Java. Falls möglich, soll sie in das nächste Cassandra Release einfließen.
1.4
Aufbau der Arbeit
In Kapitel 2 werden die Grundlagen von Cassandra erklärt. Dabei wird Cassandra in die Entwicklungslinien der NoSQL-Datenspeicher eingeordnet und die
generellen Begrifflichkeiten werden erläutert. Besonders eingegangen wird auf das
Datenmodell, die Datenverteilung und die Einschränkung der Konsistenz. Zuletzt
wird eine Übersicht über alle Operationen gegeben.
Danach wird in Kapitel 3 beschrieben, wie Indexe in Cassandra implementiert
werden sollen. Dabei muss auf die Eigenheiten von Cassandra Rücksicht genommen
werden. Es wird darauf eingegangen, welche Operationen ermöglicht werden sollen
und wie ein Index vom Benutzer erstellt werden kann. Weiterhin wird diskutiert,
wie ein Index beim Schreiben aktualisiert wird und wie es möglich ist die Daten
abzufragen. Zuletzt werden der initiale Aufbau und die effiziente Überprüfung
eines Indexes beschrieben.
In Kapitel 4 wird die konkrete Implementierung eines verteilten Indexes in
Cassandra beschrieben. Zunächst wird der generelle Aufbau von Cassandra kurz
erklärt. Danach werden das geplante Speicherformat und die Datenstrukturen zur
Laufzeit entworfen und es wird die Verarbeitung der Konfigurationsdatei erläutert.
Es wird beschrieben, wie schreibende Operationen modifiziert werden müssen,
um beim Schreiben einen Index zu aktualisieren. Anschließend wird eine sinnvolle
Implementierung für lesende Operationen auf den Datenspeicher vorgeschlagen
und kurz auf die Wartungsoperationen eingegangen.
8
KAPITEL 1. EINLEITUNG
Weiterhin wird die Implementierung in Kapitel 5 experimentell überprüft.
Dabei werden Geschwindigkeit und Durchsatz vor und nach der Implementierung
gemessen. Außerdem wird der Einfluss von großer Netzwerklatenz auf Durchsatz
und Geschwindigkeit von Schreiboperationen auf einen Index untersucht.
Kapitel 2
Grundlagen
In diesem Kapitel werden die für die Arbeit wichtigen Grundlagen von Cassandra erklärt und die zugrundeliegende, verteilte Architektur und Philosophie
erläutert. Dabei werden das Datenmodell, die Verteilung der Datensätze sowie
die Einschränkungen, welche sich daraus ergeben, erörtert. Danach wird eine
Übersicht über grundlegende Operationen gegeben und die besondere Behandlung
der Konsistenz von Daten erklärt.
2.1
Cassandra
Apache Cassandra ist ein verteiltes, strukturiertes Datenspeichersystem [14]. Als
Vorlage dienten Amazon Dynamo[12] und Google BigTable[11]. Dabei wurde
das Datenformat von Google BigTable übernommen und später etwas erweitert.
Während Google ein eigenes verteiltes Dateisystem verwendet, hat sich Amazon
für eine einfachere Verteilung per Distributed Hashtable entschieden. Der Fokus
bei Amazon liegt auf guten Antwortzeiten in E-Commerce-Anwendungen und
es wird ein einfaches Schlüssel-Wert-Datenmodell verwendet. BigTable dagegen
speichert zu jedem Schlüssel eine beliebige Anzahl an Attributen mit jeweils einem
Wert und eignet sich somit dazu, große Datenmengen zu verarbeiten und zu
analysieren. Cassandra kombiniert die strukturierte Datenhaltung von BigTable
mit der Verteilung von Dynamo und unterstützt wie Dynamo und BigTable nur
primitive Abfrageoperationen. Die meisten solcher NoSQL-Datenspeichersysteme1
1
NoSQL = Not Only SQL
9
10
KAPITEL 2. GRUNDLAGEN
unterstützen kein SQL2 als Anfragesprache und sind in der Regel auch nicht
relational.
Entwickelt wurde Cassandra von Facebook[13] zum Einsatz als Speicher für
mehrere Reverse Word Indexes3 [14, S. 5]. Aus diesem Grund wurden einige
Änderungen am von Google übernommenen Datenmodell durchgeführt und es
wurde ein besonderes Augenmerk auf gute Performance, hohe Verfügbarkeit und
Skalierbarkeit über viele einfache Knoten gelegt. Mittlerweile ist Cassandra ein
Apache Toplevel Projekt geworden und wird von mehreren großen Firmen genutzt
und weiterentwickelt.
Ähnliche Projekte sind Apache Hadoop[1] und Apache Hbase[2]. Wobei es sich
um einen Klon von Googles Dateisystem und Googles BigTable handelt. Sie eignen
sich besonders für die Analyse großer Datenmengen. Im Unterschied zu Cassandra
garantieren sie starke Konsistenz, dafür wird bei Ausfällen von einzelnen Knoten
zeitweise die Nichtverfügbarkeit einzelner Datensätze in Kauf genommen[2].
Ein Cassandra-Datenspeicher besteht aus einer Anzahl von Knoten, die zur
Laufzeit entfernt und hinzugefügt werden können. Alle Knoten besitzen die gleichen Rollen, es gibt keine Sonderrollen und keinen Single Point of Failure, also
keinen Knoten, dessen Ausfall zum Ausfall des gesamten Systems führt. Jeder
Knoten ist zugleich Speicher-Knoten und Koordinator-Knoten. Die Kommunikation funktioniert nach dem Peer-to-Peer-Modell. Das bedeutet, dass es keine
zentrale Koordinationsinstanz gibt, sondern, dass alle Knoten gleichberechtigt
sind und direkt miteinander kommunizieren [vgl. 12, S. 208], [vgl. 14, S. 3].
Die Philosophie von Cassandra besagt, dass Leseoperationen in linearer Zeit
zur Größe der zurückgegebenen Daten abgeschlossen werden. Bei Ausfällen von
Knoten ist es möglich, dass einige Bereiche zeitweise nicht zum Lesen verfügbar
sind. Allerdings schlagen Schreiboperationen niemals fehl, solange ein einziger
Knoten erreichbar ist und keine Konsistenzanforderungen gestellt werden[14].
2
SQL = Structured Query Language
Ein Index für Volltextsuche, bei dem für jedes Wort gespeichert wird, in welchen Dokumenten
es an welcher Stelle vorkommt.
3
2.2. DATENMODELL
2.2
11
Datenmodell
Während es in relationalen Datenbanksystemen mehrere Datenbanken gibt, verwendet Cassandra sogenannte Keyspaces4 . Ein Keyspace definiert einen eigenen
Namensraum für die enthaltenen Datentypen und muss beim Verbinden vom
Client, der eine Anfrage stellt, angegeben werden. Es kann beliebig viele Keyspaces
geben. In der Regel verwendet man einen Keyspace pro Anwendung.
Cassandra bietet zwei verschiedene Datentypen an: ColumnFamilies und
SuperColumnFamilies. Sie werden über ihren Schlüssel referenziert und sind
eindeutig in einem Keyspace definiert [5], [7].
Eine ColumnFamily kann mit einer Tabelle in einer relationalen Datenbank
ohne fixe Anzahl an Spalten verglichen werden und enthält eine beliebige Menge
von Attributen mit jeweils einem Wert. Die Attribute müssen nicht vorher definiert
und können bei Bedarf hinzugefügt werden. Dabei sind die Attributnamen innerhalb einer ColumnFamily eindeutig. Attribute können beliebige Werte enthalten,
die binär gespeichert werden.
Eine SuperColumnFamily enthält als Attribute eine beliebige Anzahl von
(Unter-)ColumnFamilies. Ein vergleichbarer Datentyp ist ein zweidimensionales
assoziatives Array. Alternativ kann man sich auch eine traditionelle Tabelle mit
einer beliebigen Anzahl an Spalten vorstellen, die jeweils ein Array enthalten [vgl.
14, S.1f].
Im Folgenden werden sowohl Werte zu ColumnFamilies als auch zu SuperColumnFamilies als Datensatz bezeichnet. In Beispiel 2.1 gibt es zwei ColumnFamilies Users und UserItems. Unter der ColumnFamily Users gibt es zwei
Datensätze mit den Schlüsseln a und b. Beide enthalten ein oder mehrere Attribute mit Werten. Außerdem gibt es eine SuperColumnFamily UserCars mit einem
Datensatz a. Dieser enthält zwei (Unter-)ColumnFamilies mit den Schlüsseln Auto
1 und Auto 2. Sie beinhalten jeweils wieder Attribute mit Werten. Zu jedem
Attributwert wird beim Schreiben automatisch ein aktueller Zeitstempel eingefügt.
Dieser wird intern zur Konfliktbehandlung verwendet und kann vom Benutzer
abgefragt werden.
Semantisch gesehen zeigt das Beispiel 2.1 eine kleine Benutzerdatenbank.
Die ColumnFamily Users enthält eine Liste von Benutzern mit verschiedenen
Attributen. In der Family UserItems wird die Anzahl an Gegenständen, welche
4
engl. Schlüsselraum
12
KAPITEL 2. GRUNDLAGEN
MyUserApp
Users
a
age: 27
19:10
b
age: 20
18:55
name: Hans
19:10
gender: m
19:20
UserItems
a
Autos: 2
17:10
Hunde: 5
12:00
UserCars
a
Auto 2
Auto 1
Farbe: rot
20:00
PS: 320
20:02
PS: 80
10:00
Abbildung 2.1: Beispiel Datenbestand gemäß Datenmodell von Cassandra
2.3. KONFIGURATIONSDATEI
13
zu einem Benutzer gehören, gespeichert. Dabei wird der gleiche Schlüssel wie in
der ColumnFamily Users verwendet. Jeder Attributname gibt einen Gegenstand
an, der Attributwert beschreibt die Anzahl der Gegenstände. Außerdem wurde
eine SuperColumnFamily UserCars definiert, welche verschiedene Attribute aller
Autos eines Benutzers angibt. Der Schlüssel ist hier ebenfalls der gleiche Schlüssel
wie beim Benutzer. Die Namen der enthaltenen ColumnFamilies geben die Namen
der Autos an. Die enthaltenen Attribute beschreiben jeweils die Eigenschaften
der Autos.
Das Datenmodell von Cassandra entspricht großteils dem Modell von Googles
BigTable [vgl. 11, S.2], auch der Begriff ColumnFamily wurde von Google geprägt.
SuperColumnFamilies sind eine Erweiterung in Cassandra, die dem ursprünglichen
Einsatzzweck von Cassandra als Volltextindex geschuldet sind. Weiterhin hält
Google BigTables eine einstellbare Anzahl von älteren Versionen eines Attributwertes vor und erlaubt es dem Benutzer diese abzufragen [11]. In Cassandra sind
ältere Versionen eines Wertes für den Benutzer nicht mehr sichtbar.
2.3
Konfigurationsdatei
keyspaces:
- name: MyUserApp
replication_factor: 5
column_families:
- name: Users
compare_with: BytesType
- name: UsersItems
compare_with: BytesType
- name: UsersCars
column_type: Super
compare_with: BytesType
compare_subcolumns_with: BytesType
Abbildung 2.2: cassandra.yaml Beispiel
Die Konfiguration von Cassandra und der ColumnFamilies erfolgt in einer
Konfigurationsdatei namens cassandra.yaml (Auszug mit ColumnFamilies siehe
14
KAPITEL 2. GRUNDLAGEN
Abbildung 2.2; Komplette Datei siehe Anhang A). Es handelt sich beim Inhalt um
strukturierte Daten im Yaml-Format[9], die beim Start von Cassandra eingelesen
werden. Nach einer Änderung einer ColumnFamily oder eines Keyspaces in der Datei muss das erneute Einlesen und die Verarbeitung durch den Benutzer ausgelöst
werden. Alternativ ist das Anlegen, Löschen oder Modifizieren einer ColumnFamily oder SuperColumnFamily auch über eine Wartungsoperationen möglich.
Dauerhaft werden die ColumnFamily-Definitionen intern im System-Keyspace
gespeichert.
Das oberste Element in der Konfigurationsdatei ist ein Keyspace keyspaces.
Für jeden Keyspace müssen der name und der replication factor definiert werden.
Der Name muss eindeutig sein. Der replication factor gibt an auf wie viele Knoten
N ein Datensatz zu einem Schlüssel k repliziert wird. Außerdem können unterhalb
des column families-Elements beliebig viele ColumnFamilies und SuperColumnFamilies definiert werden.
Das Format für eine ColumnFamily enthält Attribute für name, column type
und compare with. column type gibt an, ob es sich um eine SuperColumnFamily
oder eine normale ColumnFamily handelt. Wird der Parameter ausgelassen ist
der Standardwert ColumnFamily. Außerdem wird im Parameter compare with
eine Vergleichsfunktion angegeben. Sie definiert in welcher Reihenfolge die Datensätze anhand ihres Schlüssels über die Knoten und Festplatten verteilt werden.
SuperColumnFamilies besitzen zusätzlich noch ein weiteres Attribut für die Vergleichsfunktion der enthaltenen (Unter-)ColumnFamilies.
2.4
Datenverteilung
Die Verteilung der Daten wurde von Amazon Dynamo inspiriert und funktioniert
sehr ähnlich [12, S. 215f]. Alle Datensätze werden anhand ihres Schlüssels über
alle Knoten verteilt, um Skalierbarkeit und Lastverteilung zu erreichen. Zu diesem Zweck wird Partitionierung mit konsistentem Hashing verwendet. Aus dem
Schlüssel eines jeden Datensatzes wird mit Hilfe einer Hashfunktion ein Hashwert
berechnet. Standardmäßig verwendet Cassandra die md5-Funktion. Mit dem Parameter partitioner kann die Hashfunktion beeinflusst werden. Der Wertebereich
der Hashfunktion wird auf eine Ringstruktur abgebildet. Jedem Knoten wird
eine eindeutige Position auf dieser Ringstruktur zugewiesen (siehe Abbildung
2.4. DATENVERTEILUNG
15
2.3). Er ist dann für alle Hashwerte zwischen seiner Position und der Position
des nächsten Knotens zuständig. Außerdem wird jeder Datensatz auf N-1 weitere
Knoten repliziert, welche dann ebenfalls zuständig sind. Dazu werden jeweils die
nächsten N-1 Knoten nach dem ersten zuständigen Knoten auf der Ringstruktur
gewählt. Der Faktor N kann für jeden Keyspace bei der Konfiguration mit dem
Parameter replication factor eingestellt werden [vgl. 14, S. 2].
Knoten K
Knoten A
Knoten J
Knoten B
Hash(Key k)
Knoten I
Repliziert
Knoten C
Knoten H
Knoten D
Knoten G
Knoten E
N=3
Knoten F
Abbildung 2.3: Verteilung der Daten auf Knoten in einer Ringstruktur
In Beispiel 2.3 liegt der Hashwert des Datensatzes mit dem Schlüssel k auf der
Ringstruktur zwischen den Hashwerten von Knoten B und C. Damit ist Knoten
B der erste zuständige Knoten. Weiterhin wird der Datensatz auf N-1 auf B
folgende Knoten repliziert. In diesem Fall sind das die zwei Knoten C und D,
welche ebenfalls für den Hashwert zuständig sind.
Da alle Knoten nach dem Peer-to-Peer Modell kommunizieren, um voneinander
Daten abzufragen, ist es notwendig zu wissen, welche Knoten aktuell verfügbar
sind. Dazu wird eine Mitgliedsliste mit dem Status aller bekannten Knoten geführt.
16
KAPITEL 2. GRUNDLAGEN
Um bei vielen Knoten ohne großen Aufwand alle Knoten zu überwachen, wird
ein sogenanntes Gossip-Protokoll[15] verwendet. Dabei sendet jeder Knoten seine
Mitgliedsliste periodisch an einen zufällig ausgewählten Knoten. Beim Senden der
Knotenliste wird gleichzeitig überprüft, ob der Zielknoten verfügbar ist. Betritt
ein neuer Knoten den Cassandra-Cluster, so muss er nur einem Knoten bekannt
gemacht werden und wird dann im Laufe der Zeit allen bekannt. Somit haben
nicht alle Knoten immer die gleiche Sicht auf den Datenspeicher. Allerdings ist
dies aufgrund der Architektur von Cassandra kein Problem. Man kann zeigen,
dass in endlicher Zeit ein stabiler Zustand eintritt und die Mitgliedslisten aller
Knoten gleich sind, wenn man lang genug wartet [vgl 12, S. 212f], [vgl. 14, S. 3].
2.5
CAP-Theorem
Availability
Consistency
Partition Tolerance
Abbildung 2.4: Grafische Darstellung des CAP-Theorems
Das CAP-Theorem, welches 2000 von Eric Brewer aufgestellt wurde, besagt,
dass in einem verteilten System von den drei Eigenschaften Konsistenz (Consistency), Verfügbarkeit (Availability) und Partitionstoleranz (Partitiontolerance) nur
2.6. KONSISTENZ
17
maximal zwei gewählt werden können [vgl. 10]. Cassandra wählt Verfügbarkeit
und Partitionstoleranz [4]. Dies bietet den Vorteil, dass zu jedem Zeitpunkt ein
Lese- und Schreibzugriff auf den Speicher möglich ist. Jedoch können die Daten
beim Lesen eventuell veraltet sein. Der Verteilungsmechanismus von Cassandra
entspricht in etwa dem Mechanismus von Amazon Dynamo [vgl. 12, S. 207].
In der Abbildung 2.4 ist zu sehen, dass sich jeweils nur zwei Eigenschaften
überlappen. Allerdings erlaubt Cassandra, wie im nächsten Abschnitt zu sehen sein
wird, in einem gewissen Umfang die Partitionstoleranz zugunsten der Konsistenz
einzuschränken. Das widerspricht in begrenztem Umfang dem CAP-Theorem. Man
sollte die Aussage des Theorems also eher so verstehen, dass es nicht möglich ist
in einem verteilten System alle drei Eigenschaften in vollem Umfang gleichzeitig
zu gewährleisten [vgl. 8].
2.6
Konsistenz
Cassandra bietet also die Möglichkeit Datensätze mittels einer konsistenten Hashfunktion verteilt auf mehreren Knoten redundant zu speichern, um die Verfügbarkeit und Zuverlässigkeit der Daten zu erhöhen. Dabei werden Einschränkungen
der Konsistenz in Kauf genommen. Generell unterscheidet man zwischen drei
verschiedenen Konsistenzanforderungen:
• Strenge Konsistenz (strong consistency) - Wenn ein Schreibvorgang abgeschlossen ist, wird er von allen nachfolgenden Lesezugriffen zurückgeliefert.
Die Reihenfolge der Änderungen ist für alle Knoten immer gleich.
• Schwache Konsistenz (weak consistency) - Nach einem Schreibvorgang
existiert ein Zeitfenster, in dem Änderungen für nachfolgende Lesezugriffe
nicht zurückgeliefert werden. Die Reihenfolge der Änderungen kann von
Knoten zu Knoten variieren.
• Letztendliche Konsistenz (eventual consistency) - Es wird garantiert, dass in
endlicher Zeit alle Änderungen auf allen Knoten in der gleichen Reihenfolge
zurückgegeben werden.
Cassandra implementiert nur letztendliche Konsistenz. Dies hat zur Folge, dass
es während einer Schreiboperation für andere Leseoperationen ein Zeitfenster gibt,
18
KAPITEL 2. GRUNDLAGEN
in dem die Änderungen nicht immer sichtbar sind. Sie können auch auftauchen
und wieder verschwinden, aber in endlicher Zeit werden sie verfügbar sein [vgl.
16].
Für jeden Schlüssel k eines Datensatzes gibt es N zuständige Knoten (entspricht dem replication factor aus Abschnitt 2.3). Bei lesenden sowie schreibenden
Operationen ist es möglich, die Anzahl der Knoten anzugeben, auf die direkt
gelesen bzw. geschrieben werden soll. Dabei sei R die Anzahl an Knoten auf die
gelesen wird und W die Anzahl der Knoten auf die geschrieben. Wird nicht direkt
auf alle N Knoten geschrieben, erfolgt die Replikation der Daten asynchron im
Hintergrund [vgl. 14, S. 3], [vgl. 12, S. 210].
Bei lesenden und schreibenden Operationen ist es in Cassandra möglich, mit
dem Parameter consistency level anzugeben, von wie vielen Knoten die Daten
gelesen oder geschrieben werden. Folgende Werte sind für das consistency level
möglich (Siehe Anhang B):
• ONE - Es wird nur auf einen zuständigen Knoten geschrieben bzw. gelesen.
• QUORUM - Es wird auf (N/2)+1 zuständige Knoten geschrieben bzw.
gelesen. Da die Knotenanzahl in der Regel ungerade ist und N/2 abgerundet wird, überlappen sich die Knoten zweier QUORUM Operationen um
mindestens einen Knoten.
• ALL - Es wird auf alle N zuständigen Knoten geschrieben. Beim Lesen ist
ALL aktuell nicht implementiert.
• ANY - Es wird auf einen zuständigen Knoten geschrieben. Wenn dies
nicht möglich ist, wird die Schreiboperation an einen beliebigen anderen
Knoten geschickt. Sobald ein zuständiger Knoten erreichbar ist, wird die
Operation weitergeleitet und ausgeführt. Im Unterschied zu ONE schlägt
diese Operation nicht fehl, wenn kein zuständiger Knoten erreichbar ist.
Beim Lesen ist ANY nicht sinnvoll und nicht implementiert [vgl. 12, S. 212].
• ZERO - Es wird asynchron auf einen zuständigen Knoten geschrieben, wobei
nicht auf eine Rückmeldung gewartet wird. Ist kein zuständiger Knoten
verfügbar, schlägt die Operation fehl ohne, dass der Client davon etwas
bemerkt. Beim Lesen ist ZERO nicht implementiert..
2.6. KONSISTENZ
19
Allerdings ist nicht immer garantiert, dass nach Schreiboperationen folgende
Leseoperationen den aktuellen Wert lesen. Generell kann man sagen, dass nur
Konsistenz erreicht wird, wenn R + W > N gilt, also die Summe aus den Knoten,
auf die geschrieben wird, und die Knoten, auf die gelesen wird, größer als die
Anzahl der zuständigen Knoten ist. So wird garantiert, dass die Menge der Knoten,
auf die geschrieben wird, sich um mindestens einen Knoten mit der Menge der
Knoten überlappt, auf die gelesen wird[4].
Wird z.B. auf nur einen Knoten geschrieben, ist nicht sichergestellt, dass
nachfolgende lesende Operationen sofort den geschriebenen Wert lesen, wenn
sie nicht von allen zuständigen Knoten lesen. Da nicht jedem Knoten sofort
die Version des Datensatzes mit dem aktuellen Zeitstempel bekannt ist, müssen
mindestens N − W + 1 Knoten nach der aktuellen Version befragt werden, um
sicher mindestens einen Knoten mit dem aktuellen Datensatz zu erreichen. Für
viele Anwendungszwecke ist dies eine elementare Voraussetzung. Es gibt drei
Konstellationen, in denen gewährleistet werden kann, dass auf jeden Fall nach
einem abgeschlossenen Schreibvorgang aktuelle Daten gelesen werden:
• Schreiben auf alle N zuständigen Knoten mit consistency level =ALL. Lesen
von mindestens einem Knoten mit consistency level =ONE. Effizient bei
vielen Lese- und wenig Schreiboperation.
• Schreiben auf (N/2)+1 zuständige Knoten und Lesen von mindestens N/2
zuständigen Knoten, jeweils mit consistency level =QUORUM. Dies ist der
gängigste Arbeitsmodus in Cassandra.
• Schreiben auf einen zuständigen Knoten mit consistency level =ONE und
Lesen von allen N Knoten mit consistency level =ALL. Effektiv bei vielen
Schreib- und wenig Leseoperationen. Allerdings führt der Ausfall eines Knotens in den meisten Fällen zu Datenverlust. Aktuell ist diese Konstellation
nicht in Cassandra implementiert, da beim Lesen consistency level =ALL
nicht unterstützt wird.
In allen anderen Fällen wird nicht garantiert, dass nach einer Schreiboperation
sofort aktuelle Daten geliefert werden. Dabei tritt kein Datenverlust auf, allerdings
kann es eine kurze Zeit dauern, bis alle Änderungen auf allen Knoten ausgeführt
worden sind. Unter den oben genannten Bedingungen sind die Änderungen nach
20
KAPITEL 2. GRUNDLAGEN
Abschließen der Schreiboperation jedoch auf jeden Fall für alle Leseoperationen sichtbar und aufgrund letztendlicher Konsistenz ist auch die Reihenfolge
sichergestellt.
2.7
Operationen
Jede Operation wird vom Client an einen Koordinator-Knoten geschickt, welcher
sie an die zuständigen Knoten weiterleitet. Prinzipiell kann dies jeder Knoten im
Datenspeicher sein. Aufgrund der geringeren Latenz5 bei der Kommunikation ist
es idealerweise ein Knoten, welcher topologisch nah am Client ist. Um die Laufzeit
klein zu halten, verwendet man typischerweise einen Knoten im gleichen Rack
oder zumindest im gleichen Datenzentrum. Da sich die Latenz zwischen einem
Client und dem Koordinator-Knoten meist in einer ähnlichen Größenordnung
befindet wie die Latenz zwischen Koordinator-Knoten und einem Daten-Knoten,
wurde die Funktionalität zum Verbinden mit den zuständigen Knoten in Amazon
Dynamo in die Clients verlagert [vgl. 12, S.217f]. So spart man sich bei der
Kommunikation zwischen Client und Knoten einen Hop im Netzwerk und halbiert
die Latenz der Kommunikation. Direkte Kommunikation mit den Daten-Knoten
ohne Koordinator-Knoten ist in Cassandra nur in einem Javaclient verfügbar und
wird kaum verwendet.
Cassandra Clients verwenden das Thrift-Protokoll[3], um Operationen über
das Netzwerk auszuführen. Dabei handelt es sich um ein RPC-Protokoll6 , für
welches Bindings in fast allen gängigen Sprachen existieren. Es gibt ein externes
RPC-Interface, welches von Clients aufgerufen und vom Koordinator-Knoten
bereitgestellt wird. Zwischen Koordinator-Knoten und Daten-Knoten gibt es
ein weiteres Interface, welches nur primitive Speicheroperationen ermöglicht und
intern vom Koordinator-Knoten benutzt wird.
Bei jeder Operation kann der Client über den Parameter consistency level
angeben, auf wie viele Knoten direkt gelesen bzw. geschrieben werden soll. Damit
kann eine Entscheidung zwischen mehr Konsistenz oder mehr Geschwindigkeit
getroffen werden. Der Koordinator-Knoten sucht anhand der Hashfunktion die
für den Schlüssel zuständigen Knoten und verbindet sich mit ihnen. Alle Opera5
6
Zeit zwischen dem Absenden eine Anfrage und der Ankunft der Antwort
Remote Procedure Call; ermöglicht einen transparenten Methodenaufruf über das Netzwerk
2.7. OPERATIONEN
21
tionen sind nur innerhalb eines Datensatzes innerhalb einer ColumnFamily oder
SuperColumnFamily atomar.
Datensatz mit key k
Client
Koordinator Knoten
N=3
Knoten 1
Knoten 2
Knoten 3
Abbildung 2.5: Ablauf einer Schreiboperation
Bei schreibenden Operationen werden die Daten direkt an die verbundenen,
zuständigen Knoten gesendet und später asynchron auf die restlichen, zuständigen
Knoten repliziert.
Bei lesenden Operationen wird ein zuständiger Knoten nach den jeweils aktuellen Timestamps zu den angefragten Attributen eines Datensatzes zu einem
Schlüssel gefragt und gleichzeitig eine Digest-Anfrage7 an alle verbundenen zuständigen Knoten gestellt, um festzustellen, ob ein anderer Knoten ein Attribut mit
neuerem Timestamp besitzt. Dabei antworten alle Knoten mit dem Timestamp
ihrer aktuellen Version. Gibt es eine neuere Version als die vorhandene, wird
diese geholt und die vorherige verworfen. Danach wird die aktuellere Version vom
Koordinator-Knoten an alle anderen Knoten geschickt, die nicht das aktuellste Timestamp geliefert haben. So werden mögliche fehlende Daten beim Lesen repariert.
Aufgrund dieser relativ aufwändigen Kommunikation und eines Speicherformats,
welches Schreiben in konstanter Zeit und Lesen in logarithmischer Zeit ermöglicht,
sind Leseoperationen teurer als Schreiboperationen.
Wichtige Datenstrukturen für die Leseoperationen (Siehe Anhang B):
• ColumnPath - Gibt einen Pfad zu einem einzelnen Attribut column einer
ColumnFamily oder einer einzelnen SuperColumn einer SuperColumnFamily
7
Digest=Übersicht
22
KAPITEL 2. GRUNDLAGEN
super column innerhalb einer ColumnFamily column family an.
• ColumnParent - Gibt eine ColumnFamily column family und optional eine
SuperColumn super column an. Wird verwendet um alle oder einige Attribute innerhalb einer Column- oder SuperColumnFamily zu beschreiben.
• Column - Enthält Name name, Wert value und Timestamp clock eines
Attributes.
• SuperColumn - Beinhaltet eine SuperColumn einer SuperColumnFamily
mit dem Namen name und einer Liste von Attributen columns vom Typ
Column.
• ColumnOrSuperColumn - Repräsentiert ein Attribut Column oder eine
SuperColumn
• ConsistencyLevel - Ein ENUM8 mit den Werten aus Abschnitt 2.6.
• SliceRange - Beschreibt einen Bereich an Schlüsseln oder Attributnamen.
Mit start und finish werden die Grenzen angegeben. Zusätzlich kann die
Sortierung umgekehrt werden mit reversed oder die Anzahl an Datensätzen
begrenzt werden mit count.
• SlicePredicate - Gibt einen Bereich mit slice range an oder eine Liste von
Attributen mit slice range.
Cassandra unterstützt folgende einfache Operationen:
• set keyspace(string keyspace) - Setzt den Keyspace keyspace für alle folgenden Operationen.
• get(binary key, ColumnPath column path,
ConsistencyLevel consistency level ) - Lesen eines Datensatzes über den
Schlüssel key. Der Parameter column path gibt eine ColumnFamily und eine
SuperColumn oder ein Attributnamen an. Die Operation gibt ein Objekt
vom Typ ColumnOrSuperColumn zurück.
8
Enummeration; Aufzählungstyp mit endlichem Wertebereich
2.7. OPERATIONEN
23
• get slice(binary key, ColumnParent column parent, SlicePredicate predicate,
ConsistencyLevel consistency level ) - Lesen eines Teiles eines Datensatzes
über einen Schlüssel key. Der Parameter column parent gibt eine ColumnFamily an, in welcher alle Attribute, die mit dem Parameter predicate
spezifiziert sind, zurückgegeben werden. Die Operation gibt eine Liste von
ColumnOrSuperColumn Objekten zurück.
• get range slices(ColumnParent column parent, SlicePredicate predicate, SliceRange range, ConsistencyLevel consistency level ) - Lesen mehrerer Datensätze oder Teile davon über einen Schlüsselbereich range. Funktioniert
wie get slice. Zusätzlich ist es möglich mit dem Parameter range einen
Schlüsselbereich anzugeben. Bereichsabfragen sind nur möglich, wenn als
partitioner eine Hashfunktion gewählt wurde, welche die Reihenfolge der
Schlüssel beibehält. Die Operation gibt eine Liste von ColumnOrSuperColumn Objekten zurück.
• multiget slice(list<binary> keys, ColumnParent column parent, SlicePredicate predicate, ConsistencyLevel consistency level ) - Lesen mehrerer Datensätze oder Teile davon über einen Schlüssel key. Funktioniert ähnlich wie
get range slices mit dem Unterschied, dass statt eines Schlüsselbereiches mit
dem Parameter keys eine Liste an nicht zusammenhängenden Schlüsseln
angegeben werden kann. Die Operation gibt eine Liste von ColumnOrSuperColumn Objekten zurück.
• get count(binary key, ColumnParent column parent, ConsistencyLevel consistency level ) - Der Parameter column parent gibt eine ColumnFamily und
optional eine SuperColumnFamily an. Dabei wird die Anzahl der Attribute
in der Column- oder SuperColumnFamily gezählt, denen unter dem Schlüssel
key ein Wert zugeordnet worden ist und die nicht zuvor gelöscht wurden.
Die Operation gibt eine positive Ganzzahl zurück.
• insert(binary key, ColumnParent column parent, Column column, ConsistencyLevel consistency level ) - Schreiben eines Attributes column mit einem
Timestamp und einem Wert in die in column parent angegebene ColumnFamily oder SuperColumnFamily.
• remove(binary key, ColumnParent column path, Clock timestamp, Consisten-
24
KAPITEL 2. GRUNDLAGEN
cyLevel consistency level ) - Löschen eines Datensatzes oder eines Attributes
eines Datensatzes über seinen Schlüssel.
• batch mutate(map<string, Mutation> mutation map,
ConsistencyLevel consistency level ) - Modifizieren (Löschen oder Schreiben)
mehrerer Datensätze über einen Schlüssel auf einmal. Der Parameter mutation map enthält eine Liste von insert- und/oder remove-Operationen.
Dadurch, dass mehrere Operationen auf einmal ausgeführt werden, wird die
Gesamtzeit für die Ausführung geringer, da zwischen den Operationen nicht
auf eine Rückgabe gewartet wird.
Alle Operationen sind innerhalb eines Datensatzes atomar [14]. Schreibende
Operationen können nicht fehlschlagen, solange eine Verbindung zu einem beliebigen Knoten hergestellt werden kann und das consistency level auf ANY oder
ZERO gestellt ist (siehe Abschnitt 2.6). Alle Operationen und Datenstrukturen
sind in der Datei interface/cassandra.thrift definiert (Siehe dazu Anhang B).
2.7.1
Beispiele
Um die Funktion der Operationen zu verdeutlichen, werden nun beispielhafte
Operationen auf die Datensätze in Abbildung 2.6 ausgeführt. Als erstes wird mit
set keyspace(’MyUserApp’) der Keyspace gesetzt.
MyUserApp
Users
a
age: 27
19:10
name: Hans
19:10
gender: m
19:20
b
age: 20
18:55
c
age: 24
08:00
name: Jan
01:33
gender: m
15:00
d
name: Udo
17:10
gender: m
17:30
Abbildung 2.6: Beispiel Datensätze gemäß Datenmodell von Cassandra
2.7. OPERATIONEN
25
• insert(’b’, {column family = ’Users’}, {name = ’age’, value = ’20’, clock =
’18:55’}, ONE) - Erstellen von Datensatz b.
• get(’a’, {column family = ’Users’, column = ’age’}, QUORUM) - Anfrage
nach dem Attribut age unter dem Schlüssel a in der ColumnFamily Users.
Gibt ein Objekt vom Typ ColumnOrSuperColumn zurück:
{name = ’age’, value = ’27’, clock = ’19:10’}.
• get count(’a’, {column family = ’Users’}, QUORUM) - Zählt die Anzahl an
Attributen in Datensatz a. Gibt 3 zurück.
• get range slices({column family = ’Users’}, {start = ’a’, finish = ’h’},
{start=’a’, finish=’c’}, ONE) - Fragt nach allen Datensätzen in der ColumnFamily Users mit den Schlüsseln zwischen a und c. Dabei werden die
Attribute zwischen a und h abgefragt. Zurückgegeben wird die Liste:
a => {{name = ’age’, value = ’27’, clock = ’19:10’}, {name = ’gender’,
value = ’m’, clock = ’19:20’}},
b => {{name = ’age’, value = ’20’, clock = ’18:55’}},
c => {{name = ’age’, value = ’24’, clock = ’08:00’}, {name = ’gender’,
value = ’m’, clock = ’15:00’}}
26
KAPITEL 2. GRUNDLAGEN
Kapitel 3
Konzept
Die Zielsetzung der Arbeit ist es, Indexe auf dem verteilten Datenspeicher Cassandra zu implementieren. Dazu wird ein Konzept vorgestellt, welches besonders die
Eigenheiten von Cassandra berücksichtigt. Dabei soll ein Verzeichnis aufgebaut
werden, um effizient alle Datensätze mit bestimmten Attributwerten zu einem oder
mehreren festen Attributnamen zu finden. Danach wird ein Speicherformat definiert. Weiterhin werden Veränderungen im Ablauf von schreibenden Operationen
erklärt und Operationen beschrieben, die auf den Index lesen.
3.1
Übersicht
Der nahe liegende Ansatz zum Aufbauen eines Indexes ist, beim Schreiben von
neuen Datensätzen einen Eintrag in einem Verzeichnis einzufügen. Wird der Datensatz geändert, muss zuvor der alte Datensatz gelesen und der alte Eintrag im
Verzeichnis entfernt werden. Danach wird ein neuer Eintrag erstellt. Leider ist
dieser Ansatz nicht direkt geeignet, da weder Transaktionen noch Sperren von
Datensätzen in Cassandra existieren und nur einzelne Operationen innerhalb eines
einzelnen Datensatzes atomar sind. Es ist daher nicht möglich sicherzustellen,
dass bei zwei gleichzeitig stattfindenden Änderungen an einem Datensatz keine
Race Conditions bei der Indexaktualisierung auftreten, da nicht sicher entschieden
werden kann, welches der aktuellste Datensatz ist. Beide Operationen könnten
den gleichen älteren Datensatz lesen und beide den gleichen älteren Indexeintrag
löschen. Danach würden beide einen neuen Indexeintrag schreiben. Dies ist ohne
Sperren oder Transaktionen nicht zu verhindern. Als Folge wären zwei Indexein27
28
KAPITEL 3. KONZEPT
träge zu einem Datensatz vorhanden und der Index dauerhaft inkonsistent.
Es wäre nun möglich, Sperren in Cassandra zu implementieren. Allerdings
widerspräche es der Philosophie von Cassandra, dass Schreibzugriffe niemals
fehlschlagen und Leseoperationen eine lineare Laufzeit haben, da sie nun potentiell auf Sperren warten müssten. Transaktionen dagegen würden das Problem
potentiell beheben. Leider sind sie in einem verteilten System nur sehr teuer
zu implementieren und mit dem Konzept von letztendlicher Konsistenz nicht
vereinbar.
Wenn man nun davon ausgeht, dass weder Transaktionen noch Sperren verfüg-bar sind und Race Conditions wie oben gezeigt nicht hinnehmbar sind, wird
eine Möglichkeit benötigt, um das Aktualisieren des Indexes atomar vorzunehmen.
Aus diesem Grund ist es nicht möglich, den alten Datensatz vor der Änderung
zu lesen, den alten Index zu löschen und danach den neuen Datensatz und den
neuen Indexeintrag zu schreiben. Zusätzlich sind lesende Operationen aufgrund des
Speicherformats von Cassandra teurer als schreibende Operationen. Aus diesem
Grund könnte man nun das Entfernen des alten Indexeintrages unterlassen. Das
hat natürlich zur Folge, dass der Index viel mehr Einträge enthält als es passende
Datensätze gibt. Es wird also in Kauf genommen, dass veraltete Einträge im
Index vorhanden sind. Um dieses Problem zu beseitigen, wird später beim Lesen
vom Index die Korrektheit des Eintrages überprüft, indem das Timestamp des
Indexeintrages mit dem Timestamp des indexierten Attributs des Datensatzes
verglichen wird. Ist das Timestamp der Indexeintrages älter als das Timestamp
des gefundenen indexierten Attributs, wird der Indexeintrag entfernt. Ist das
Timestamp neuer so wird der Indexeintrag übersprungen, da der Datensatz
wahrscheinlich noch nicht geschrieben wurde. Sind die Timestamps gleich ist der
Eintrag korrekt und der Datensatz wird zurückgegeben.
3.2
Index-Speicherformat
Der Index wird intern als ColumnFamily mit der angegebenen Vergleichsfunktion
im gleichen Keyspace wie die indexierten Datensätze definiert. Der Schlüssel
eines indexierten Datensatzes wird als Attributname mit dem Timestamp des
Datensatzes und leerem Wert in der ColumnFamily des Indexschlüssels gespeichert.
Der Index ist damit wie jeder normale Datensatz verteilt über alle Knoten von
3.2. INDEX-SPEICHERFORMAT
29
Cassandra gespeichert.
MyUserApp
Users
a
age: 27
19:10
b
age: 20
18:55
c
age: 27
13:10
name: Hans
19:10
gender: m
19:20
UsersAgeIndex
20
b:
18:55
27
a:
19:10
c:
13:10
Abbildung 3.1: Speicherformat eines Indexes
In Beispiel 3.1 werden drei Benutzer in der ColumnFamily Users gespeichert.
Es gibt einen Index über das Attribut mit dem Namen age. Der Index wird in
der ColumnFamily UsersAgeIndex gespeichert. Zum Wert 27 des Attributes mit
dem Namen age gibt es zwei Attribute mit jeweils leerem Wert. Dabei entspricht
der Attributname dem Datensatz, auf den referenziert wird, im Beispiel also
die Datensätze mit den Schlüsseln a und c. Analog zeigt der Wert 20 auf den
Datensatz mit dem Schlüssel b. Dabei ist zu beachten, dass die Timestamps der
Indexeinträge und der Datensätze übereinstimmen. Bei Attributkombinationen
gäbe es theoretisch mehrere Timestamps, jedoch wird beim Schreiben sichergestellt,
dass immer auf alle Indexattribute gleichzeitig geschrieben wird. Daher haben in
diesem Fall alle Attribute das gleiche Timestamp und es muss nur das Timestamp
eines Attributs überprüft werden. Allerdings ist es dazu wichtig, dass wie in
Abschnitt 3.5 beschrieben, alle Schreiboperationen, welche nicht alle Attribute
eines zusammengesetzten Indexes enthalten, abgelehnt werden.
30
KAPITEL 3. KONZEPT
3.3
Korrektheit
Elementare Voraussetzung für eine Indeximplementierung ist, dass nach konkurrierenden Lese- und Schreiboperationen letztendlich ein konsistenter Zustand eintritt.
Dazu muss zu jedem Datensatz mit vorhandenem Indexattribut genau ein Indexeintrag vorhanden sein. Da alle Datensätze nur letztendlich konsistent sind und
Indexeinträge erst beim Lesen gelöscht werden, muss sichergestellt werden, dass
nur Indexeinträge gelöscht werden zu denen auf keinen Fall noch ein Datensatz
existieren kann. Wir zu einem Indexeintrag nicht der korrekte Datensatz gefunden,
wird er übersprugen und es gilt zu prüfen, ob der Indexeintrag gelöscht werden
kann, um die Überprüfung beim nächsten Lesezugriff nicht erneut durchführen zu
müssen. Es gibt drei verschiedene Fälle:
• Der referenzierte Datensatz wurde nicht gefunden. Dabei kann es sein, dass
bisher nur der Indexeintrag, aber der Datensatz noch nicht geschrieben
wurde. Alternativ kann auch der Datensatz gelöscht worden sein. Da beide
Fälle nicht unterschieden werden können, muss so lang gewartet werden bis
ausgeschlossen werden kann, dass der Datensatz geschrieben wurde. In der
Regel sollte hier eine Zeit von einem Tag verwendet werden.
• Der gefundene Datensatz hat ein älteres Timestamp als der Indexeintrag. Es
kann davon ausgegangen werden, dass der Datensatz noch nicht geschrieben
wurde und es muss gewartet werden.
• Der gefundene Datensatz hat ein neueres Timestamp als der Indexeintrag. In
diesem Fall kann der Indexeintrag sofort gelöscht werden, da das Timestamp
eines Datensatzes unter keinen Umständen wieder älter werden kann.
Um ein potentielles Problem zu demonstrieren, werden im Sequenzdiagramm
3.2 gleichzeitig eine Lese- und eine Schreiboperation auf die Datensätze in Abbildung 3.3 ausgeführt. Die schwarzen Datensätze stellen die Ausgangssituation
dar. Im Verlauf wird ein Indexeintrag von Client2 gelesen, bevor der Datensatz
zum Indexeintrag von Client1 geschrieben wurde. Beide Clients verwenden jeweils einen eigenen Koordinator-Knoten, welche mit Cassandra1 und Cassandra2
gekennzeichnet sind.
Client1 ändert im Datensatz a das indexierte Attribut age auf 20. Zuerst
wird vom Koordinator-Knoten Cassandra1 ein Indexeintrag erstellt, der für den
3.3. KORREKTHEIT
: Client1
31
: Client2
: Cassandra1
: Cassandra2
: Cassandra Backend
: Schreibe a mit age=20
: Schreibe Index von 20=a
: Lese age=20
: Lese Index von 20
: Lese a
: Lösche a in Index von 20
: Schreibe a
Abbildung 3.2: Race Condition beim Indexschreiben/-lesen
MyUserApp
Users
2.
a
age: 27 20
19:10 20:00
b
age: 20
18:55
c
age: 27
13:10
name: Hans
19:10
gender: m
19:20
1.
UsersAgeIndex
20
b:
18:55
a:
20:00
27
a:
19:10
c:
13:10
Abbildung 3.3: Datensätze für Race Condition Beispiel
32
KAPITEL 3. KONZEPT
Wert 20 auf den Schlüssel a des Datensatzes verweist (siehe 1. in Abbildung 3.3).
Zu diesem Zeitpunkt wurde der Wert und das Timestamp des Attributs age im
Datensatz a noch nicht geändert.
Gleichzeitig liest Client2 alles Datensätze zum Wert 20 vom Index. Cassandra
liest alle Indexeinträge und versucht den zugehörigen Datensatz zu lesen. Leider
existiert für den von Client1 geschriebenen Indexeintrag im Datensatz a zum
Attribut age ein älteres Timestamp. Es gilt an dieser Stelle zu überprüfen, ob der
Indexeintrag gelöscht werden kann. Laut der obigen Liste muss gewartet werden.
Würde man nicht warten und den Indexeintrag wie im Beispiel löschen, obwohl
Client1 den Datensatz später noch schreibt, gäbe es keinen Indexeintrag mehr
zum Datensatz a und der Index wäre nicht mehr korrekt. Aus diesem Grund ist
es unerlässlich das Löschen von Indexeinträgen abhängig vom Timestamp erst
nach längerer Zeit vorzunehmen, damit der Index unter allen Umständen korrekt
bleibt.
3.4
Index definieren
Als nächstes wird beschrieben, welche Informationen notwendig sind um ein
Index auf einer ColumnFamily zu definieren. Alle ColumnFamilies werden in
der Konfigurationsdatei cassandra.yaml definiert oder alternativ beim Start aus
dem System-Keyspace geladen. Beide sollen nun um einen Elementtyp für Indexe
erweitert werden. Das Element soll folgende Attribute enthalten:
• name - Muss ein im Keyspace eindeutiger Name für den Index sein.
• index type - Gibt den Namen der Indeximplementierung an, die verwendet
werden soll.
• compare with - Die Vergleichsfunktion gibt die Verteilung der Indexdatensätze über Knoten und Festplatten an und ist für Bereichsabfragen über
den Index wichtig. Entspricht den Vergleichsfunktionen auf ColumnFamilies.
• keys - Liste mit Attributnamen die indexiert werden sollen
Außerdem sollen beliebige Elemente als Optionen möglich sein, welche von der
konkreten Indeximplementierung ausgewertet werden. Die Indeximplementierung
in index type wird bei jedem Schreibzugriff auf die ColumnFamily sowie bei allen
3.5. INDEX AKTUALISIEREN
33
Lesezugriffen auf den Index aufgerufen. Die Vergleichsfunktion compare with gibt
die Verteilung der Indexdatensätze über Knoten und Festplatten an.
3.5
Index aktualisieren
Wenn auf einer ColumnFamily Indexe definiert sind, muss bei jedem Schreibzugriff
überprüft werden, ob die Indexe aktualisiert werden müssen. Da jeder Schreibzugriff zuerst an einen beliebigen Koordinator-Knoten geschickt wird, bietet es sich
an, die Indexaktualisierung auf diesem Knoten auszuführen. Für jeden Index muss
in der Indeximplementierung überprüft werden, ob eine Aktualisierung notwendig
ist und sie gegebenen Falles durchgeführt werden.
Knoten 7
Knoten 8
Knoten 9
Index
1.
N=3
Client
Koordinator Knoten
2.
Knoten 1
Knoten 2
Knoten 3
Data
Abbildung 3.4: Ablauf einer Schreiboperation mit Indexaktualisierung
In der konkreten Implementierungsidee dieser Arbeit (siehe Abschnitt 3.1) muss
lediglich überprüft werden, ob der Schreibzugriff auf alle der vorher definierten
Indexattribute schreibt und die entsprechenden Indexeinträge erstellen. Sollte
sich der Schreibzugriff nur auf einige Attribute eines Indexes erstrecken, wird die
Schreiboperation abgelehnt, da es notwendig wäre, alle fehlenden Attribute zu
lesen. Weil nicht sichergestellt werden kann, welches der aktuelle Datensatz ist, da
der Zugriff nicht atomar erfolgen kann, ist dies nicht lösbar (siehe Abschnitt 2.6).
Es ist daher zwingend nötig, bei Änderungen der Indexattribute immer auf alle
Attribute zu schreiben, selbst wenn sich nur ein einziges ändert. Dieses Problem
34
KAPITEL 3. KONZEPT
tritt nur bei Indexen über mehrere Attribute auf. Eigentlich widerspricht das
Ablehnen einer Schreiboperation der Philosophie von Cassandra, jedoch ist diese
Schreiboperation in diesem Fall als ungültig zu werten, da sie die Integrität eines
Indexes dauerhaft zerstören würde. Als Folge muss ein Anwendung immer alle
indexierten Attribute lesen, um sie dann wieder schreiben zu können.
Sind nun die Werte aller Attribute vorhanden, so wird daraus ein Schlüssel
für den verteilten Index generiert und es wird der Indexeintrag geschrieben
(siehe Abbildung 3.4). Erst wenn die Schreiboperation auf den Indexeintrag
abgeschlossen ist, kann der Datensatz selber geschrieben werden. Es ist also
die doppelte Latenz beim Schreiben zu erwarten. Trotzdem wird sichergestellt,
dass sowohl der Indexdatensatz als auch der indexierte Datensatz das gleiche
Timestamp erhalten und letztendliche Konsistenz erreicht wird.
3.6
Index abfragen
Im Gegensatz zu Anfragen an relationale Datenbanksysteme müssen in Cassandra
Anfragen vom Benutzer explizit gegen einen Index gestellt werden, da die vorhandenen Operationen (Siehe Abschnitt 2.7) nur Zugriffe über den Schlüssel eines
Datensatzes erlauben. Wie bei normalen Datensätzen gesehen, wird es auch bei
Indexabfragen möglich sein Abfragen auf einen Wert, mehrere Werte oder einen
Bereich von Werten durchzuführen. Dazu werden später in Kapitel 4 konkrete
neue Operationen vorgestellt.
Um Datensätze in einem Index abzufragen, wird wie beim Schreiben aus den
Werten der indexierten Attribute ein Indexschlüssel berechnet. Damit werden
alle Indexeinträge zu diesem Schlüssel gelesen. Das geschieht mit den aus Abschnitt 2.7 bekannten Operationen. Durch die gemachten Einschränkungen beim
Schreiben müssen alle Indexeinträge beim Lesen auf ihre Aktualität überprüft
werden. Dies geschieht durch eine Digest-Anfrage auf alle in einem Eintrag referenzierten Datensätze. Ist ein Datensatz zu einem Eintrag nicht zu finden oder
ist das Timestamp des referenzierten Datensatzes nicht gleich dem Timestamp
des Indexeintrages, wird der entsprechende Indexeintrag gelöscht und muss bei
der nächsten Abfrage nicht wieder überprüft werden. Einzige Ausnahme von
dieser Regel sind Indexeinträge, die noch sehr neu sind, da der Schreibvorgang
des indexierten Datensatzes noch nicht abgeschlossen sein muss. Alle gefundenen
3.7. INDEX (NEU-)AUFBAUEN
35
korrekten Datensätze werden zurückgegeben.
Eine Besonderheit stellen zusammengesetzte Indexe dar. Dabei müssen mehrere
Attributwerte auf einen Indexschlüssel abgebildet werden. Um Abfragen auf das
erste Attribut zu stellen, wird eine Bereichsanfrage gestellt, welches die hinteren
Teile des Schlüssels ignoriert. Eine Bereichsabfrage über den Index ist aufgrund der
Reihenfolge der Attributwerte im Indexschlüssel nur über die vordersten Attribute
effizient möglich. Wird eine Bereichsanfrage über ein anderes Attribut gestellt,
so müssen alle Werte der davor liegenden Attribute gelesen werden. Somit sind
effizient keine Einschränkungen des Bereichs für die davor liegenden Attribute
möglich. Bei der Konfiguration spielt also die Reihenfolge der Attribute von
zusammengesetzten Indexen eine wesentliche Rolle.
3.7
Index (neu-)aufbauen
Nach der Definition eines Indexes auf einer nicht-leeren ColumnFamily ist es
notwendig, einen Index initial aufzubauen. Auch nach Ausfällen kann es notwendig
sein, einen Index erneut zu initialisieren. Zuerst werden alle Indexeinträge zu dem
Index sequentiell gelöscht. Danach wird eine get range slice-Operation über den
gesamten indexierten Schlüsselraum ausgeführt und alle am Index beteiligten
Attribute abgefragt. Für jeden Schlüssel wird dann eine update index -Operation
ausgeführt. So werden alle Schlüssel im Index abgelegt. Währenddessen können
bereits normale Schreiboperationen ausgeführt werden. Leseoperationen werden
allerdings erst sicher korrekt beantwortet, wenn der Vorgang fertig ist.
3.8
Index überprüfen/reparieren
Eine Indexüberprüfung kann nach Ausfällen von Knoten oder während der Entwicklung sinnvoll sein, da sie günstiger ist als ein Neuaufbau des Indexes. Dazu
muss sichergestellt werden, dass Indexeinträge und indexierte Datensätze bijektiv
sind. Als Folge muss überprüft werden, dass jeder Verweis von Indexeintrag auf
einen Datensatz injektiv und surjektiv ist. Da beim Lesen alle ungültigen Indexdatensätze herausgefiltert werden und jeder Datensatz nur einen einzigen Schlüssel
haben kann, wird sichergestellt, dass die Funktion immer injektiv ist. Um zu
überprüfen, ob sie auch surjektiv ist, wird eine get range slice-Operation über
36
KAPITEL 3. KONZEPT
den gesamten indexierten Schlüsselraum ausgeführt und alle am Index beteiligten
Attribute abgefragt. Für jeden Schlüssel wird der entsprechende Indexschlüssel
berechnet und abgefragt. Ist er nicht mit dem gleichen Timestamp vorhanden,
wird ein Fehler ausgegeben und ggf. korrigiert.
So wird erreicht, dass für jeden Datensatz genau ein Indexeintrag vorhanden
ist und dass zu jedem Indexeintrag ein Datensatz vorhanden ist. Wobei beachtet
werden muss, dass jeder Indexeintrag nur zurückgegeben wird, wenn der Datensatz
existiert. So wird sichergestellt, dass kein Datensatz im Index fehlt und auch nicht
von mehreren Indexeinträgen referenziert wird.
Kapitel 4
Implementierung
thrift.CassandraServer
1.
8.
2.
service.StorageProxy
db.ICassandraIndex
3.
6.
7.
4.
5.
service.StorageService
Abbildung 4.1: Ablauf einer Schreiboperation in Cassandra mit Index
In diesem Kapitel werden zunächst alle konkreten neuen Operationen, die in
Kapitel 3 theoretisch beschrieben wurden, vorgestellt, die später zusammen mit
den bestehenden Operationen aus Abschnitt 2.7 für Clients nutzbar sein sollen.
Danach wird ein Interface vorgeschlagen, das intern für alle Indeximplementierungen genutzt werden soll. Dadurch ist es möglich, verschiedene Implementierungen
nebeneinander in der gleichen Instanz zu nutzen. Gleichzeitig wird als Teil der
Arbeit die konkrete Implementierung eines Indexes IndirectDictionary, die das
Interface implementiert, beschrieben. Als nächstes werden die Datenstrukturen
zur Laufzeit erläutert und eine Erweiterung der Konfigurationsdatei vorgenommen.
Zuletzt werden die Implementierungen der Lese-, Schreib- sowie Wartungsope37
38
KAPITEL 4. IMPLEMENTIERUNG
rationen beschrieben. Im Folgenden sind alle Namen von Paketen und Klassen
relativ zum Pfad org.apache.cassandra.
In Abbildung 4.1 sieht man die Namen der Klassen, welche bei einer Schreiboperation involviert sind. Die Pfeile stellen den Ablauf der Aufrufe dar. Zuerst
kommt die Anfrage im externen Interface in der Klasse thrift.CassandraServer an.
Dort wird sie ausgepackt und an die Klasse service.StorageProxy weitergegeben
(1). An dieser Stelle wird die update index -Funktion einer Indeximplementierung
aufgerufen, die das Interface db.ICassandraIndex implementiert (2). Um den
Indexeintrag zu schreiben verwendet die Implementierung nun wieder die Klasse
service.StorageProxy (3), welche den zu schreibenden Datensatz an die Klasse
service.StorageService weitergibt um ihn zu speichern (4). Nach dem der Index
geschrieben wurde (5), wird der eigentliche Datensatz geschrieben (6). Danach
wird die erfolgreiche Ausführung der Schreiboperation gemeldet (7,8).
4.1
Externes Interface
Damit ein Index für eine Anwendung nutzbar wird, müssen zusätzliche Methoden
zum Abfragen in das externe Interface exportiert werden. Sie orientieren sich
stark an den normalen Leseoperationen von Cassandra. Dabei werden jeweils die
Datensätze und nicht nur die Schlüssel der Datensätze zurückgegeben. Aufgrund
der mangelnden Atomizität ist dies für die meisten Anwendungszwecke vorteilhaft.
Da beim Lesen die Datensätze sowieso zur Überprüfung gelesen werden, entsteht
dadurch kaum zusätzlicher Aufwand.
Alle Anfragen von Clients werden in einer Instanz der Klasse thrift.CassandraServer verarbeitet und jeweils nach Überprüfung von Berechtigungen an eine
Instanz der Klasse service.StorageProxy weitergeleitet, wo die Logik für die Abarbeitung einer Anfrage implementiert wird (siehe Abbildung 4.1). Folgende neue
Funktionen werden implementiert (Siehe Anhang B):
• index get(String index name, Map<byte[],byte[]> key map, ColumnPath
column path, ConsistencyLevel consistency level ) - Lesen aller Datensätze zu
einem Indexeintrag. index name gibt den Namen des Indexes im Keyspace
keyspace an. Der column path kann optional die Abfrage auf ein Attribut
oder eine SuperColumn in den zurückgegebenen Datensätzen begrenzen.
4.2. INTERNES INTERFACE
39
Die key map enthält eine Liste von indexierten Attributnamen und Attributwertpaaren, auf denen der Index abgefragt wird. Das consistency level
gibt, wie in Abschnitt 2.6 beschrieben an, auf wie viele Knoten beim Lesen
zugegriffen wird.
• index get slice(Stringindex name, Map<byte[],byte[]> key map, ColumnParent column parent, SlicePredicate predicate,
ConsistencyLevel consistency level ) - Entspricht index get. Jedoch kann über
das predicate eine Liste oder ein Bereich von Attributen oder SuperColumns
unterhalb des column parent in den zurückgegebenen Datensätzen abgefragt
werden.
• index get range slices(String index name, ColumnParent column parent,
SlicePredicate predicate, KeyMap Range key map range, ConsistencyLevel
consistency level ) - Entspricht index get slice. Hier ist es aber möglich statt
einer Liste von indexierten Attributnamen und Attributwertpaaren einen
Bereich in key map range anzugeben. Vorausgesetzt wird, wie bei allen
Bereichsanfragen, dass als partitioner eine Hashfunktion gewählt wurde,
welche die Reihenfolge der Schlüssel beibehält (siehe Abschnitt 2.4).
• index multiget slice(String index name,
List<Map<byte[],byte[]>> key map list, ColumnParent column parent,
SlicePredicate predicate, ConsistencyLevel consistency level ) - Entspricht
index get slice. Weiterhin ist es in key map list möglich statt einer Liste
von indexierten Attributnamen und Attributwertpaaren mehrere Listen
anzugeben.
4.2
Internes Interface
Die Klasse service.StorageProxy leitet einige Operationen an eine Indeximplementierung weiter, welche das folgende Interface ICassandraIndex implementieren
muss. Dabei handelt es sich um folgende Funktionen, welchen den Funktionen des
externen Interface ohne index name entsprechen:
• Konstruktor(String keyspace, String name, AbstractType compare with,
List<String> column name, List<String> options) - Initialisiert das IndexObjekt und wird beim Laden der Indeximplementierung aufgerufen. Es
40
KAPITEL 4. IMPLEMENTIERUNG
müssen der Keyspace keyspace, der Name der Indeximplementierung name,
die Vergleichsfunktion compare with und die zu indexierenden Attributnamen column name übergeben werden (Genaueres siehe Parameter keys
in Abschnitt 3.4). Außerdem können Optionen mit dem Parameter option
übergeben werden.
• indexUpdate(RowMutation mutation map,
ConsistencyLevel consistency level ) - Überprüft, ob der Index beim Schreiben auf einer Liste von Attributen aktualisiert werden muss. Berechnet
Lösch- und Schreiboperationen auf Indexdatensätzen und führt diese aus.
Wird von der Klasse service.StorageProxy aufgerufen, wenn eine Schreiboperation ausgeführt wird. Übergeben werden als Parameter ein Schlüssel
key von einem Datensatz, eine Liste der Änderungen mutation map, dabei
handelt es sich um jeweils eine Schreiboperation auf ein Attribut, und das
consistency level.
• indexGet(Map<byte[],byte[]> key map, ColumnPath column path, ConsistencyLevel consistency level ) - Lesen eines einzelnen Datensatzes über den
Index.
• indexGetSlice(Map<byte[],byte[]> key map, ColumnParent column parent,
SlicePredicate predicate, ConsistencyLevel consistency level ) - Lesen eines
Teildatensatzes über den Index.
• indexGetRangeSlices(ColumnParent column parent, SlicePredicate predicate,
KeyMapRange key map range, ConsistencyLevel consistency level ) - Lesen
mehrerer Datensätze oder Teile davon über einen Indexbereich.
• indexMultiget slice(List<Map<byte[],byte[]>> key map list,
ColumnParent column parent, SlicePredicate predicate, ConsistencyLevel
consistency level ) - Lesen mehrerer Datensätze oder Teile davon über den
Index.
• indexRebuild() - Baut den Index neu auf. Siehe Abschnitt 3.7.
• indexValidate() - Überprüft die Korrektheit des Indexes. Siehe Abschnitt
3.8.
4.3. BEISPIELE
4.3
41
Beispiele
Um die Funktion der neuen Operationen aus Abschnitt 4.1 zu verdeutlichen, werden nun beispielhafte Operationen auf die Datensätze in Abbildung 4.2 ausgeführt.
Als erstes wird mit set keyspace(’MyUserApp’) der Keyspace gesetzt.
MyUserApp
Users
a
age: 27
19:10
b
age: 20
18:55
c
age: 27
13:10
name: Hans
19:10
gender: m
19:20
UsersAgeIndex
20
b:
18:55
27
a:
19:10
c:
13:10
Abbildung 4.2: Beispieldatensätze für Indexabfragen
• index get(’UserAgeIndex’, age=’27’, {column family = ’Users’, column =
’age’}, QUORUM) - Anfrage nach dem Attribut age aller Datensätze in der
ColumnFamily Users mit Attribut age = 27 über den Index UserAgeIndex.
Gibt eine Liste mit zwei Elementen zurück:
a => {{name = ’age’, value = ’27’, clock = ’19:10’}},
c => {{name = ’age’, value = ’27’, clock = ’13:10’}}.
• index get slice(’UserAgeIndex’, age=’27’, {column family = ’Users’}, start
= ’a’, finish = ’h’, ONE) - Fragt nach allen Datensätzen in der ColumnFamily Users mit age = 27 über den Index UserAgeIndex. Dabei werden
die Attribute zwischen a und h abgefragt. Zurückgegeben wird die Liste:
a => {{name = ’age’, value = ’27’, clock = ’19:10’}, {name = ’gender’,
42
KAPITEL 4. IMPLEMENTIERUNG
value = ’m’, clock = ’19:20’}},
c => {{name = ’age’, value = ’27’, clock = ’13:10’}}
4.4
Konfigurationsinterface
Beim Start von Cassandra wird die Konfigurationsdatei cassandra.yaml bei der
Instanziierung der Klasse config.DatabaseDescriptor in die Klasse config.Config
eingelesen, welche auf diverse weitere Datentypen verweist. Unter anderem wird
für jeden Keyspace eine Instanz der Klasse config.Keyspace erzeugt. Pro Instanz
werden alle ColumnFamilies in Instanzen der Klasse config.ColumnFamily eingelesen.
Config
+ cluster_name : string
+ partitioner : string
+ keyspaces : RawKeyspace
+
+
+
+
+
+
+
+
+
+
+keyspaces +
+
+
RawColumnFamily
name : string
column_type : ColumnFamilyType
compare_with : string
compare_subcolumns_with : string
comment : string
rows_cached : double
keys_cached : double
read_repair_chance : double
indexes : RawIndex
RawKeyspace
name : string
replica_placement_strategy : string
replication_factor : int
column_families : RawColumnFamily
+column_families
+indexes
+
+
+
+
+
RawIndex
name : char
index_type : string
compare_with : string
column_name : string[]
options : string[]
Abbildung 4.3: UML-Diagramm der Klassen, welche die Konfigurationsdatei
abbilden im Package config
Im Beispiel der Abbildung 4.4 sieht man zwei Indexe, die in der Konfigurationsdatei cassandra.yaml mit den Erweiterungen aus Abschnitt 3.4 definiert werden.
Beide werden auf der ColumnFamily Users definiert. Bei einem Schreibzugriff auf
das in column name angegebene Attribut age wird während der Operation der
Index mit dem in name spezifizierten Namen UsersAgeIndex aktualisiert. Dabei
wird die beispielhafte Indeximplementierungen IndirectDictionary verwendet. Um
Bereichsabfragen über den Index zu ermöglichen, sollen alle Indexeinträge mit der
Vergleichsfunktion BytesType sortiert werden, die in compare with konfiguriert
4.4. KONFIGURATIONSINTERFACE
43
keyspaces:
- name: MyUserApp
replication_factor: 5
column_families:
- name: Users
compare_with: BytesType
indexes:
- name: UsersAgeIndex
# Benutze einen indirekten Index
index_type: IndirectDictionary
# Sortiere Datensätze binär
compare_with: BytesType
# Index auf Spalte Age
column_name: age
- name: UsersGenderIndex
# Benutze einen in Memory Index
index_type: InMemoryDictionary
# Sortiere Datensätze binär
compare_with: BytesType
# Index auf Spalte Geschlecht
column_name: gender
- name: UsersItems
compare_with: BytesType
Abbildung 4.4: cassandra.yaml Index Beispiel
wird. Äquivalent funktioniert der Index UserGenderIndex. Zu jeder ColumnFamily
oder SuperColumnFamily sind beliebig viele Indexe möglich.
An dieser Stelle müssen nun auch Indexe aus der Konfigurationsdatei geladen
werden und die entsprechenden internen Objekte erzeugt werden. Dazu wird
zuerst eine neue Klasse config.Index erstellt (in Abbildung 4.3 zu sehen). Um
Informationen über Indexe auf ColumnFamilies einlesen zu können wird diese
Klasse config.ColumnFamily um eine Liste von Instanzen der Klasse config.Index
erweitert. Abbildung 4.3 zeigt das Klassendiagramm mit der neuen Klasse config.Index.
44
KAPITEL 4. IMPLEMENTIERUNG
4.5
Datenstrukturen zur Laufzeit
Während die Klassen im vorherigen Abschnitt dazu verwendet werden um die
Konfigurationsdatei einzulesen, werden jetzt Klassen erstellt die das Schema
zur Laufzeit von Cassandra abbilden. Beim Start von Cassandra werden alle
Keyspaces und ColumnFamilies von der Funktion loadSchemas() in der Klasse
config.DatabaseDescriptor aus dem System-Keyspace geladen. Das Einlesen neuer
ColumnFamilies und/oder Keyspaces aus der Konfigurationsdatei cassandra.yaml
kann vom Benutzer ausgelöst werden. Dazu wird die Methode readTablesFromYaml() in der Klasse config.DatabaseDescriptor verwendet.
KSMetaData
+ name : string
+ strategyClass : AbstractReplicationStrategy
+ replicationFactor : int
- cfMetaData : Map<String, CFMetaData>
+ deflate() : KsDef
+ inflate(ks : KsDef) : KSMetaData
CFMetaData
+ tableName : string
+ cfName : string
+ cfType : ColumnFamilyType
+ clockType : ClockType
+ comparator : AbstractType
+ subcolumnComparator : AbstractType
+ reconciler : AbstractReconciler
+ comment : string
+ rowCacheSize : double
+ keyCacheSize : double
+ readRepairChance : double
+ cfId : int
+ preloadRowCache : bool
+ column_metadata : Map<byte[], ColumnDefinition>
- idxMetaData : Map<String, idxMetaData>
+ deflate() : CfDef
+ inflate(cf : CfDef) : CFMetaData
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
IdxMetaData
keyspace : string
columnFamily : string
name : string
indexType : AbstractCassandraIndex
columnName : List<String>
compareWith : AbstractType
deflate() : IdxDef
inflate(idx : IdxDef) : IdxMetaData
+indexType
+meta
AbstractCassandraIndex
meta : IdxMetaData
index_get(key_map : Map<byte[],byte[]>, column_path : ColumnPath, consistency_level : ConsistencyLevel)
index_get_slice(key_map : List<byte[],byte[]>, column_parent : ColumnParent, predicate : SlicePredicate, consistency_level : ConsistencyLevel)
index_multiget_slice(key_map_list : List<Map<byte[],byte[]>>, column_parent : ColumnParent, predicate : SlicePredicate, consistency_level : ConsistencyLevel)
index_get_range_slice(column_parent : ColumnParent, predicate : SlicePredicate, key_map_range : KeyMapRange, consistency_level : ConsistencyLevel)
rebuildIndex()
validateIndex()
update_index(key : byte[], mutation : RowMutation, consistency_level : ConsistencyLevel)
Diagram: runtime Page 1
Abbildung 4.5: UML-Diagramm der Laufzeit Klassen des Schemas im Package
config
Während der Laufzeit von Cassandra werden alle Keyspaces in Instanzen
der Klasse config.KSMetaData gespeichert, welche unter anderem eine Liste mit
Instanzen der Klasse config.CFMetaData für jede ColumnFamily im Keyspace
enthält. Analog zu Abschnitt 4.4 wird eine neue Klasse für Indexe mit dem Namen
config.IdxMetaData erstellt. Jede ColumnFamily wird um eine Liste von Instanzen
dieser Klasse erweitert. Die Klassen und ihre Abhängigkeiten untereinander sind
in Abbildung 4.5 dargestellt.
4.6. SCHREIBOPERATIONEN
45
Da die Laufzeitstrukturen nach jeder Änderung eines Keyspaces oder einer ColumnFamily als binäre Daten im System-Keyspace gespeichert werden, enthalten sie
eine serialize- und eine deserialize-Funktion. Die neue Klasse config.IdxMetaData
muss ebenfalls beide Methoden enthalten. Außerdem müssen die serialize- und
deserialize-Methoden der Klasse config.CFMetaData erweitert werden, damit auch
die alle Indexe gespeichert bzw. wiederhergestellt werden.
4.6
Schreiboperationen
Alle Schreiboperation laufen über einen Koordinator-Knoten. Der relevante Code
befindet sich in der Klasse service.StorageProxy. Sie enthält zwei Methoden um
Schreiboperationen auszuführen: mutate und mutateBlocking. Es wird entweder die
Methode mutate, wenn es sich um einen Schreibzugriff mit keinen Konsistenzanforderungen handelt, oder die Methode mutateBlocking, wenn ein Konsistenzlevel
angegeben wurde. Beide erhalten eine Liste von Veränderungen als Argument und
leiten die Anfrage je nach Konsistenzlevel an einen oder mehrere Knoten weiter.
Row
+ key : DecoratedKey
+ cf : ColumnFamily
IColumn
+ name : byte[]
+ value : byte[]
+ clock : IClock
+
+
+
+
+
+cf
+
+
+
+
+
+
+
+
+
ColumnFamily
cfid : int
type : ColumnFamilyType
markedForDeleteAt : IClock
columns : List<byte[], IColumn>
addColumn(superColumnName : byte[], column : Column)
addColumn(column : IColumn)
getColumn(name : byte[]) : IColumn
remove(columnName : byte[])
metadata()
RowMutation
table : string
key : byte[]
modifications : Map<Integer,ColumnFamily>
add(cf : ColumnFamily)
getColumnFamilies()
Abbildung 4.6: UML-Diagramm Klassen die Daten puffern im Package db
Da alle Schreiboperationen über diese Methoden laufen, bietet es sich an
für jede Operation zu überprüfen, ob ein oder mehrere Indexe aktualisiert werden müssen. Dies wird in die neue Methode handleIndexUpdate in der Klasse
service.StorageProxy ausgelagert. An dieser Stelle werden alle Indexe auf der
betroffenen ColumnFamily durchlaufen und jeweils die indexUpdate-Methode
46
KAPITEL 4. IMPLEMENTIERUNG
aufgerufen, welche dann überprüft, ob für diesen Schreibvorgang etwas geändert
werden muss.
Das weitere Vorgehen ist von der konkreten Indeximplementierung abhängig.
Die Implementierung dieser Arbeit verwendet einen indirekten Index, wobei jeder
Indexeintrag nur auf den indexierten Datensatz verweist. Zu diesem Zweck wird
zunächst die Schreiboperation invertiert. Eine neue Schreiboperation wird erzeugt,
welche den Wert des Attributs als Schlüssel verwendet und den ursprünglichen
Schlüssel als Attributnamen, und über das in Abschnitt 2.7 beschriebene Interface
ausgeführt. Dabei wird das gleiche Konsistenzlevel wie bei der ursprünglichen
Schreiboperation verwendet.
Das zusätzliche Schreiben des Indexeintrages beim Schreiben eines Datensatzes
erledigt folgender in vereinfachter Form abgedruckte Algorithmus:
// Gegeben s e i e n f u e r das B e i s p i e l :
// Name der ColumnFamily i n d i e der I n d e x g e s c h r i e b e n wird
S t r i n g indexCF = ” UserAgeIndex ” ;
// Name d e s i n d e x i e r t e n A t t r i b u t e s der i n d e x i e r t e n
// ColumnFamily
S t r i n g i n d e x A t t r i b u t e = ” age ” ;
// Eine ColumnFamily , w e l c h e d i e Aenderungen e n t h a e l t
ColumnFamily c f = new ColumnFamily ( ) ;
// O b j e k t d e s i n d e x i e r t e s A t t r i b u t e s h o l e n
IColumn oCol = c f . getColumn ( i n d e x A t t r i b u t e ) ;
// Wenn e s n i c h t vorhanden i s t a b b r e c h e n
i f ( oCol == null ) {
return ;
}
// E r s t e l l e n e i n e s neuen I n d e x d a t e n s a t z e s , wobei der Wert
// d e s i n d e x i e r t e n A t t r i b u t e s a l s S c h l u e s s e l v e r w e n d e t wird
Mutation indexMut = new Mutation ( indexCF , oCol . v a l u e ( ) ) ;
ColumnFamily iCF = ColumnFamily . c r e a t e ( indexCF ) ;
indexMut . add ( iCF ) ;
// Eine neues A t t r i b u t im I n d e x d a t e n s a t z e r s t e l l e n , wobei
// a l s Name der S c h l u e s s e l d e s r e f e r e n z i e r t e n D a t e n s a t z e s
// und e i n e l e e r e r Wert v e r w e n d e t wird . Das Timestamp wird
4.7. LESEOPERATIONEN
47
// vom i n d e x i e r t e n A t t r i b u t uebernommen
Column i C o l = new Column ( mutation . key ( ) , ”” , oCol . c l o c k ( ) ) ;
iCF . addColumn ( iCF ) ;
// I n d e x d a t e n s a t z s c h r e i b e n
StorageProxy . mutate ( indexMut ) ;
4.7
Leseoperationen
Genau wie alle Schreiboperationen laufen alle Leseoperationen über einen Koordinator-Knoten. Sie werden ebenfalls in der Klasse service.StorageProxy analysiert
und weitergeleitet. Der Code bisheriger Leseoperationen bleibt unverändert. Alle neuen Leseoperationen werden der Klasse service.StorageProxy hinzugefügt.
Allerdings wird dabei lediglich die entsprechende Methode einer ICassandraIndex -Implementierung aufgerufen. Da bei jeder Leseoperation auf einen Index der
Name des Indexes angegeben wird, braucht er nur in der Liste von IdxMetaDataInstanzen in der aktuellen CFMetaData-Instanz nachgesehen werden.
In der konkreten Implementierung dieser Arbeit läuft eine Indexabfrage wie
folgt ab: Zuerst wird mit der Methode readProtocol in der Klasse service.StorageProxy eine get slice Operation (Siehe Abschnitt 2.7) auf die Index-ColumnFamily
ausgeführt, um als Attribute die gespeicherten Schlüssel aller Datensätze zu
diesem Indexwert indexKey zu erhalten. Dabei wird das consistency level der
ursprünglichen Schreiboperation verwendet. Allerdings ist nicht garantiert, dass
alle Datensätze aktuell noch existieren und der Wert des indexierten Attributs
noch dem Indexwert entspricht. Dies gilt es zu überprüfen.
Daher wird als nächster Schritt über alle Attributnamen iteriert, welche den
Schlüsseln der indexierten Datensätze entsprechen, und jeweils mit der Methode
readProtocol der indexierte Datensatz geladen. Wird er nicht gefunden oder
stimmt das Timestamp des indexierten Attributs nicht mit dem Timestamp des
Indexeintrags überein, wird der Datensatz übersprungen. Stimmen die Timestamps
nicht überein muss geprüft werden, ob der Indexeintrag eventuell gelöscht werden
kann. Zum Löschen des Schlüssels wird die Methode deleteIndexEntry in der
Implementierung aufgerufen. Alle Datensätze, die nicht übersprungen wurden,
werden zurückgegeben. Der Folgende vereinfachte Algorithmus löst die Probleme
aus Abschnitt 3.6.
48
KAPITEL 4. IMPLEMENTIERUNG
// Gegeben s e i e n :
// A k t u e l l e Z e i t
Clock now = Clock . now ( )
// T h r e s h o l d nach dem e i n I n d e x e i n t r a g g e l o e s c h t werden
// d a r f , wenn k e i n D a t e n s a t z dazu e x i s t i e r t
Clock t h r e s h o l d = new Clock ( ”1 day ” ) ;
// Name der ColumnFamily i n d i e der I n d e x g e s c h r i e b e n wird
S t r i n g indexCF = ” UserAgeIndex ” ;
// R e f e r e n z i e r t e ColumnFamily mit o r i g i n a l D a t e n s a e t z e n
S t r i n g orgCf = ” U s e r s ” ;
// Der a n g e f r a g t e S c h l u e s s e l
S t r i n g indexKey = ” 27 ” ;
// Name d e s i n d e x i e r t e n A t t r i b u t e s der i n d e x i e r t e n
// ColumnFamily
S t r i n g i n d e x A t t r i b u t e = ” age ” ;
// Lese I n d e x e i n t r a g
Row indexEntry = read ( indexCF , indexKey ) ;
// I t e r i e r e u e b e r A t t r i b u t e d e s I n d e x e i n t r a g e s
for ( IColumn i C o l : indexEntry . c f ) {
// Lese r e f e r e n z i e r e n D a t e n s a t z
Row data = rea d ( orgCF , i C o l . name ( ) ) ;
// V e r g l e i c h e d i e Timestamps
Column orgCol = data . getColumn ( i n d e x A t t r i b u t e ) ;
i f ( orgCol . c l o c k ( ) == i C o l . c l o c k ( ) ) {
// Als E r g e b n i s z u r u e c k g e b e n
addToResultSet ( data ) ;
} else {
// I n d e x e i n t r a g l o e s c h e n , wenn D a t e n s a t z neuer i s t
i f ( i C o l . c l o c k ( ) < orgCol . c l o c k ( ) ) {
d e l e t e I n d e x E n t r y ( orgCol ) ;
}
// I n d e x e i n t r a g l o e s c h e n , wenn e r a l t genug i s t
i f ( now − orgCol . c l o c k ( ) > t h r e s h o l d ) {
4.8. WARTUNGSOPERATIONEN
49
d e l e t e I n d e x E n t r y ( orgCol ) ;
}
}
}
4.8
Wartungsoperationen
Die Methoden indexVerify und indexRebuild werden ebenfalls der Klasse service.StorageProxy hinzugefügt. Beide rufen jeweils die entsprechende Methode
einer ICassandraIndex -Implementierung auf. Konkret wird bei einem index rebuild
eine get range slice-Operation über alle Datensätze ausgeführt und alle Datensätze
werden neu geschrieben.
// Gegeben s e i e n :
// Name der ColumnFamily i n d i e der I n d e x g e s c h r i e b e n wird
S t r i n g indexCF = ” UserAgeIndex ” ;
// Lesen a l l e r D a t e n s a e t z e i n der Index−ColumnFamily
L i s t <ColumnFamily> c f s = readRange ( indexCF , ”” , ”” ) ;
// I t e r i e r e u e b e r a l l e D a t e n s a e t z e
for ( ColumnFamily c f : c f s ) {
// G e n e r i e r e Fake Aenderung
Mutation fakeMut = new Mutation (CF, c f . key ( ) ) ;
// Fuege D a t e n s a t z h i n z u
fakeMut . add ( c f ) ;
// Nutze d i e Funktion u p d a t e I n d e x um den I n d e x zu den
// Aenderungen zu e r s t e l l e n
updateIndex ( c f ) ;
}
50
KAPITEL 4. IMPLEMENTIERUNG
Kapitel 5
Experimente und Tests
In diesem Kapitel soll die Implementierung experimentell bewertet und überprüft
werden. Zuerst soll durch ein Benchmark ermittelt werden, wie sich die Latenz und
der Durchsatz beim Schreiben verändert hat, nachdem ein Index eingeführt wurde.
Danach wird experimentell überprüft, ob unter hoher Latenz, wo Inkonsistenzen
potentiell häufiger auftreten, der Index letztendlich konsistent bleibt, was sich
unter anderem durch Ausführen der Methode indexVerify überprüfen lässt.
5.1
Benchmarks
Für verschiedene Knotenanzahlen soll jeweils gemessen werden, wie hoch die
Latenz, also die Laufzeit einer Operation bis ein Ergebnis vorliegt, und der
Durchsatz, also die Anzahl an Operationen die pro Sekunde ausgeführt werden
können, beim Schreiben mit und ohne Index ist. Danach soll für verschiedene
Lese-/Schreibverhältnisse die Latenz und der Durchsatz beim Lesen über den
Index ermittelt werden. Da Cassandra ein hochparalleler Dienst ist, muss auch der
Benchmark stark parallelisiert sein. Einfache sequentielle Benchmarks erreichen
nur einen relativ geringen Durchsatz, da die Latenz einzelner Operationen relativ
hoch ist.
Für die Tests wurde der unter contrib/py stress/stress.py mitgelieferte Benchmark verwendet und angepasst. Alle Tests werden mit 50 parallel laufenden
Clients ausgeführt und schreiben bzw. lesen eine Million Datensätze mit je 30
Attributen. Es wurde ein Index auf das Attribut mit dem Namen name definiert.
Das Beispiel in Abbildung 5.1 zeigt fünf Datensätze aus dem Benchmark mit Index.
51
52
KAPITEL 5. EXPERIMENTE UND TESTS
Keyspace1
Standard1
1
C1: rand()
10:10
...
C29: rand()
10:10
Name: 1.1
10:10
2
C1: rand()
10:11
...
C29: rand()
10:11
Name: 1.1
10:11
3
C1: rand()
10:12
...
C29: rand()
10:12
Name: 2.1
10:12
4
C1: rand()
10:13
...
C29: rand()
10:13
Name: 2.1
10:13
5
C1: rand()
10:14
...
C29: rand()
10:14
Name: 3.1
10:14
NameIndex
1.1
1:
10:10
2:
10:11
2.1
3:
10:12
4:
10:13
3.1
5:
10:14
Abbildung 5.1: Datenmodell beim Benchmark
Das consistency level wurde auf ONE gestellt. Folgende Hardwarekonfigurationen
werden verwendet:
• A - 1 x AMD Athlon(tm) II X4 620 Processor, 4 GB Ram, 2x Sata 1TB in
Raid 1
• B - 1 x Intel(R) Pentium(R) 4 CPU 3.20GHz, 1GB Ram, NFS SAN
• C - 6 x Intel(R) Pentium(R) 4 CPU 3.20GHz, 1GB Ram, NFS SAN
5.1. BENCHMARKS
5.1.1
53
Schreiben ohne Index
Es wird kein Index in Cassandra definiert und die oben genannten eine Million
Datensätze mit 30 Attributen werden geschrieben.
Schreiben ohne Index
Schreiben ohne Index
8000
30
25
6000
Latenz in ms
Durchsatz Operationen/s
7000
5000
4000
3000
20
15
10
2000
5
1000
0
0
A
B
Testsystem
C
A
B
Testsystem
C
Abbildung 5.2: Durchsatz und Latenz beim Schreiben ohne Index
5.1.2
Schreiben mit Index
Es wird ein Index auf das Attribut name definiert und es werden wieder die eine
Million Datensätze mit 30 Attributen geschrieben.
Schreiben mit Index
35
6000
30
5000
25
Latenz in ms
Durchsatz Operationen/s
Schreiben mit Index
7000
4000
3000
20
15
2000
10
1000
5
0
0
A
B
Testsystem
C
A
B
Testsystem
Abbildung 5.3: Durchsatz und Latenz beim Schreiben mit Index
C
54
KAPITEL 5. EXPERIMENTE UND TESTS
5.1.3
Lesen auf Datensäte
Es werden die Datensätze aus Abschnitt 5.1.1 gelesen.
Lesen auf Datensätze
45
9000
40
8000
35
7000
Latenz in ms
Durchsatz Operationen/s
Lesen auf Datensätze
10000
6000
5000
4000
3000
30
25
20
15
2000
10
1000
5
0
0
A
B
Testsystem
C
A
B
Testsystem
C
Abbildung 5.4: Durchsatz und Latenz beim Lesen eines Datensatzes
5.1.4
Lesen mit Index auf einen Datensatz
Es werden die Datensätze aus Abschnitt 5.1.2 über den Index gelesen. Jeder
Indexeintrag zeigt jeweils auf einen Datensätze. Die Einträge wurden vom Schreibbenchmark in Abschnitt 5.1.2 erzeugt. Die Korrektheit der Datensätze wird beim
Lesen überprüft.
Lesen auf Datensätze über Index
Lesen auf Datensätze über Index
4500
50
40
3500
3000
Latenz in ms
Durchsatz Operationen/s
4000
2500
2000
1500
1000
30
20
10
500
0
0
A
B
Testsystem
C
A
B
Testsystem
C
Abbildung 5.5: Durchsatz und Latenz beim Lesen eines Datensatzes über den
Index
5.2. SIMULATION HOHER LATENZ
5.1.5
55
Lesen mit Index auf 10 Datensätze
Der Ablauf ist wie im Abschnitt 5.1.4, jedoch zeigt jeder Indexeintrag auf jeweils 10
Datensätze. Dazu wurde der Benchmark aus Abschnitt 5.1.2 minimal modifiziert.
Lesen auf Datensätze über Index
2500
20000
2000
Latenz in ms
Durchsatz Operationen/s
Lesen auf Datensätze über Index
25000
15000
10000
5000
1500
1000
500
0
0
A
B
Testsystem
C
A
B
Testsystem
C
Abbildung 5.6: Durchsatz und Latenz beim Lesen von 10 Datensätzen über den
Index
5.1.6
Bewertung der Ergebnisse
Beim Schreiben eines Datensatzes in eine indexierte ColumnFamily sinkt der
Durchsatz um etwa als 20% und die Latenz wird weniger als 20% größer (Vergleiche
Abschnitte 5.1.1 und 5.1.2). In Abschnitt 3.5 war doppelte Latenz erwartet worden,
jedoch hat sich die Erhöhung als geringer herausgestellt.
Der Durchsatz beim Lesen vom Index hängt offensichtlich von der Anzahl
an referenzierten Datensätzen ab. Bei 10 referenzierten Datensätzen steigt der
Durchsatz und die Latenz. Die Durchsatzsteigerung kommt zustande, weil die vom
Index referenzierten Datensätze per multiget slice-Operation geladen werden, was
schneller ist als sie einzeln zu laden. Bei einem referenzierten Datensatz kommt es
wie erwartet zu der Halbierung des Durchsatzes und die Latenz steigt, zu jedem
zurückgegebenen Datensatz auch noch der Indexeintrag gelesen werden muss.
5.2
Simulation hoher Latenz
Um zu ermitteln, ob der Index korrekt arbeitet, wird eine höhere Latenz zwischen
den Knoten simuliert, da Race-Conditions zwischen Operationen dann potentiell
56
KAPITEL 5. EXPERIMENTE UND TESTS
viel häufiger auftreten. Zu diesem Zweck wird in der Klasse service.StorageProxy in
den Methoden readProtocol und mutate bzw. mutateBlocking eine Schlafoperation
von zufälliger Dauer eingefügt. Dadurch streut die Latenz der Operation sehr viel
stärker und Wettrennen zwischen Lese- und Schreiboperationen treten öfter auf.
Es wurden alle im vorherigen Abschnitt ausgeführten Benchmarks wiederholt.
Dabei blieb der Durchsatz gleich. Allerdings stieg die Latenz um die durchschnittliche Wartezeit. In den Benchmarks aus den Abschnitten 5.1.4 und 5.1.5 wurden
keine inkorrekten Datensätze festgestellt.
Kapitel 6
Fazit
Dieses Kapitel fasst die Ergebnisse der Arbeit zusammen. Es werden Vergleiche zu
relationalen Datenbankmanagementsystemen angestellt und es gibt einen Ausblick
auf weitere Verbesserungen.
6.1
Zusammenfassung
In dieser Arbeit wurde eine Erweiterung des verteilten Datenspeichersystems
Cassandra um eine Indexunterstützung vorgestellt. Cassandra unterstützte bisher
nur den Zugriff auf Datensätze über den Schlüssel (siehe Abschnitt 2.7). Dabei sind
nur einzelne Operationen atomar und es kann zeitweise unterschiedliche Versionen
eines Datensatzes auf verschiedenen Knoten geben. Letztendlich wird aber immer
Konsistenz erreicht (siehe Abschnitt 2.6). Sperren und Transaktionen sind weiter
nicht verfügbar. In Kapitel 3 wurde vorgestellt, wie ein indirekter Index aussehen
könnte, der ohne Sperren, Transaktionen und mit letztendlicher Konsistenz funktionieren kann. Dabei wird bei Änderung eines indexierten Datensatzes immer ein
neuer Indexeintrag geschrieben. Erst beim Lesen über den Index wird für jeden
Indexeintrag überprüft, ob ein passender Datensatz mit dem gleichen Timestamp
existiert. Nur wenn dies so ist, wird der Datensatz zurückgegeben. Anderenfalls
wird der Indexeintrag nach einer gewissen Zeit gelöscht. Die Korrektheit der
Implementierung wurde in Abschnitt 3.3 gezeigt. Danach wurde in Abschnitt 3.2
festgelegt, wie das Speicherformat aussehen soll. Weiterhin wurde in Abschnitt
3.5 und 3.6 spezifiziert, wie Lese- und Schreiboperationen aussehen sollen. Im
praktischen Teil der Arbeit wurde in Kapitel 4 eine konkrete Indeximplementier57
58
KAPITEL 6. FAZIT
ung entwickelt. Dabei wurde konkret beschrieben, welche neuen Operationen in
das Interface von Cassandra eingefügt wurden und wie diese intern funktionieren.
Weiterhin wurde eine Erweiterung der Konfigurationsdatei vorgenommen, um
Indexe einlesen zu können (siehe Abschnitt 4.4). Dazu war es notwendig, interne
Metadatenstrukturen anzupassen (siehe Abschnitt 4.5). Danach wurden konkrete Algorithmen entwickelt, um das Konzept aus Kapitel 3 umzusetzen. Zuletzt
wurden alle Ergebnisse in Abschnitt 5 experimentell überprüft.
Zusammenfassend wurde es möglich, über spezielle Operationen einen Zugriff
auf Datensätze über einen Attributwert zu einem vorher definierten Attributnamen durchzuführen, statt wie bisher nur über den Schlüssel des Datensatzes.
Ohne einen Index wäre ein solcher Zugriff nicht möglich, ohne den gesamten
Datenbestand sequentiell zu durchsuchen. In vielen Anwendungen werden Daten
denormalisieren und unter einem weiteren Schlüssel dupliziert. Dies entfällt mit
dieser Implementierung. Es entsteht ein zusätzlicher Aufwand beim Schreiben
eines indexierten Attributs in einem Datensatz. Bei Datensätzen mit 30 Attributen
verringert sich der Durchsatz dabei um 20% (Siehe Kapitel 5). Gleichzeitig wird
letztendliche Konsistenz des Indexes von Cassandra garantiert.
6.2
Vergleich zu relationalen Datenbankmanagementsystemen
In relationalen Datenbanksystemen dagegen ist es im Unterschied zu Cassandra
möglich Anfragen nach beliebigen Spalten zu stellen. Dabei entscheidet intern
ein Planner, ob und wie vorhandene Indexe verwendet werden. Dadurch kann
die Laufzeit von zwei ähnlichen Anfragen sehr unterschiedlich sein und es ist
kaum möglich die Laufzeit im Voraus abzuschätzen. In Cassandra dagegen hängt
die Laufzeit beim Lesen nur von der Anzahl der zurückgegeben Datensätze ab.
Anfragen über einen Index müssen explizit vom Benutzer gestellt werden. Auch
das Datenmodell unterscheidet sich fundamental zwischen relationen Datenbanken
und Cassandra. Während RDMS ein festes Schema für Tabllen besitzen, verwendet
Cassandra ColumnFamilies mit einer flexiblen Anzahl von Attributen. Die meisten
RDMS sind auf einen Knoten begrenzt. Cassandra dagegen skaliert auf eine große
Anzahl von Knoten und verteilt alle Datensätze anhand ihres Schlüssels.
6.3. AUSBLICK
6.3
59
Ausblick
In Kapitel 3 wurde eine Möglichkeit einen indirekten Index zu implementieren
vorgestellt. Da keine Sperren und Transaktionen in Cassandra verfügbar sind,
muss bei der Indexaktualisierung zusätzlicher Aufwand betrieben werden, um
Konsistenz und Korrektheit zu garantieren. Dabei sollten noch weitere Methoden
ausprobiert werden und die Geschwindigkeit hinsichtlich Latenz und Durchsatz
verglichen werden:
Es wäre denkbar, jeden Indexeintrag um ein Attribut zu erweitern, welches
angibt, ob der Schreibvorgang des indexierten Datensatzes abgeschlossen ist. Somit
könnte die Konsistenz sichergestellt werden, ohne das Löschen von Indexeinträgen
temporär zu verhindern. Dadurch kann die Anzahl von falschen Indexeinträgen
reduziert werden und die durchschnittliche Latenz, also die Zeit zwischen Absenden
der Anfrage und Empfangen der Antwort, beim ersten Lesezugriff auf einen
Indexeintrag verringert werden, da weniger falsche Einträge übersprungen werden
müssten.
Man könnte alternativ einen optimistischen Ansatz wählen und beim Schreiben den alten Datensatz lesen, um einen potentiellen vorherigen Indexeintrag
zu löschen. Da nicht garantiert werden kann, dass dabei wirklich der vorherige
und nicht ein viel älterer Datensatz gelesen wurde, kann die vorher eingeführte
Überprüfung beim Lesen vom Index nicht entfallen. In den meisten Fällen würde
mit hoher Wahrscheinlichkeit der vorherige Datensatz gelesen und der entsprechende Indexeintrag könnte gelöscht werden. Vermutlich ist es aber aufgrund der
schlechten Leseperformance von Cassandra insgesamt langsamer, da insgesamt
mehr Leseoperationen durchgeführt werden müssten. Wie im vorherigen Absatz
würde es die durchschnittliche Latenz beim ersten Lesen auf einen Indexeintrag
verringern.
Eine andere denkbare Erweiterung wäre es beim Schreiben eines neuen Indexeintrages mit einer gewissen Chance die Überprüfung aller Einträge zu dem
Indexwert anzustoßen. Das könnte asynchron im Hintergrund auf dem KoordinatorKnoten geschehen. Bei Datensätzen, die sich häufig ändern, aber sehr selten vom
Index gelesen werden, kann so der maximale Speicherplatzoverhead im Index
und die Latenz des ersten Lesezugriffs verringert werden. Äquivalent funktioniert das Reparieren von veralteten Datensätzen bei normalen Leseoperationen in
Cassandra.
60
KAPITEL 6. FAZIT
Weiterhin ist es auf Grund der Einführung des ICassandraIndex Interfaces in
Kapitel 4 möglich, weitere Indeximplementierung für Cassandra zu entwickeln
und zu nutzen. Dabei beschränkt sich das Interface nicht auf indirekte Indexe.
Man könnte Indexe in Form von materialisierten Sichten entwickeln, welche den
Datenbestand der indexierten ColumnFamily duplizieren würden um die Geschwindigkeit beim Lesen zu maximieren. Dies würde einen anderen Ansatz erfordern um
Konsistenz zu garantierten. Ebenfalls interessant wäre eine Implementierungen
eines verteilten Indexes, welcher nur im Hauptspeicher arbeitet. Dazu könnte
man z.B. das memcached System[6] als Speicher für den Index verwenden, da es
ähnliche Operationen wie Cassandra unterstützt.
Literaturverzeichnis
[1] Apache hadoop projektseite. http://hadoop.apache.org/.
[2] Apache hbase projektseite. http://hbase.apache.org/.
[3] Apache thrift homepage. http://incubator.apache.org/thrift/.
[4] Architectureoverview - cassandra wiki (rev 10). http://wiki.apache.org/
cassandra/ArchitectureOverview.
[5] Datamodel - cassandra wiki (rev 9). http://wiki.apache.org/cassandra/
DataModel.
[6] memcached - a distributed
http://memcached.org/.
memory
object
caching
system.
[7] A quick introduction to the cassandra data model. http://maxgrinev.com/
2010/07/09/a-quick-introduction-to-the-cassandra-data-model/,
July 2010.
[8] Daniel Abadi.
Problems with cap, and yahoo’s little known
nosql
system.
http://dbmsmusings.blogspot.com/2010/04/
problems-with-cap-and-yahoos-little.html, April 2010.
[9] Oren Ben-Kiki, Clark Evans, and Ingy döt Net. Yaml ain’t markup language
(yaml) version 1.2. http://yaml.org/spec/1.2/spec.pdf, October 2009.
[10] Eric A. Brewer. Towards robust distributed systems. In PODC ’00: Proceedings of the nineteenth annual ACM symposium on Principles of distributed
computing, page 7, New York, NY, USA, 2000. ACM.
[11] Fay Chang, Jeffrey Dean, Sanjay Ghemawat, Wilson C. Hsieh, Deborah A.
Wallach, Michael Burrows, Tushar Chandra, Andrew Fikes, and Robert
Gruber. Bigtable: A distributed storage system for structured data. In
Proceedings of OSDI 2006, pages 205–218, 2006.
[12] Giuseppe DeCandia, Deniz Hastorun, Madan Jampani, Gunavardhan Kakulapati, Avinash Lakshman, Alex Pilchin, Swaminathan Sivasubramanian, Peter
61
62
LITERATURVERZEICHNIS
Vosshall, and Werner Vogels. Dynamo: amazon’s highly available key-value
store. SIGOPS Oper. Syst. Rev., 41(6):205–220, 2007.
[13] Avinash Lakshman. Cassandra - a structured storage system on a p2p network
— facebook. http://www.facebook.com/note.php?note id=24413138919, August 2008.
[14] Avinash Lakshman and Prashant Malik. Cassandra - a decentralized structured storage system. In Proceedings of the 3rd ACM International Workshop
on Large Scale Distributed Systems and Middleware (LADIS), pages 35–40,
2009.
[15] Robbert van Renesse, Dan Dumitriu, Valient Gough, and Chris Thomas.
Efficient reconciliation and flow control for anti-entropy protocols. In LADIS
’08: Proceedings of the 2nd Workshop on Large-Scale Distributed Systems and
Middleware, pages 1–7, New York, NY, USA, 2008. ACM.
[16] Werner Vogels. Eventually consistent. Commun. ACM, 52(1):40–44, 2009.
Abbildungsverzeichnis
2.1
2.2
2.3
2.4
2.5
2.6
Beispiel Datenbestand gemäß Datenmodell von Cassandra
cassandra.yaml Beispiel . . . . . . . . . . . . . . . . . . . .
Verteilung der Daten auf Knoten in einer Ringstruktur . .
Grafische Darstellung des CAP-Theorems . . . . . . . . . .
Ablauf einer Schreiboperation . . . . . . . . . . . . . . . .
Beispiel Datensätze gemäß Datenmodell von Cassandra . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
12
13
15
16
21
24
3.1
3.2
3.3
3.4
Speicherformat eines Indexes . . . . . . . . . . . . . . .
Race Condition beim Indexschreiben/-lesen . . . . . . .
Datensätze für Race Condition Beispiel . . . . . . . . .
Ablauf einer Schreiboperation mit Indexaktualisierung
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
29
31
31
33
4.1
4.2
4.3
Ablauf einer Schreiboperation in Cassandra mit Index . . . . . . .
Beispieldatensätze für Indexabfragen . . . . . . . . . . . . . . . .
UML-Diagramm der Klassen, welche die Konfigurationsdatei abbilden im Package config . . . . . . . . . . . . . . . . . . . . . . . .
cassandra.yaml Index Beispiel . . . . . . . . . . . . . . . . . . . .
UML-Diagramm der Laufzeit Klassen des Schemas im Package config
UML-Diagramm Klassen die Daten puffern im Package db . . . .
4.4
4.5
4.6
5.1
5.2
5.3
5.4
5.5
5.6
.
.
.
.
.
.
.
.
Datenmodell beim Benchmark . . . . . . . . . . . . . . . . . . . .
Durchsatz und Latenz beim Schreiben ohne Index . . . . . . . . .
Durchsatz und Latenz beim Schreiben mit Index . . . . . . . . . .
Durchsatz und Latenz beim Lesen eines Datensatzes . . . . . . . .
Durchsatz und Latenz beim Lesen eines Datensatzes über den Index
Durchsatz und Latenz beim Lesen von 10 Datensätzen über den
Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
63
37
41
42
43
44
45
52
53
53
54
54
55
64
ABBILDUNGSVERZEICHNIS
Eidesstattliche Erklärung
Hiermit erkläre ich, dass ich die vorliegende Arbeit und die zugehörige Implementierung selbstständig verfasst und dabei nur die angegebenen Quellen und
Hilfsmittel verwendet habe.
Hannover, den 26. August 2010
Jan Kantert
65
66
ABBILDUNGSVERZEICHNIS
Anhang A
Konfigurationsdatei
cassandra.yaml
# Cassandra s t o r a g e c o n f i g YAML
# See h t t p : // w i k i . apache . or g / c a s s a n d r a /
StorageConfiguration for
# explanations of configuration directives .
# name o f t he c l u s t e r
c l u s t e r n a m e : ’ Test C l u s t e r ’
# S e t t o t r u e t o make new [ non−s e e d ] nodes a u t o m a t i c a l l y
# m i g r a t e data t o t h e m s e l v e s from t he pre−e x i s t i n g nodes
# in the c l u s t e r .
auto bootstrap: true
# See h t t p : // w i k i . apache . or g / c a s s a n d r a / HintedHandoff
hinted handoff enabled: true
# a u t h e n t i c a t i o n backend , implementing I A u t h e n t i c a t o r ;
used t o
# l i m i t keyspace a c c e s s
a u t h e n t i c a t o r : o rg . apache . c a s s a n d r a . auth .
AllowAllAuthenticator
# any I P a r t i t i o n e r may be used , i n c l u d i n g your own as l o n g
as
# i t i s on th e c l a s s p a t h . Out o f t h e box , Cassandra
provides
# o r g . apache . c a s s a n d r a . dht . RandomPartitioner
67
68
ANHANG A. KONFIGURATIONSDATEI CASSANDRA.YAML
# o r g . apache . c a s s a n d r a . dht . O r d e r P r e s e r v i n g P a r t i t i o n e r , and
# o r g . apache . c a s s a n d r a . dht .
CollatingOrderPreservingPartitioner .
p a r t i t i o n e r : or g . apache . c a s s a n d r a . dht . RandomPartitioner
# d i r e c t o r i e s where Cassandra s h o u l d s t o r e data on d i s k .
data file directories:
− / var / l i b / c a s s a n d r a / data
# A d d r e s s e s o f h o s t s t h a t a r e deemed c o n t a c t p o i n t s .
# Cassandra nodes use t h i s l i s t o f h o s t s t o f i n d each
other
# and l e a r n th e t o p o l o g y o f th e r i n g . You must change
this
# i f you a r e running m u l t i p l e nodes !
seeds:
− 127.0.0.1
# A c c e s s mode . mmapped i /o i s s u b s t a n t i a l l y f a s t e r , but
only
# p r a c t i c a l on a 64 b i t machine ( which n o t a b l y does not
include
# EC2 ” s m a l l ” i n s t a n c e s ) o r r e l a t i v e l y s m a l l d a t a s e t s . ”
auto ” ,
# t h e s a f e c h o i c e , w i l l e n a b l e mmapping on a 64 b i t JVM.
Other
# v a l u e s a r e ”mmap” , ” mmap index only ” ( which may a l l o w
you t o
# g e t p a r t o f t he b e n e f i t s o f mmap on a 32 b i t machine by
# mmapping o n l y i n d e x f i l e s ) and ” s t a n d a r d ” . ( The b u f f e r
size
# s e t t i n g s t h a t f o l l o w o n l y apply t o standard , non−mmapped
i /o . )
d i s k a c c e s s m o d e : auto
# U n l i k e most systems , i n Cassandra w r i t e s a r e f a s t e r than
# r e ads , so you can a f f o r d more o f t h o s e i n p a r a l l e l . A
good
# r u l e o f thumb i s 2 c o n c u r r e n t r e a d s p er p r o c e s s o r c o r e .
# I n c r e a s e ConcurrentWrites t o t he number o f c l i e n t s
w r i t i n g at
# once i f you e n a b l e CommitLogSync + CommitLogSyncDelay .
−−>
69
concurrent reads: 8
c o n c u r r e n t w r i t e s : 32
# B u f f e r s i z e t o use when p e r f o r m i n g c o n t i g u o u s column
slices .
# I n c r e a s e t h i s t o th e s i z e o f t he column s l i c e s you
typically
# perform
s l i c e d b u f f e r s i z e i n k b : 64
# TCP port , f o r commands and data
s t o r a g e p o r t : 7000
# Address t o bind t o and t e l l o t h e r nodes t o c o n n e c t t o .
You
# must change t h i s i f you want m u l t i p l e nodes t o be a b l e
to
# communicate !
listen address: localhost
# The a d d r e s s t o bind t he T h r i f t RPC s e r v i c e t o
rpc address: localhost
# p o r t f o r T h r i f t t o l i s t e n on
r p c p o r t : 9160
# Whether o r not t o use a framed t r a n s p o r t f o r T h r i f t .
thrift framed transport: false
snapshot before compaction: f a l s e
# The t h r e s h o l d s i z e i n megabytes th e b i n a r y memtable must
grow to ,
# b e f o r e i t ’ s submitted f o r f l u s h i n g t o d i s k .
b i n a r y m e m t a b l e t h r o u g h p u t i n m b : 256
# Number o f minutes t o keep a memtable i n memory
m e m t a b l e f l u s h a f t e r m i n s : 60
# S i z e o f th e memtable i n memory b e f o r e i t i s dumped
m e m t a b l e t h r o u g h p u t i n m b : 64
# Number o f o b j e c t s i n m i l l i o n s i n t h e memtable b e f o r e i t
i s dumped
memtable operations in millions: 0.3
# B u f f e r s i z e t o use when f l u s h i n g ! memtables t o d i s k .
f l u s h d a t a b u f f e r s i z e i n m b : 32
# I n c r e a s e ( decrease ) the index b u f f e r s i z e r e l a t i v e to
t h e data
# b u f f e r i f you have few (many) columns pe r key .
70
ANHANG A. KONFIGURATIONSDATEI CASSANDRA.YAML
flush index buffer size in mb: 8
c o l u m n i n d e x s i z e i n k b : 64
r o w w a r n i n g t h r e s h o l d i n m b : 512
# commit l o g
c o m m i t l o g d i r e c t o r y : / var / l i b / c a s s a n d r a / commitlog
# S i z e t o a l l o w commitlog t o grow t o b e f o r e c r e a t i n g a new
segment
c o m m i t l o g r o t a t i o n t h r e s h o l d i n m b : 128
# c o m m i t l o g s y n c may be e i t h e r ” p e r i o d i c ” o r ” batch . ”
# When i n batch mode , Cassandra won ’ t ack w r i t e s u n t i l t he
commit l o g
# has been f s y n c e d t o d i s k . I t w i l l wai t up t o
# CommitLogSyncBatchWindowInMS m i l l i s e c o n d s f o r o t h e r
writes , before
# p e r f o r m i n g t he sync .
commitlog sync: periodic
# t h e o t h e r o p t i o n i s ” timed , ” where w r i t e s may be acked
immediately
# and t he CommitLog i s s i m p l y synced e v e r y
commitlog sync period in ms
# milliseconds .
c o m m i t l o g s y n c p e r i o d i n m s : 10000
# Time t o w ait f o r a r e p l y from o t h e r nodes b e f o r e f a i l i n g
t he command
r p c t i m e o u t i n m s : 10000
# p h i v a l u e t h a t must be r e a c h e d f o r a h o s t t o be marked
down .
# most u s e r s s h o u l d n e v e r need t o a d j u s t t h i s .
# phi convict threshold: 8
# time t o wait b e f o r e garbage c o l l e c t i n g tombstones (
d e l e t i o n markers )
g c g r a c e s e c o n d s : 864000
# e n d p o i n t s n i t c h −− S et t h i s t o a c l a s s t h a t implements
# I E n d p o i n t S n i t c h , which w i l l l e t Cassandra know enough
71
# about your network t o p o l o g y t o r o u t e r e q u e s t s
efficiently .
# Out o f t he box , Cassandra p r o v i d e s
# o r g . apache . c a s s a n d r a . l o c a t o r . S i m p l e S n i t c h ,
# o r g . apache . c a s s a n d r a . l o c a t o r . R a c k I n f e r r i n g S n i t c h , and
# o r g . apache . c a s s a n d r a . l o c a t o r . P r o p e r t y F i l e S n i t c h .
e n d p o i n t s n i t c h : o rg . apache . c a s s a n d r a . l o c a t o r . S i m p l e S n i t c h
# A ColumnFamily i s t h e Cassandra c o n c e p t c l o s e s t t o a
relational table .
#
# Keyspaces a r e s e p a r a t e groups o f ColumnFamilies . Except
i n very
# unusual c i r c u m s t a n c e s you w i l l have one Keyspace pe r
application .
#
# Keyspace r e q u i r e d p a r a m e t e r s :
# − name: name o f th e k e y s p a c e ; ” system ” and ” d e f i n i t i o n s ”
are
#
r e s e r v e d f o r Cassandra I n t e r n a l s .
# − r e p l i c a p l a c e m e n t s t r a t e g y : th e c l a s s t h a t d e t e r m i n e s
how r e p l i c a s
#
a r e d i s t r i b u t e d among nodes . Must implement
IReplicaPlacementStrategy .
#
Out o f t he box , Cassandra p r o v i d e s
#
∗ o rg . apache . c a s s a n d r a . l o c a t o r . RackUnawareStrategy
#
∗ o rg . apache . c a s s a n d r a . l o c a t o r . RackAwareStrategy
#
∗ o rg . apache . c a s s a n d r a . l o c a t o r .
DatacenterShardStrategy
#
#
RackUnawareStrategy i s t he s i m p l e s t ; i t s i m p l y p l a c e s
the f i r s t
#
r e p l i c a a t t he node whose token i s c l o s e s t t o t h e key
( a s determined
#
by th e P a r t i t i o n e r ) , and a d d i t i o n a l r e p l i c a s on
s u b s e q u e n t nodes
#
a l o n g t he r i n g i n i n c r e a s i n g Token o r d e r .
#
#
RackAwareStrategy i s s p e c i a l c a s e d f o r
r e p l i c a t i o n f a c t o r of 3. It
#
p l a c e s one r e p l i c a i n each o f two d a t a c e n t e r s , and t he
t h i r d on a
#
d i f f e r e n t r a c k i n i n th e f i r s t .
72
#
#
#
#
ANHANG A. KONFIGURATIONSDATEI CASSANDRA.YAML
DatacenterShardStrategy i s a g e n e r a l i z a t i o n of
RackAwareStrategy .
For each d a t a c e n t e r , you can s p e c i f y ( i n ‘ d a t a c e n t e r .
properties ‘)
how many r e p l i c a s you want on a per−k e y s p a c e b a s i s .
Replicas are
p l a c e d on d i f f e r e n t r a c k s w i t h i n each DC, i f p o s s i b l e .
#
#
# − r e p l i c a t i o n f a c t o r : Number o f r e p l i c a s o f each row
# − c o l u m n f a m i l i e s : column f a m i l i e s a s s o c i a t e d with t h i s
keyspace
#
# ColumnFamily r e q u i r e d p a r a m e t e r s :
# − name: name o f th e ColumnFamily . Must not c o n t a i n th e
c h a r a c t e r ”−” .
# − c o m p a r e w i t h : t e l l s Cassandra how t o s o r t t h e columns
for slicing
#
o p e r a t i o n s . The d e f a u l t i s BytesType , which i s a
straightforward
#
l e x i c a l comparison o f t he b y t e s i n each column . Other
options are
#
AsciiType , UTF8Type , LexicalUUIDType , TimeUUIDType ,
and LongType .
#
You can a l s o s p e c i f y t he f u l l y −q u a l i f i e d c l a s s name t o
a class of
#
your c h o i c e e x t e n d i n g or g . apache . c a s s a n d r a . db . marshal .
AbstractType .
#
# ColumnFamily o p t i o n a l p a r a m e t e r s :
# − k e y s c a c h e d : s p e c i f i e s t h e number o f keys p er s s t a b l e
whose
#
l o c a t i o n s we keep i n memory i n ” mostly LRU” o r d e r . (
JUST th e key
#
l o c a t i o n s , NOT any column v a l u e s . ) S p e c i f y a f r a c t i o n
( value l e s s
#
than 1 ) o r an a b s o l u t e number o f keys t o cache .
Defaults to
#
200000 keys .
# − r o w s c a c h e d : s p e c i f i e s th e number o f rows whose e n t i r e
c o n t e n t s we
#
cache i n memory . Do not use t h i s on ColumnFamilies
with l a r g e rows ,
73
#
#
#
#
#
#
#
#
#
o r ColumnFamilies with high w r i t e : r e a d r a t i o s . S p e c i f y
a fraction
( v a l u e l e s s than 1 ) o r an a b s o l u t e number o f rows t o
c ac he .
D e f a u l t s t o 0 . ( i . e . row c a c h i n g i s o f f by d e f a u l t )
− comment: used t o a t t a c h a d d i t i o n a l human−r e a d a b l e
i n f o r m a t i o n about
t h e column f a m i l y t o i t s d e f i n i t i o n .
− r e a d r e p a i r c h a n c e : s p e c i f i e s t h e p r o b a b i l i t y with
which read
r e p a i r s s h o u l d be invoked on non−quorum r e a d s . must
be between 0
and 1 . d e f a u l t s t o 1 . 0 ( always read r e p a i r ) .
− p r e l o a d r o w c a c h e : I f t r u e , w i l l p o p u l a t e row cache on
startup .
Defaults to f a l s e .
#
#
# NOTE: t h i s k e y s p a c e d e f i n i t i o n i s f o r d e m o n s t r a t i o n
purposes only .
#
Cassandra w i l l not l o a d t h e s e d e f i n i t i o n s d u r i n g
s t a r t u p . See
#
h t t p : // w i k i . apache . o rg / c a s s a n d r a /FAQ#n o k e y s p a c e s
f o r an
#
explanation .
keyspaces:
− name: Keyspace1
r e p l i c a p l a c e m e n t s t r a t e g y : or g . apache . c a s s a n d r a .
l o c a t o r . RackUnawareStrategy
replication factor: 1
column families:
− name: Standard1
c o m p a r e w i t h : BytesType
− name: Standard2
c o m p a r e w i t h : UTF8Type
read repair chance: 0.1
k e y s c a c h e d : 100
− name: StandardByUUID1
c o m p a r e w i t h : TimeUUIDType
− name: Super1
c o l u m n t y p e : Super
74
ANHANG A. KONFIGURATIONSDATEI CASSANDRA.YAML
c o m p a r e w i t h : BytesType
c o m p a r e s u b c o l u m n s w i t h : BytesType
− name: Super2
c o l u m n t y p e : Super
c o m p a r e s u b c o l u m n s w i t h : UTF8Type
preload row cache: true
r o w s c a c h e d : 10000
k e y s c a c h e d : 50
comment: ’A column f a m i l y with supercolumns ,
whose
column and subcolumn names a r e UTF8
strings ’
− name: Super3
c o l u m n t y p e : Super
c o m p a r e w i t h : LongType
comment: ’A column f a m i l y with supercolumns ,
whose
column names a r e Longs ( 8 b y t e s ) ’
Anhang B
Thrift Interface
#!/ u s r / l o c a l / bi n / t h r i f t −−j a v a −−php −−py
# L i c e n s e d t o th e Apache S o f t w a r e Foundation (ASF) under
one
# o r more c o n t r i b u t o r l i c e n s e agreements . See th e NOTICE
file
# d i s t r i b u t e d with t h i s work for a d d i t i o n a l i n f o r m a t i o n
# r e g a r d i n g c o p y r i g h t ownership . The ASF l i c e n s e s t h i s
file
# t o you under th e Apache L i c e n s e , V e r s i o n 2 . 0 ( th e
# ” L i c e n s e ” ) ; you may not use t h i s f i l e e x c e p t i n
compliance
# with t h e L i c e n s e . You may o b t a i n a copy o f t he L i c e n s e
at
#
#
htt p : //www. apache . org / l i c e n s e s /LICENSE−2.0
#
# U n l e s s r e q u i r e d by a p p l i c a b l e law o r a g r e e d t o i n
writing , software
# d i s t r i b u t e d under t he L i c e n s e i s d i s t r i b u t e d on an ”AS
IS ” BASIS ,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, e i t h e r
express or implied .
# See t h e L i c e n s e for t he s p e c i f i c l a n g u a g e g o v e r n i n g
p e r m i s s i o n s and
# l i m i t a t i o n s under th e L i c e n s e .
# ˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜
# ∗∗∗ PLEASE REMEMBER TO EDIT THE VERSION CONSTANT WHEN
MAKING CHANGES ∗∗∗
# ˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜˜
75
76
ANHANG B. THRIFT INTERFACE
#
# I n t e r f a c e d e f i n i t i o n for Cassandra S e r v i c e
#
namespace
namespace
namespace
namespace
namespace
namespace
j a v a or g . apache . c a s s a n d r a . t h r i f t
cpp o rg . apache . c a s s a n d r a
c s h a r p Apache . Cassandra
py c a s s a n d r a
php c a s s a n d r a
p e r l Cassandra
# T h r i f t . rb has a bug where top−l e v e l modules t h a t i n c l u d e
modules
# with t h e same name a r e not p r o p e r l y r e f e r e n c e d , so we
can ’ t do
# Cassandra : : Cassandra : : C l i e n t .
namespace rb C a s s a n d r a T h r i f t
# The API v e r s i o n (NOT t h e product v e r s i o n ) , composed as a
dot d e l i m i t e d
# s t r i n g with major , minor , and patch l e v e l components .
#
# − Major : Incremented f o r backward i n c o m p a t i b l e changes .
An example would
#
be changes t o t h e number o r d i s p o s i t i o n o f
method arguments .
# − Minor : Incremented f o r backward c o m p a t i b l e changes .
An example would
#
be t he a d d i t i o n o f a new ( o p t i o n a l ) method .
# − Patch : Incremented f o r bug f i x e s . The patch l e v e l
s h o u l d be i n c r e a s e d
#
f o r e v e r y e d i t t h a t doesn ’ t r e s u l t i n a change
t o major / minor .
#
# See t h e Semantic V e r s i o n i n g S p e c i f i c a t i o n ( SemVer ) h ttp :
// semver . org .
const s t r i n g VERSION = ” 8 . 5 . 0 ”
#
# data s t r u c t u r e s
#
77
/∗ ∗ E n c a p s u l a t e t y p e s o f c o n f l i c t r e s o l u t i o n .
∗
∗ @param timestamp . User−s u p p l i e d timestamp . When two
columns w i t h t h i s t y p e o f c l o c k c o n f l i c t , t h e one w i t h
the
∗
h i g h e s t timestamp i s t h e one whose
v a l u e t h e system w i l l c o n v e r g e t o . No o t h e r
assumptions
∗
are made a b o u t what t h e timestamp
r e p r e s e n t s , b u t u s i n g microseconds−s i n c e −epoch i s
customary .
∗/
s t r u c t Clock {
1 : r e q u i r e d i 6 4 timestamp ,
}
/∗ ∗ B asic u n i t o f d a t a w i t h i n a ColumnFamily .
∗ @param name , t h e name by which t h i s column i s s e t and
r e t r i e v e d . Maximum 64KB l o n g .
∗ @param v a l u e . The d a t a a s s o c i a t e d w i t h t h e name .
Maximum 2GB long , b u t i n p r a c t i c e you s h o u l d l i m i t i t
t o s m a l l numbers o f MB ( s i n c e T h r i f t must read t h e
f u l l v a l u e i n t o memory t o o p e r a t e on i t ) .
∗ @param c l o c k . The c l o c k i s used f o r c o n f l i c t d e t e c t i o n /
r e s o l u t i o n when two columns w i t h same name need t o be
compared .
∗ @param t t l . An o p t i o n a l , p o s i t i v e d e l a y ( i n s e c o n d s )
a f t e r which t h e column w i l l be a u t o m a t i c a l l y d e l e t e d .
∗/
s t r u c t Column {
1 : r e q u i r e d b i n a r y name ,
2 : r e q u i r e d b i n a r y value ,
3 : r e q u i r e d Clock c l o c k ,
4: optional i32 ttl ,
}
/∗ ∗ A named l i s t o f columns .
∗ @param name . s e e Column . name .
∗ @param columns . A c o l l e c t i o n o f s t a n d a r d Columns . The
columns w i t h i n a s u p e r column are d e f i n e d i n an adhoc
manner .
∗
Columns w i t h i n a s u p e r column do not
78
ANHANG B. THRIFT INTERFACE
have t o have matching s t r u c t u r e s ( s i m i l a r l y named
c h i l d columns ) .
∗/
s t r u c t SuperColumn {
1 : r e q u i r e d b i n a r y name ,
2 : r e q u i r e d l i s t <Column> columns ,
}
/∗ ∗
Methods f o r f e t c h i n g rows / r e c o r d s from Cassandra w i l l
return either a s i n g l e instance of
ColumnOrSuperColumn or a l i s t
o f ColumnOrSuperColumns ( g e t s l i c e ( ) ) . I f you ’ r e
l o o k i n g up a SuperColumn ( or l i s t o f SuperColumns )
then the r e s u l t i n g
i n s t a n c e s o f ColumnOrSuperColumn w i l l have t h e
r e q u e s t e d SuperColumn i n t h e a t t r i b u t e s u p e r c o l u m n
. For q u e r i e s r e s u l t i n g
i n Columns , t h o s e v a l u e s w i l l be i n t h e a t t r i b u t e
column . This change was made between 0 . 3 and 0 . 4 t o
s t a n d a r d i z e on
s i n g l e q u e r y methods t h a t may r e t u r n e i t h e r a
SuperColumn or Column .
@param
get
@param
or
column . The Column r e t u r n e d by g e t ( ) or
s l i c e () .
s u p e r c o l u m n . The SuperColumn r e t u r n e d by g e t ( )
g e t s l i c e () .
∗/
s t r u c t ColumnOrSuperColumn {
1 : o p t i o n a l Column column ,
2 : o p t i o n a l SuperColumn super column ,
}
#
# Exceptions
# ( note t h a t i n t e r n a l s e r v e r e r r o r s w i l l r a i s e a
TApplicationException , courtesy of T h r i f t )
#
/∗ ∗ A s p e c i f i c column was r e q u e s t e d t h a t does not e x i s t .
∗/
79
e x c e p t i o n NotFoundException {
}
/∗ ∗ I n v a l i d r e q u e s t c o u l d mean k e y s p a c e or column f a m i l y
d o es not e x i s t , r e q u i r e d p a r a m e t e r s are m i s s i n g , or a
parameter i s malformed .
why c o n t a i n s an a s s o c i a t e d e r r o r message .
∗/
exception InvalidRequestException {
1 : r e q u i r e d s t r i n g why
}
/∗ ∗ Not a l l t h e r e p l i c a s r e q u i r e d c o u l d be c r e a t e d and/ or
read . ∗/
exception UnavailableException {
}
/∗ ∗ RPC t i m e o u t was e x c e e d e d . e i t h e r a node f a i l e d mid−
o p e r a t i o n , or l o a d was t o o high , or t h e r e q u e s t e d op
was t o o l a r g e . ∗/
e x c e p t i o n TimedOutException {
}
/∗ ∗ i n v a l i d a u t h e n t i c a t i o n r e q u e s t ( i n v a l i d k e y s p a c e , u s e r
d oes not e x i s t , or c r e d e n t i a l s i n v a l i d ) ∗/
exception AuthenticationException {
1 : r e q u i r e d s t r i n g why
}
/∗ ∗ i n v a l i d a u t h o r i z a t i o n r e q u e s t ( u s e r does not have
a c c e s s t o k e y s p a c e ) ∗/
exception AuthorizationException {
1 : r e q u i r e d s t r i n g why
}
#
# s e r v i c e api
#
/∗ ∗ The C o n s i s t e n c y L e v e l i s an enum t h a t c o n t r o l s b o t h
read and w r i t e b e h a v i o r b a s e d on <R e p l i c a t i o n F a c t o r > i n
your
∗ s t o r a g e −c o n f . xml . The d i f f e r e n t c o n s i s t e n c y l e v e l s have
80
∗
∗
∗
∗
∗
ANHANG B. THRIFT INTERFACE
d i f f e r e n t meanings , d e p e n d i n g on i f you ’ r e d o i n g a
w r i t e or read
o p e r a t i o n . Note t h a t i f W + R > R e p l i c a t i o n F a c t o r ,
where W i s t h e number o f nodes t o b l o c k f o r on w r i t e ,
and R
t h e number t o b l o c k f o r on reads , you w i l l have
strongly consistent behavior ; that is , readers w i l l
a l w a y s s e e t h e most
r e c e n t w r i t e . Of t h e s e , t h e most i n t e r e s t i n g i s t o do
QUORUM r e a d s and w r i t e s , which g i v e s you c o n s i s t e n c y
while s t i l l
a l l o w i n g a v a i l a b i l i t y i n t h e f a c e o f node f a i l u r e s up
t o h a l f o f <R e p l i c a t i o n F a c t o r >. Of c o u r s e i f l a t e n c y
i s more
i m p o r t a n t than c o n s i s t e n c y t h e n you can use l o w e r
v a l u e s f o r e i t h e r or b o t h .
∗
∗ Write c o n s i s t e n c y l e v e l s make t h e f o l l o w i n g g u a r a n t e e s
before reporting success to the c l i e n t :
∗
ZERO
Ensure n o t h i n g . A w r i t e happens
a s y n c h r o n o u s l y i n background
∗
ANY
Ensure t h a t t h e w r i t e has been w r i t t e n
once somewhere , i n c l u d i n g p o s s i b l y b e i n g h i n t e d i n a
non−t a r g e t node .
∗
ONE
Ensure t h a t t h e w r i t e has been w r i t t e n
t o a t l e a s t 1 node ’ s commit l o g and memory t a b l e
∗
QUORUM
Ensure t h a t t h e w r i t e has been w r i t t e n
t o <R e p l i c a t i o n F a c t o r > / 2 + 1 nodes
∗
DCQUORUM
Ensure t h a t t h e w r i t e has been w r i t t e n
t o <R e p l i c a t i o n F a c t o r > / 2 + 1 nodes , w i t h i n t h e l o c a l
datacenter ( requires DatacenterShardStrategy )
∗
DCQUORUMSYNC Ensure t h a t t h e w r i t e has been w r i t t e n
t o <R e p l i c a t i o n F a c t o r > / 2 + 1 nodes i n each
datacenter ( requires DatacenterShardStrategy )
∗
ALL
Ensure t h a t t h e w r i t e i s w r i t t e n t o <
code>&l t ; R e p l i c a t i o n F a c t o r&g t ;</ code> nodes b e f o r e
responding to the c l i e n t .
∗
∗ Read :
∗
ZERO
Not s u p p o r t e d , b e c a u s e i t doesn ’ t make
sense .
∗
ANY
Not s u p p o r t e d . You p r o b a b l y want ONE
instead .
81
∗
∗
∗
∗
∗
ONE
W i l l r e t u r n t h e r e c o r d r e t u r n e d by t h e
f i r s t node t o respond . A c o n s i s t e n c y c h e c k i s a l w a y s
done i n a background t h r e a d t o f i x any c o n s i s t e n c y
i s s u e s when C o n s i s t e n c y L e v e l .ONE i s used . This means
s u b s e q u e n t c a l l s w i l l have c o r r e c t d a t a even i f t h e
i n i t i a l read g e t s an o l d e r v a l u e . ( This i s c a l l e d ’
read r e p a i r ’ . )
QUORUM
W i l l q u e r y a l l s t o r a g e nodes and r e t u r n
t h e r e c o r d w i t h t h e most r e c e n t timestamp once i t has
a t l e a s t a m a j o r i t y o f r e p l i c a s r e p o r t e d . Again , t h e
remaining r e p l i c a s w i l l be c h e c k e d i n t h e background .
DCQUORUM
Returns t h e r e c o r d w i t h t h e most r e c e n t
timestamp once a m a j o r i t y o f r e p l i c a s w i t h i n t h e l o c a l
d a t a c e n t e r have r e p l i e d .
DCQUORUMSYNC Returns t h e r e c o r d w i t h t h e most r e c e n t
timestamp once a m a j o r i t y o f r e p l i c a s w i t h i n each
d a t a c e n t e r have r e p l i e d .
ALL
Q u e r i e s a l l s t o r a g e nodes and r e t u r n s
t h e r e c o r d w i t h t h e most r e c e n t timestamp .
∗/
enum C o n s i s t e n c y L e v e l {
ZERO = 0 ,
ONE = 1 ,
QUORUM = 2 ,
DCQUORUM = 3 ,
DCQUORUMSYNC = 4 ,
ALL = 5 ,
ANY = 6 ,
}
/∗ ∗
ColumnParent i s used when s e l e c t i n g g r o u p s o f columns
from t h e same ColumnFamily . In d i r e c t o r y s t r u c t u r e
terms , imagine
ColumnParent as ColumnPath + ’ / . . / ’ .
See a l s o <a h r e f =”c a s s a n d r a . html#Struct ColumnPath”>
ColumnPath</a>
∗/
s t r u c t ColumnParent {
3 : r e q u i r e d s t r i n g column family ,
4 : o p t i o n a l b i n a r y super column ,
}
82
ANHANG B. THRIFT INTERFACE
/∗ ∗ The ColumnPath i s t h e p a t h t o a s i n g l e column i n
Cassandra . I t might make s e n s e t o t h i n k o f ColumnPath
and
∗ ColumnParent i n terms o f a d i r e c t o r y s t r u c t u r e .
∗
∗ ColumnPath i s used t o l o o k i n g up a s i n g l e column .
∗
∗ @param c o l u m n f a m i l y . The name o f t h e CF o f t h e column
b e i n g l o o k e d up .
∗ @param s u p e r c o l u m n . The s u p e r column name .
∗ @param column . The column name .
∗/
s t r u c t ColumnPath {
3 : r e q u i r e d s t r i n g column family ,
4 : o p t i o n a l b i n a r y super column ,
5 : o p t i o n a l b i n a r y column ,
}
/∗ ∗
A s l i c e range i s a s t r u c t u r e t h a t s t o r e s b a s i c range ,
o r d e r i n g and l i m i t i n f o r m a t i o n f o r a q u e r y t h a t
w i l l return
m u l t i p l e columns . I t c o u l d be t h o u g h t o f as Cassandra ’
s v e r s i o n o f LIMIT and ORDER BY
@param s t a r t . The column name t o s t a r t t h e s l i c e w i t h .
This a t t r i b u t e i s not r e q u i r e d , t h o u g h t h e r e i s no
d e f a u l t value ,
and can be s a f e l y s e t t o ’ ’ , i . e . , an
empty b y t e array , t o s t a r t w i t h t h e
f i r s t column name . Otherwise , i t
must a v a l i d v a l u e under t h e r u l e s o f
t h e Comparator d e f i n e d f o r t h e g i v e n
ColumnFamily .
@param f i n i s h . The column name t o s t o p t h e s l i c e a t .
This a t t r i b u t e i s not r e q u i r e d , t h o u g h t h e r e i s no
d e f a u l t value ,
and can be s a f e l y s e t t o an empty b y t e
a r r a y t o not s t o p u n t i l ’ count ’
r e s u l t s are seen . Otherwise , i t
must a l s o be a v a l i d v a l u e t o t h e
ColumnFamily Comparator .
83
@param r e v e r s e d . Whether t h e r e s u l t s s h o u l d be o r d e r e d
i n r e v e r s e d o r d e r . S i m i l a r t o ORDER BY b l a h DESC
i n SQL .
@param count . How many k e y s t o r e t u r n . S i m i l a r t o
LIMIT 100 i n SQL . May be a r b i t r a r i l y l a r g e , b u t
Thrift will
m a t e r i a l i z e t h e whole r e s u l t i n t o memory
before returning i t to the client ,
so be aware t h a t you may
be b e t t e r s e r v e d by i t e r a t i n g t h r o u g h
s l i c e s by p a s s i n g t h e l a s t v a l u e o f
one c a l l i n as t h e ’ s t a r t ’
o f t h e n e x t i n s t e a d o f i n c r e a s i n g ’ count
’ arbitrarily large .
@param b i t m a s k s . A l i s t o f OR−ed b i n a r y AND masks
applied to the r e s u l t set .
∗/
struct
1:
2:
3:
4:
5:
}
SliceRange {
required binary start ,
required binary f i n i s h ,
r e q u i r e d b o o l r e v e r s e d =0,
r e q u i r e d i 3 2 count =100 ,
o p t i o n a l l i s t <binary > bitmasks ,
/∗ ∗
A S l i c e P r e d i c a t e i s s i m i l a r t o a mathematic p r e d i c a t e
( s e e h t t p : / / en . w i k i p e d i a . org / w i k i / P r e d i c a t e (
mathematical logic ) ) ,
which i s d e s c r i b e d as ”a p r o p e r t y t h a t t h e e l e m e n t s o f
a s e t have i n common . ”
S l i c e P r e d i c a t e ’ s i n Cassandra are d e s c r i b e d w i t h
e i t h e r a l i s t o f column names or a S l i c e R a n g e .
column names i s
specified , s l i c e r a n g e i s ignored .
If
@param column name . A l i s t o f column names t o r e t r i e v e
. This can be used s i m i l a r t o Memcached ’ s ” m u l t i −
get ” feature
t o f e t c h N known column names . For
i n s t a n c e , i f you know you wish
t o f e t c h columns ’ Joe ’ , ’ Jack
84
ANHANG B. THRIFT INTERFACE
’,
and ’ Jim ’ you can p a s s t h o s e
column names as a l i s t t o f e t c h
a l l t h r e e a t once .
@param s l i c e r a n g e . A S l i c e R a n g e d e s c r i b i n g how t o
range , order , and/ or l i m i t t h e s l i c e .
∗/
struct SlicePredicate {
1 : o p t i o n a l l i s t <binary > column names ,
2: optional SliceRange
slice range ,
}
enum IndexOperator {
EQ,
}
struct
1:
2:
3:
}
IndexExpression {
r e q u i r e d b i n a r y column name ,
r e q u i r e d IndexOperator op ,
r e q u i r e d b i n a r y value ,
struct
1:
2:
3:
}
IndexClause {
r e q u i r e d l i s t <I n d e x E x p r e s s i o n > e x p r e s s i o n s
r e q u i r e d i 3 2 count =100 ,
optional binary start key ,
/∗ ∗
The s e m a n t i c s o f s t a r t k e y s and t o k e n s are s l i g h t l y
different .
Keys are s t a r t −i n c l u s i v e ; t o k e n s are s t a r t −e x c l u s i v e .
Token
r a n g e s may a l s o wrap −− t h a t i s , t h e end t o k e n may be l e s s
than t h e s t a r t one . Thus , a range from keyX t o keyX i s a
one−e l e m e n t range , b u t a range from tokenY t o tokenY i s
the
f u l l ring .
∗/
s t r u c t KeyRange {
1: optional binary start key ,
2 : o p t i o n a l b i n a r y end key ,
3: optional string start token ,
85
4 : o p t i o n a l s t r i n g end token ,
5 : r e q u i r e d i 3 2 count =100
}
struct
1:
2:
3:
}
RowPredicate {
o p t i o n a l l i s t <binary > keys ,
o p t i o n a l KeyRange k e y r a n g e ,
o p t i o n a l IndexClause i n d e x c l a u s e
/∗ ∗
A K e y S l i c e i s key f o l l o w e d by t h e d a t a i t maps t o . A
c o l l e c t i o n o f K e y S l i c e i s r e t u r n e d by t h e
g e t r a n g e s l i c e operation .
@param key . a row key
@param columns . L i s t o f d a t a r e p r e s e n t e d by t h e key .
T y p i c a l l y , t h e l i s t i s pared down t o o n l y t h e
columns s p e c i f i e d by
a SlicePredicate .
∗/
struct KeySlice {
1 : r e q u i r e d b i n a r y key ,
2 : r e q u i r e d l i s t <ColumnOrSuperColumn> columns ,
}
s t r u c t KeyCount {
1 : r e q u i r e d b i n a r y key ,
2 : r e q u i r e d i 3 2 count
}
struct
1:
2:
3:
}
Deletion
required
optional
optional
{
Clock c l o c k ,
b i n a r y super column ,
SlicePredicate predicate ,
/∗ ∗
A Mutation i s e i t h e r an i n s e r t , r e p r e s e n t e d by f i l l i n g
column or supercolumn , or a d e l e t i o n , r e p r e s e n t e d
by f i l l i n g t h e d e l e t i o n a t t r i b u t e .
@param c o l u m n o r s u p e r c o l u m n . An i n s e r t t o a column or
supercolumn
86
ANHANG B. THRIFT INTERFACE
@param d e l e t i o n . A d e l e t i o n o f a column or supercolumn
∗/
s t r u c t Mutation {
1 : o p t i o n a l ColumnOrSuperColumn column or supercolumn ,
2: optional Deletion deletion ,
}
struct
1:
2:
3:
}
TokenRange {
required string start token ,
r e q u i r e d s t r i n g end token ,
r e q u i r e d l i s t <s t r i n g > e n d p o i n t s ,
/∗ ∗ The A c c e s s L e v e l i s an enum t h a t e x p r e s s e s t h e
a u t h o r i z e d a c c e s s l e v e l g r a n t e d t o an API u s e r :
∗
∗
NONE
No a c c e s s p e r m i t t e d .
∗
READONLY
Only read a c c e s s i s a l l o w e d .
∗
READWRITE Read and w r i t e a c c e s s i s a l l o w e d .
∗
FULL
Read , w r i t e , and remove a c c e s s i s
allowed .
∗/
enum A c c e s s L e v e l {
NONE = 0 ,
READONLY = 1 6 ,
READWRITE = 3 2 ,
FULL = 6 4 ,
}
/∗ ∗
A u t h e n t i c a t i o n r e q u e s t s can c o n t a i n any data ,
d e p e n d e n t on t h e A u t h e n t i c a t i o n B a c k e n d used
∗/
st ruc t AuthenticationRequest {
1 : r e q u i r e d map<s t r i n g , s t r i n g > c r e d e n t i a l s
}
enum IndexType {
KEYS,
}
/∗ d e s c r i b e s a column i n a column f a m i l y . ∗/
s t r u c t ColumnDef {
87
1:
2:
3:
4:
required
required
optional
optional
b i n a r y name ,
string validation class ,
IndexType i n d e x t y p e ,
s t r i n g index name
}
/∗ d e s c r i b e s a column f a m i l y . ∗/
s t r u c t CfDef {
1 : r e q u i r e d s t r i n g keyspace ,
2 : r e q u i r e d s t r i n g name ,
3 : o p t i o n a l s t r i n g column type=” Standard ” ,
4 : o p t i o n a l s t r i n g c l o c k t y p e=”Timestamp” ,
5 : o p t i o n a l s t r i n g c o m p a r a t o r t y p e=” BytesType ” ,
6 : o p t i o n a l s t r i n g s u b c o m p a r a t o r t y p e=”” ,
7 : o p t i o n a l s t r i n g r e c o n c i l e r=”” ,
8 : o p t i o n a l s t r i n g comment=”” ,
9 : o p t i o n a l double r o w c a c h e s i z e =0,
1 0 : o p t i o n a l b o o l p r e l o a d r o w c a c h e =0,
1 1 : o p t i o n a l double k e y c a c h e s i z e =200000 ,
1 2 : o p t i o n a l double r e a d r e p a i r c h a n c e =1.0
1 3 : o p t i o n a l l i s t <ColumnDef> column metadata
14: optional i32 gc grace seconds
}
/∗ d e s c r i b e s a k e y s p a c e . ∗/
s t r u c t KsDef {
1 : r e q u i r e d s t r i n g name ,
2: required string strategy class ,
3: required i32 replication factor ,
5 : r e q u i r e d l i s t <CfDef> c f d e f s ,
}
struct
1:
2:
3:
4:
5:
}
KeyMapRange {
o p t i o n a l map<binary , binary >
o p t i o n a l map<binary , binary >
o p t i o n a l map<binary , s t r i n g >
o p t i o n a l map<binary , s t r i n g >
r e q u i r e d i 3 2 count =100
start key ,
end key ,
start token ,
end token ,
88
ANHANG B. THRIFT INTERFACE
s e r v i c e Cassandra {
# auth methods
AccessLevel login ( 1 : required AuthenticationRequest
a u t h r e q u e s t ) throws ( 1 : A u t h e n t i c a t i o n E x c e p t i o n
authnx , 2 : A u t h o r i z a t i o n E x c e p t i o n authzx ) ,
# s e t keyspace
void s e t k e y s p a c e ( 1 : r e q u i r e d s t r i n g k e y s p a c e ) throws
( 1 : InvalidRequestException i r e ) ,
# r e t r i e v a l methods
/∗ ∗
Get t h e Column or SuperColumn a t t h e g i v e n co lumn pat h
. I f no v a l u e i s p r e s e n t , NotFoundException i s
thrown . ( This i s
t h e o n l y method t h a t can throw an e x c e p t i o n under non−
failure conditions .)
∗/
ColumnOrSuperColumn g e t ( 1 : r e q u i r e d b i n a r y key ,
2 : r e q u i r e d ColumnPath column path ,
3: required ConsistencyLevel
c o n s i s t e n c y l e v e l=ONE)
throws ( 1 : I n v a l i d R e q u e s t E x c e p t i o n i r e , 2 :
NotFoundException nfe , 3 : U n a v a i l a b l e E x c e p t i o n ue ,
4 : TimedOutException t e ) ,
/∗ ∗
Get t h e group o f columns c o n t a i n e d by c o l u m n p a r e n t (
e i t h e r a ColumnFamily name or a ColumnFamily /
SuperColumn name
p a i r ) s p e c i f i e d by t h e g i v e n S l i c e P r e d i c a t e . I f no
matching v a l u e s are found , an empty l i s t i s
returned .
∗/
l i s t <ColumnOrSuperColumn> g e t s l i c e ( 1 : r e q u i r e d b i n a r y
key ,
2 : r e q u i r e d ColumnParent column parent ,
3: required SlicePredicate predicate ,
4: required ConsistencyLevel
c o n s i s t e n c y l e v e l=ONE)
throws ( 1 : I n v a l i d R e q u e s t E x c e p t i o n i r e , 2 :
U n a v a i l a b l e E x c e p t i o n ue , 3 : TimedOutException t e ) ,
89
/∗ ∗
Performs a g e t s l i c e f o r c o l u m n p a r e n t and p r e d i c a t e
for the given keys in p a r a l l e l .
@Deprecated ; use ‘ scan ‘
∗/
map<binary , l i s t <ColumnOrSuperColumn>> m u l t i g e t s l i c e ( 1 :
r e q u i r e d l i s t <binary > keys ,
2 : r e q u i r e d ColumnParent column parent ,
3: required SlicePredicate predicate ,
4: required ConsistencyLevel
c o n s i s t e n c y l e v e l=ONE)
throws ( 1 : I n v a l i d R e q u e s t E x c e p t i o n i r e , 2 :
U n a v a i l a b l e E x c e p t i o n ue , 3 : TimedOutException t e ) ,
/∗ ∗
r e t u r n s t h e number o f columns matching <code>p r e d i c a t e
</code> f o r a p a r t i c u l a r <code>key </code >,
<code>ColumnFamily</code> and o p t i o n a l l y <code>
SuperColumn</code >.
∗/
i 3 2 g e t c o u n t ( 1 : r e q u i r e d b i n a r y key ,
2 : r e q u i r e d ColumnParent column parent ,
3: required SlicePredicate predicate ,
4: required ConsistencyLevel
c o n s i s t e n c y l e v e l=ONE)
throws ( 1 : I n v a l i d R e q u e s t E x c e p t i o n i r e , 2 :
U n a v a i l a b l e E x c e p t i o n ue , 3 : TimedOutException t e ) ,
/∗ ∗
Perform a g e t c o u n t i n p a r a l l e l on t h e g i v e n l i s t <
b i n a r y > k e y s . The r e t u r n v a l u e maps k e y s t o t h e
count found .
∗/
map<binary , i 3 2 > m u l t i g e t c o u n t ( 1 : r e q u i r e d s t r i n g
keyspace ,
2 : r e q u i r e d l i s t <binary > keys ,
3 : r e q u i r e d ColumnParent column parent ,
4: required SlicePredicate predicate ,
5: required ConsistencyLevel
c o n s i s t e n c y l e v e l=ONE)
throws ( 1 : I n v a l i d R e q u e s t E x c e p t i o n i r e , 2 :
U n a v a i l a b l e E x c e p t i o n ue , 3 : TimedOutException t e ) ,
90
ANHANG B. THRIFT INTERFACE
/∗ ∗
r e t u r n s a s u b s e t o f columns f o r a range o f k e y s .
@Deprecated ; use ‘ scan ‘
∗/
l i s t <K e y S l i c e > g e t r a n g e s l i c e s ( 1 : r e q u i r e d ColumnParent
column parent ,
2: required SlicePredicate predicate ,
3 : r e q u i r e d KeyRange range ,
4: required ConsistencyLevel
c o n s i s t e n c y l e v e l=ONE)
throws ( 1 : I n v a l i d R e q u e s t E x c e p t i o n i r e , 2 :
U n a v a i l a b l e E x c e p t i o n ue , 3 : TimedOutException t e ) ,
/∗ ∗ Returns t h e s u b s e t o f columns s p e c i f i e d i n
S l i c e P r e d i c a t e f o r t h e rows r e q u e s t e d i n
RowsPredicate ∗/
l i s t <K e y S l i c e > scan ( 1 : r e q u i r e d ColumnParent
column parent ,
2 : r e q u i r e d RowPredicate r o w p r e d i c a t e ,
3: required S l i c e P r e d i c a t e column predicate
,
4: required ConsistencyLevel
c o n s i s t e n c y l e v e l=ONE)
throws ( 1 : I n v a l i d R e q u e s t E x c e p t i o n i r e , 2 :
U n a v a i l a b l e E x c e p t i o n ue , 3 : TimedOutException t e ) ,
/∗ ∗ Counts t h e s u b s e t o f columns s p e c i f i e d i n
S l i c e P r e d i c a t e f o r t h e rows r e q u e s t e d i n
RowsPredicate ∗/
l i s t <KeyCount> s c a n c o u n t ( 1 : r e q u i r e d ColumnParent
column parent ,
2 : r e q u i r e d RowPredicate r o w p r e d i c a t e ,
3: required S l i c e P r e d i c a t e column predicate
,
4: required ConsistencyLevel
c o n s i s t e n c y l e v e l=ONE)
throws ( 1 : I n v a l i d R e q u e s t E x c e p t i o n i r e , 2 :
U n a v a i l a b l e E x c e p t i o n ue , 3 : TimedOutException t e ) ,
# m o d i f i c a t i o n methods
/∗ ∗
91
∗ I n s e r t a Column a t t h e g i v e n c o l u m n p a r e n t .
c o l u m n f a m i l y and o p t i o n a l c o l u m n p a r e n t .
super column .
∗/
void i n s e r t ( 1 : r e q u i r e d b i n a r y key ,
2 : r e q u i r e d ColumnParent column parent ,
3 : r e q u i r e d Column column ,
4: required ConsistencyLevel
c o n s i s t e n c y l e v e l=ONE)
throws ( 1 : I n v a l i d R e q u e s t E x c e p t i o n i r e , 2 :
U n a v a i l a b l e E x c e p t i o n ue , 3 : TimedOutException t e ) ,
/∗ ∗
Remove d a t a from t h e row s p e c i f i e d by key a t t h e
g r a n u l a r i t y s p e c i f i e d by column path , and t h e g i v e n
c l o c k . Note
t h a t a l l t h e v a l u e s i n colum n pat h b e s i d e s colum n pat h
. c o l u m n f a m i l y are t r u l y o p t i o n a l : you can remove
the entire
row by j u s t s p e c i f y i n g t h e ColumnFamily , or you can
remove a SuperColumn or a s i n g l e Column by
s p e c i f y i n g those l e v e l s too .
∗/
void remove ( 1 : r e q u i r e d b i n a r y key ,
2 : r e q u i r e d ColumnPath column path ,
3 : r e q u i r e d Clock c l o c k ,
4 : C o n s i s t e n c y L e v e l c o n s i s t e n c y l e v e l=ONE)
throws ( 1 : I n v a l i d R e q u e s t E x c e p t i o n i r e , 2 :
U n a v a i l a b l e E x c e p t i o n ue , 3 : TimedOutException t e ) ,
/∗ ∗
Mutate many columns or s u p e r columns f o r many row k e y s
. See a l s o : Mutation .
mutation map maps key t o column f a m i l y t o a l i s t o f
Mutation o b j e c t s t o t a k e p l a c e a t t h a t s c o p e .
∗ ∗/
void batch mutate ( 1 : r e q u i r e d map<binary , map<s t r i n g ,
l i s t <Mutation>>> mutation map ,
2: required ConsistencyLevel
c o n s i s t e n c y l e v e l=ONE)
throws ( 1 : I n v a l i d R e q u e s t E x c e p t i o n i r e , 2 :
U n a v a i l a b l e E x c e p t i o n ue , 3 : TimedOutException t e )
92
ANHANG B. THRIFT INTERFACE
,
/∗ ∗
Truncate w i l l mark and e n t i r e column f a m i l y as d e l e t e d .
From t h e u s e r ’ s p e r s p e c t i v e a s u c c e s s f u l c a l l t o
t r u n c a t e w i l l r e s u l t c o m p l e t e d a t a d e l e t i o n from
cfname .
I n t e r n a l l y , however , d i s k s p a c e w i l l not be i m m e d i a t i l y
r e l e a s e d , as w i t h a l l d e l e t e s i n cassandra , t h i s
one
o n l y marks t h e d a t a as d e l e t e d .
The o p e r a t i o n s u c c e e d s o n l y i f a l l h o s t s i n t h e c l u s t e r
a t a v a i l a b l e and w i l l throw an U n a v a i l a b l e E x c e p t i o n
if
some h o s t s are down .
∗/
void t r u n c a t e ( 1 : r e q u i r e d s t r i n g cfname )
throws ( 1 : I n v a l i d R e q u e s t E x c e p t i o n i r e , 2 :
U n a v a i l a b l e E x c e p t i o n ue ) ,
// Meta−APIs −− APIs t o g e t i n f o r m a t i o n a b o u t t h e node
or c l u s t e r ,
// r a t h e r than u s e r d a t a . The nodeprobe program
p r o v i d e s u s a g e examples .
/∗ ∗
∗ a s k t h e c l u s t e r i f t h e y a l l are u s i n g t h e same
m i g r a t i o n i d . r e t u r n s a map o f v e r s i o n −>h o s t s −on−
t h a t −v e r s i o n .
∗ h o s t s t h a t d i d not respond w i l l be under t h e key
D a t a b a s e D e s c r i p t o r . INITIAL VERSION . agreement can be
determined
∗ by c h e c k i n g i f t h e s i z e o f t h e map i s 1 .
∗/
map<s t r i n g , l i s t <s t r i n g >> c h e c k s c h e m a a g r e e m e n t ( )
throws ( 1 : I n v a l i d R e q u e s t E x c e p t i o n i r e ) ,
/∗ ∗ l i s t t h e d e f i n e d k e y s p a c e s i n t h i s c l u s t e r ∗/
s e t <s t r i n g > d e s c r i b e k e y s p a c e s ( ) ,
/∗ ∗ g e t t h e c l u s t e r name ∗/
string describe cluster name () ,
93
/∗ ∗ g e t t h e t h r i f t a p i v e r s i o n ∗/
string describe version () ,
/∗ ∗ g e t t h e t o k e n r i n g : a map o f r a n g e s t o h o s t
addresses ,
r e p r e s e n t e d as a s e t o f TokenRange i n s t e a d o f a map
from range
t o l i s t o f e n d p o i n t s , b e c a u s e you can ’ t use T h r i f t
s t r u c t s as
map k e y s :
h t t p s : / / i s s u e s . apache . org / j i r a / browse /THRIFT−162
f o r t h e same reason , we can ’ t r e t u r n a s e t here ,
even t h o u g h
o r d e r i s n e i t h e r i m p o r t a n t nor p r e d i c t a b l e . ∗/
l i s t <TokenRange> d e s c r i b e r i n g ( 1 : r e q u i r e d s t r i n g
keyspace )
throws ( 1 : I n v a l i d R e q u e s t E x c e p t i o n i r e ) ,
/∗ ∗ d e s c r i b e s p e c i f i e d k e y s p a c e ∗/
map<s t r i n g , map<s t r i n g , s t r i n g >> d e s c r i b e k e y s p a c e ( 1 :
required s t r i n g keyspace )
throws ( 1 : NotFoundException n f e ) ,
/∗ ∗ e x p e r i m e n t a l API f o r hadoop / p a r a l l e l q u e r y s u p p o r t .
may change v i o l e n t l y and w i t h o u t warning .
r e t u r n s l i s t o f t o k e n s t r i n g s such t h a t f i r s t
subrange i s ( l i s t [ 0 ] , l i s t [ 1 ] ] ,
n e x t i s ( l i s t [ 1 ] , l i s t [ 2 ] ] , e t c . ∗/
l i s t <s t r i n g > d e s c r i b e s p l i t s ( 1 : r e q u i r e d s t r i n g keyspace ,
2 : r e q u i r e d s t r i n g cfName ,
3: required string start token ,
4 : r e q u i r e d s t r i n g end token ,
5: required i32 keys per split ) ,
/∗ ∗ adds a column f a m i l y . r e t u r n s t h e new schema i d . ∗/
s t r i n g s y s t e m a d d c o l u m n f a m i l y ( 1 : r e q u i r e d CfDef c f d e f )
throws ( 1 : I n v a l i d R e q u e s t E x c e p t i o n i r e ) ,
/∗ ∗ d r o p s a column f a m i l y . r e t u r n s t h e new schema i d . ∗/
s t r i n g system drop column family ( 1 : required s t r i n g
column family )
94
ANHANG B. THRIFT INTERFACE
throws ( 1 : I n v a l i d R e q u e s t E x c e p t i o n i r e ) ,
/∗ ∗ renames a column f a m i l y . r e t u r n s t h e new schema i d .
∗/
s t r i n g system rename column family ( 1 : required s t r i n g
old name , 2 : r e q u i r e d s t r i n g new name )
throws ( 1 : I n v a l i d R e q u e s t E x c e p t i o n i r e ) ,
/∗ ∗ adds a k e y s p a c e and any column f a m i l i e s t h a t are
p a r t o f i t . r e t u r n s t h e new schema i d . ∗/
s t r i n g s y s t e m a d d k e y s p a c e ( 1 : r e q u i r e d KsDef k s d e f )
throws ( 1 : I n v a l i d R e q u e s t E x c e p t i o n i r e ) ,
/∗ ∗ d r o p s a k e y s p a c e and any column f a m i l i e s t h a t are
p a r t o f i t . r e t u r n s t h e new schema i d . ∗/
s t r i n g system drop keyspace ( 1 : required s t r i n g keyspace )
throws ( 1 : I n v a l i d R e q u e s t E x c e p t i o n i r e ) ,
/∗ ∗ renames a k e y s p a c e . r e t u r n s t h e new schema i d . ∗/
s t r i n g s y s t e m r e n a m e k e y s p a c e ( 1 : r e q u i r e d s t r i n g old name
, 2 : r e q u i r e d s t r i n g new name )
throws ( 1 : I n v a l i d R e q u e s t E x c e p t i o n i r e ) ,
/∗ ∗ g e t ( ) v i a i n d e x ∗ ∗/
map<binary , l i s t <ColumnOrSuperColumn>> i n d e x g e t ( 1 :
r e q u i r e d s t r i n g index name ,
2 : r e q u i r e d map<binary , binary > key map ,
3 : r e q u i r e d ColumnPath column path ,
4: required ConsistencyLevel
c o n s i s t e n c y l e v e l=ONE)
throws ( 1 : I n v a l i d R e q u e s t E x c e p t i o n i r e , 2 :
U n a v a i l a b l e E x c e p t i o n ue , 3 : TimedOutException t e ) ,
/∗ ∗ g e t s l i c e ( ) v i a i n d e x ∗ ∗/
map<binary , l i s t <ColumnOrSuperColumn>> i n d e x g e t s l i c e ( 1 :
r e q u i r e d s t r i n g index name ,
2 : r e q u i r e d map<binary , binary > key map ,
3 : r e q u i r e d ColumnParent column parent ,
4: required SlicePredicate predicate ,
5: required ConsistencyLevel
c o n s i s t e n c y l e v e l=ONE)
throws ( 1 : I n v a l i d R e q u e s t E x c e p t i o n i r e , 2 :
95
U n a v a i l a b l e E x c e p t i o n ue , 3 : TimedOutException t e ) ,
/∗ ∗ m u l t i g e t s l i c e ( ) v i a i n d e x ∗ ∗/
map<binary , l i s t <ColumnOrSuperColumn>>
i n d e x m u l t i g e t s l i c e ( 1 : r e q u i r e d s t r i n g index name ,
2 : r e q u i r e d l i s t <map<binary , binary >>
key map list ,
3 : r e q u i r e d ColumnParent column parent ,
4: required SlicePredicate predicate ,
5: required ConsistencyLevel
c o n s i s t e n c y l e v e l=ONE)
throws ( 1 : I n v a l i d R e q u e s t E x c e p t i o n i r e , 2 :
U n a v a i l a b l e E x c e p t i o n ue , 3 : TimedOutException t e ) ,
/∗ ∗ g e t r a n g e s l i c e ( ) v i a i n d e x ∗ ∗/
map<binary , l i s t <ColumnOrSuperColumn>>
i n d e x g e t r a n g e s l i c e s ( 1 : r e q u i r e d s t r i n g index name
2 : r e q u i r e d ColumnParent column parent ,
3: required SlicePredicate predicate ,
4 : r e q u i r e d KeyMapRange key map range ,
5: required ConsistencyLevel
c o n s i s t e n c y l e v e l=ONE)
throws ( 1 : I n v a l i d R e q u e s t E x c e p t i o n i r e , 2 :
U n a v a i l a b l e E x c e p t i o n ue , 3 : TimedOutException t e ) ,
/∗ ∗ r e b u i l d an i n d e x ∗ ∗/
void i n d e x r e b u i l d ( 1 : r e q u i r e d s t r i n g index name )
throws ( 1 : I n v a l i d R e q u e s t E x c e p t i o n i r e , 2 :
U n a v a i l a b l e E x c e p t i o n ue , 3 : TimedOutException t e ) ,
/∗ ∗ v a l i d a t e an i n d e x ∗ ∗/
void i n d e x v a l i d a t e ( 1 : r e q u i r e d s t r i n g index name )
throws ( 1 : I n v a l i d R e q u e s t E x c e p t i o n i r e , 2 :
U n a v a i l a b l e E x c e p t i o n ue , 3 : TimedOutException t e ) ,
}
Herunterladen