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 ) , }