stefan EDLICH achim FRIEDLAND jens HAMPE benjamin BRAUER N SQL EINSTIEG IN DIE WELT NICHTRELATIONALER WEB 2.0 DATENBANKEN EMPFOHLEN VON 2 2 NoSQL – Theoretische Grundlagen Wenn Sie NoSQL-Datenbanken einsetzen wollen, sollten Sie auch mit den wichtigsten Grundbegriffen und theoretischen Ansätzen auskennen, auf denen diese Systeme aufbauen. Einige davon sind quasi traditionelle Verfahren der klassischen Datenbankwelt und Ihnen vielleicht schon bekannt. Daneben gibt es aber in der NoSQL-Welt auch Algorithmen und Protokolle, die bisher noch nicht häufig verwendet wurden. Diese wollen wir in diesem Kapitel vorstellen. Dazu zählen: Map/Reduce CAP-Theorem/Eventually Consistent Consistent Hashing MVCC-Protokoll Vector Clocks Paxos Mit diesen Grundlagen sind Sie gut gerüstet, wenn Sie sich die NoSQL-Welt erobern wollen, weil die meisten NoSQL-Datenbanken auf diesem minimalen Fundament aufbauen. Eine vollständige Darstellung der Theorie, die den NoSQL-Systemen zugrunde liegt, würde eigentlich ein eigenes Buch erfordern. Im Rahmen unseres Buches können und wollen wir nur die wichtigsten Themen ansprechen. Wenn Sie noch tiefer in die Thematik einsteigen wollen, als wir es in dieser Einführung tun, müssten Sie sich auch noch mit folgenden Themen auseinandersetzen: B- und B*-Trees sowie beispielsweise Quad-Trees, die für Key/Value-Datenbanken, räumliche Indexierung und Kollisionserkennung wichtig sind. Transaktionsprotokolle und Consensus-Protokolle; Protokolle wie 2PC, 3PC sind für die klassische Datenbankwelt und auch für NoSQL wichtige Grundlagen. Diese sind aber in der Literatur sehr gut behandelt. Die Themen Replikation, Partitionierung und Fragmentierung der Daten spielen für NoSQL ebenfalls eine wichtige Rolle. Zu diesem Bereich gehören evtl. auch verschiedene Quorum-Strategien. 11 2 NoSQL – Theoretische Grundlagen Literatur zu allen obigen Themen finden Sie entweder in Büchern zu Algorithmen oder in umfassenderen Standardwerken zum Thema Datenbanken: Gunter Saake, Kai-Uwe Sattler, Algorithmen und Datenstrukturen: Eine Einführung mit Java, dpunkt Verlag, 2010 Hector Garcia-Molina, Jeffrey D. Ullman und Jennifer Widom, Database Systems. The Complete Book, Pearson International, 2nd Edition, 2008 2.1 Map/Reduce Um die rasant wachsende Menge von Daten und Informationen effizient verarbeiten zu können, wurden neue alternative Algorithmen, Frameworks und Datenbankmanagementsysteme entwickelt. Bei der Verarbeitung großer Datenmengen in der Größenordnung von vielen Terabytes bis hin zu mehreren Petabytes spielt das in diesem Kapitel beschriebene Map/Reduce-Verfahren eine entscheidende Rolle. Mittels eines Map/Reduce-Frameworks wird eine effiziente nebenläufige Berechnung über solch große Datenmengen in Computerclustern erst ermöglicht. Entwickelt wurde das Map/Reduce-Framework 2004 bei Google Inc. von den Entwicklern Jeffrey Dean und Sanjay Ghemawat. Eine erste Vorstellung und Demonstration, beschrieben in [Dean04], erfolgte auf der Konferenz „OSDI 04, Sixth Symposium on Operating System Design and Implementation“ in San Francisco, Kalifornien, im Dezember 20041. Im Januar 2010 hat Google Inc. auf das dort vorgestellte Map/Reduce-Verfahren vom USamerikanischen Patentbüro ein Patent erhalten2. Experten sind sich allerdings einig, dass es sich hierbei um ein Schutzpatent für Google selbst handelt und Google nun keine Klagewelle beginnen wird. Die grundlegende Idee, Komponenten, Architektur und Anwendungsbereiche des Map/Reduce-Verfahrens werden in diesem Abschnitt 2.1 vorgestellt und beschrieben. Die zahlreichen Implementierungen, die seit der ersten Vorstellung dieses Verfahrens entwickelt wurden, werden zur Übersicht kurz aufgelistet. Abgerundet wird dieser Abschnitt durch ein einfaches Einsatzbeispiel, welches zum praktischen Nachvollziehen des beschriebenen Map/Reduce-Algorithmus ermuntern soll. 2.1.1 Funktionale Ursprünge Die Parallelisierung von Prozessen beginnt bei der Formulierung von Algorithmen. Parallelisierung ist eine Stärke der funktionalen Sprachen. Die Grundidee von Map/Reduce kommt daher auch von funktionalen Programmiersprachen wie LISP3 und ML4. Hinsicht1 http://labs.google.com/papers/mapreduce.html, 28.02.2010 Patent Nummer: US007650331; zu finden bei http://patft.uspto.gov 3 List Processing 4 Meta Language 2 12 2.1 Map/Reduce lich der Parallelisierung bieten funktionale Sprachen aufgrund ihrer Arbeitsweise Vorteile gegenüber anderen Sprachen. Es entstehen keine Seiteneffekte wie Verklemmungen (deadlock) und Wettlaufsituationen (race conditions). Funktionale Operationen ändern die vorhandenen Datenstrukturen nicht, sie arbeiten immer auf neu erstellten Kopien vorhandener Daten. Die Originaldaten bleiben unverändert erhalten. Unterschiedliche Operationen auf dem gleichen Datensatz beeinflussen sich somit nicht gegenseitig, da jede Operation auf einer eigenen Kopie der Originaldaten angewendet wird oder bei Datenergänzungen eine neue Datenstruktur erzeugt wird. Ohne Seiteneffekte spielt auch die Ausführungsreihenfolge von Operationen keine Rolle, wodurch die Parallelisierung dieser Operationen möglich wird. Das Konzept einer Funktion im Sinne der Mathematik ist in der funktionalen Programmierung am klarsten umgesetzt. Hier stellen die Funktionen Abbildungsvorschriften dar. Eine Funktion besteht dann aus einer Reihe von Definitionen, die diese Vorschrift beschreibt. Ein funktionales Programm besteht ausschließlich aus Funktionsdefinitionen und besitzt keine Kontrollstrukturen wie Schleifen. Wichtigstes Hilfsmittel für die funktionale Programmierung ist daher die Rekursion. Funktionen sind in funktionalen Programmiersprachen Objekte, mit denen wie mit Variablen gearbeitet werden kann. Insbesondere können Funktionen als Argument oder Rückgabewert einer anderen Funktion auftreten. Man spricht dann von Funktionen höherer Ordnung. Die aus der funktionalen Programmierung bekannten Routinen map() und fold(), auch als reduce() bezeichnet, werden in modifizierter Form im Map/Reduce-Algorithmus jeweils nebenläufig in zwei Phasen ausgeführt. Sie zählen zu den Funktionen höherer Ordnung. Wie der Name der ältesten funktionalen Programmiersprache LISP (= List Processing) schon verrät, geht es dabei um die Verarbeitung von Listen. Die Funktion map() wendet eine Funktion sukzessive auf alle Elemente einer Liste an und gibt eine durch die Funktion modifizierte Liste zurück. Die Funktion reduce() akkumuliert einzelne Funktionsergebnisse der Listenpaare und reduziert sie damit auf einen Ausgabewert. Diese beiden Funktionen werden in modifizierter Ausprägung als Map/Reduce-Algorithmus jeweils parallel auf verschiedenen Knoten im Netzwerk in zwei Phasen hintereinander angewendet. Das Besondere an der Map/Reduce-Formulierung ist, dass sich mit den zwei Phasen jeweils eine Parallelisierungsmöglichkeit ergibt, die innerhalb eines Computer-Clusters für eine beschleunigte Berechnung von sehr großen Datenmengen verwendet werden kann. Bei solchen Datenmengen ist eine Parallelisierung unter Umständen allein schon deshalb erforderlich, weil diese Datenmengen für einen einzelnen Prozess und das ausführende Rechnersystem bereits zu groß sind. Die map()-Funktion der funktionalen Sprachen erhält als Argument eine Funktion f und wendet diese auf jedes Element einer übergebenen Liste an. Es ist eine polymorphe Funktion, die beliebige Argumenttypen erhalten kann, wie durch die Typvariablen a und b in Listing 2.1.1, angegeben ist. 13 2 NoSQL – Theoretische Grundlagen Listing 2.1.1 Haskell-Definition der map-Funktion5 map :: (a -> b) -> [a] -> [b] map f [] = [] map f (x : xs) = f x : map f xs Die erste Zeile in der Funktionsdefinition wird als Typsignatur bezeichnet und beschreibt die Aufgabe der Funktion. Sie wendet die Funktion (a->b) auf eine Liste [a] an und gibt die Liste [b] zurück. Die zweite und dritte Zeile definieren das Verhalten der map()Funktion für verschiede Eingabemuster. Bei Eingabe einer Funktion f und einer leeren Liste [] wird als Ergebnis auch nur eine leere Liste [] zurückgegeben. Die dritte Zeile zeigt, dass bei Eingabe einer Funktion f und einer Liste, die durch die Listenkonstruktion(x:xs)dargestellt wird, die Funktion f auf das erste Listenelement x und anschließend dann rekursiv auf die restliche Liste xs angewendet wird. Ein Funktionsaufruf in Haskell mit folgenden Angaben: map (\x -> x^2) [1,2,3,4,5] ergibt das Ergebnis, wie auch in Abbildung 2.1.1 dargestellt: [1,4,9,16,25] Listenelemente der Eingabe 1 2 3 4 5 f f f f f 1 4 9 16 25 Listenelemente der Ausgabe Abbildung 2.1.1 Anwenden der map()-Funktion Die map()-Funktion -lässt sich nach Definition auf beliebige Datentypen anwenden. Das folgende Beispiel wendet die Funktion toUpper auf jedes Zeichen des Strings „nosql“ an. Um die Funktion toUpper nutzen zu können, muss sie vorher durch den Befehl :module +Data.Char in den Arbeitsbereich importiert werden: :module +Data.Char map toUpper "nosql" und erzeugt damit die Zeichenausgabe in Großbuchstaben (siehe auch Abbildung 2.1.2): "NOSQL" Die Reihenfolge der Elementeingabe und Elementausgabe bleibt bei dieser n-zu-n-Transformation erhalten, wobei n = Anzahl der Listenelemente ist. 5 14 Vgl. [Rech02] Seite 559 2.1 Map/Reduce Listenelemente der Eingabe n o s q l f f f f f N O S Q L Listenelemente der Ausgabe Abbildung 2.1.2 Anwendung der map()-Funktion mit toUpper Die fold()-Funktion realisiert quasi eine n-zu-1-Transformation und wird in diesem Zusammenhang auch in anderen Sprachen als reduce-, accumulate-, compress- oder injectFunktion bezeichnet. Das Ergebnis dieser Transformation muss nicht aus einem Element bestehen, es kann auch wiederum eine Liste von reduzierten Elementen sein. In Haskell wie auch in vielen anderen funktionalen Programmiersprachen werden zwei Varianten von fold() unterschieden: die foldl-Funktion für die Bearbeitung einer Liste von links nach rechts und die foldr-Funktion für die Bearbeitung einer Liste von rechts nach links6. Listing 2.1.2 Haskell-Definitionen für foldl foldl :: (a -> b -> a) -> a -> [b] -> a foldl f z [] = z foldl f z (x:xs) = foldl f (f z x) xs Listing 2.1.3 Haskell-Definitionen für foldr foldr :: (a -> b -> b) -> b -> [a] -> b foldr f z [] = z foldr f z (x:xs) = f x (foldr f z xs) Die Typsignaturen der Funktionen in den jeweils ersten Zeilen geben die Aufgaben der Funktionen wieder. Die Argumente der foldl-Funktion bestehen aus einer Schrittfunktion (a -> b -> a), einem initialen Wert für einen Akkumulator a und einer Eingabeliste [b]. Die Schrittfunktion (a -> b -> a)wendet den Akkumulator a auf ein Listenelement b an und gibt einen neuen Akkumulator a zurück. Die Schrittfunktion wird nun rekursiv für alle Listenelemente angewendet und liefert den akkumulierten Rückgabewert a. Das Verhalten für verschiedene Eingabemuster wird in den jeweils folgenden beiden Zeilen des Listings definiert. Bei Eingabe einer Schrittfunktion f, einem Akkumulatorwert z und einer leeren Liste [] besteht das Ergebnis auch nur aus dem initialen Wert des Akkumulators z. Bei Eingabe einer Schrittfunktion f, einem Akkumulatorwert z und einer nicht leeren Liste (x:xs) wird foldl f rekursiv auf alle Listenelemente xs angewendet, wobei der jeweils neue Anfangswert das Ergebnis der Zusammenlegung des alten ursprünglichen Wertes z mit dem nächsten Element x ist, ausgedrückt durch (f z x). 6 Vgl. [Rech02] Seite 559-560 15 Register . .NET-4.0 207 10 10gen 101, 116 A Abfrage 277 Acceptors 48 ACID 30, 184, 276 ACM 31 Adjazenzliste 176 Adjazenzmatrix 174 Adobe 55 Amazon 1, 34 Amazon Dynamo 157, 259 Amazon EC2 145 Amazon Elastic MapReduce 24 Amazon Web Services 83 Apache Hadoop 55 Apache Hama 248 Apache Jackrabbit 259 Apache Thrift 272 App Engine 268 Armstrong, Joe 103 Atom 223 Ausfalltoleranz 31 Availability 31 AWS 83 B Backup 280 Backup/Restore 280 baidu.com 256 ballots 48 BASE 33, 156, 276 BashReduce 24 BBC 114 BerkeleyDB 1, 228, 267 BerkeleyDB XML 270 BigTable 1, 54, 69, 268 Breitensuche 176 BSON 117 B-Tree 266 Bucket 156 Bulk-Kommando 137 Business-Daten 273 C C# 204 Canonical 114 CAP-Theorem 31 Cassandra 69 Causal Consistency 34 Cell-Prozessorserie 24 Chord 39 Chordless 146 Chubby-Prozess 50 Chunk 126 Client-Sharding 144 Cloud-Computing 85 285 Register Clustering 158 Codd 180 Column Families 6 Consistency 31 Consistent-Hashing 36, 260 CouchApps 113 CouchDB 24, 102 CouchDB-Lounge 113, 260 Couchio 106 Crash Resistance 281 C-Store 53, 270 cURL 108 D Data Access Pattern 279 Data-Structure Server 144 Data-Warehouse 53 Daten- und Speichermodell 274 Datenart 273 Datenkomplexität 275 Datenmenge 275 Datennavigation 275 Datenzugriffsmuster 279 db4o 1, 270 DBObject 120 DEX 215 Dictionary 144 Disco 24 Django 257 Document Stores 6 Domain-Daten 273 Dynomite 260 E EAV-Model 181 EMC Documentum 270 Emit 19 Engine Yard 114 Erlang/OPT 103 ETS 265 Euler, Leonhard 172 Eulerkreise 177 Evans, Eric 1 286 Event-Daten 273 eXist 270 F Facebook 1 Filament 251 FileMap 23 Filtering 192 flaps 243 FlockDB 242, 268 FluidDB 53 fold 13 funktionale Sprache 13 Futon 107, 111 G Gemstone 270 geographische Daten 273 GFS 1, 20 GigaSpaces 7, 269 Gizzard 268 Go 24 Google 12, 50, 268 Google Pregel 245 Google Query Language 268 GQL 196, 200, 268 Graph Query Language 196 Graphdatenbank 6 Graphtraversierungen 176 Greenplum 23, 270 Gremlin 190 Grep 22 Grid-Datenbank 6 GridFS 117 GridGain 23 GROUP BY 124 GT.M 1, 268 GXEF 207 H Hamiltonkreis 177 Hamiltonweg 177 Hashes 140 Register Hashfunktionen 36 Haskell 14 Hazelcast 7, 269 HBase 54 HDFS 68, 256 Hidden-Handoff 157 Hinted Handoff 165 Hive 272 Holumbus 24 HQL 256 HyperGraphDB 222 Hyperkante 174 HyperSQL 151 Hypertable 256 I IBM 103 IBMs Lotus 269 InfiniteGraph 229 InfoGrid 1, 209 Inzidenzmatrix 175 IP-Adresse 44 ISIS 270 J Jabber 227 Java 161 JavaScript 104, 116, 163 JDBC 141 JDO 268 join 163, 171 JPA 268 JRuby 58, 187 JSON 8, 62, 103, 156, 182 Judd, Doug 256 JVM 195 K KAI 261 Key/Hash-Datenbank 1 keyspace 50 Keyspaces 54 Key/Value 6 Konsistenz 30 Kosten 85 Kryptographie 36 Kyoto Cabinet 266 L Leader 48 Link 157 LinkedIn 1 link-walking 163 LINQ 4 LISP 13 Liste 138 Location Based Services 8 Log-Daten 273 Lotus Notes 1 Lucene 185 M MAC-ID 44 map 13 Map/Reduce 1, 12, 123 MarkLogic 270 Master 20 MEMBASE 261 Memcached 1 MemcacheDB 267 Message-Daten 273 Metadaten 273 MonetDB 53, 270 MongoDB 24, 115 Monotonic Read Consistency 35 Monotonic Write Consistency 35 Multi-Node Cluster 58 Multiprozessor 144 MVCC 41, 256 MySpace 1 MySql 31 MySQL 243 N Neo4j 1, 8, 184 NVIDIA GPUs 24 287 Register O Objectivity 229, 270 Objectivity/DB 229 Objektdatenbank 6, 270 objektorientiertes Datenmodell 181 OLAP 53 Open Source 3 OpenSSL 105 OrientDB 236, 258 Oskarsson, Johan 1 P PaaS 268 Paging 139 Partition Tolerance 31 Patent 12 Pattern-Matching 166 Paxos 48, 265 Peer-to-Peer 155 Performance-Dimensionen 277 Phoenix 23 Pig 272 Pipelining 144 Postgres 31 Prevayler 269 Primärschlüssel 56 Progress 270 Property-Graph 198, 230 Proposer 48 protobuf 62 Prüfsummen 36 Pruning 191 Q Qizmt 24 Qt-Concurrent 24 Quorum 166 Quorum-Consensus-Algorithmen 47 R RAM 132 RavenDB 259 RDF 8 288 RDF-Schema 182 Read-your-write Consistency 34 rediff.com 256 Redis 132 redis-conf 143 Refactoring 279 RegionServer 66 Replikation 127, 157, 279 REST 4, 156, 188 Riak 39, 101, 156 Ring 38, 154, 164 Ring-Adressraum 156 Ruby 141, 162 rufus-tokyo 266 S SaaS 97, 268 Sanfillippo, Salvatore 132 Scala 142 Scalaris 50, 264 Scale-out 277 Scale-up 277 Scalien 50 Schema 116 Schlüsselraum 164 SciDB 54 Scratchpad 89 Sector/Sphere 24 Sedna 270 Semantic Web 181 Session Consistency 34 Session-Daten 273 Sets 138 Shard 126 Sharding 125, 194 shared memory 19 shared-nothing 157 Shell 221 Sibling 160 Sicherheit 280 Single-Node Cluster 58 Skalierbarkeit 3 Skynet 24 Register SNMP-Protokoll 158 SOAP 84 Solr 185 sones 196 SonesDB 8 Spaltenfamilie 56 SPARQL 189 Sprache, funktionale 13 SQL 166 Stargate 61 Strozzi, Carlo 1, 5 StumbleUpon 55 Super Columns 72, 78 SwapSpace 265 Sybase IQ 53, 270 T Tamino 270 telnet 135 temporäre Daten 273 Terrastore 258 Thompson, Ken 1 Thrift 63, 256 ThruDB 259 Tiefensuche 176 Time To Live 137 Timeout 164 Tinkerpop 189 Tokyo Cabinet 265, 266 Tokyo Dystopia 266 Tokyo Promenade 266 Tokyo Tyrant 266 Traveling Salesman 177 Traverser 191 Tupel 117 Tuple Stores 6 Twister 23 Twitter 243, 268 two-phase commit 184 Two-Phase-Commit-Protokoll 47 U Ubuntu 114, 159 UNIX 133 UTF-8 141 UUID 187 V Vector Clock 43, 158, 160, 260 Verfügbarkeit 31 Versant 270 Versions-Vektoren 44 VertexDBs 248 Vertica 270 Views 104, 111 virtueller Server 39 VMWare 132 Vogels, Werner 34 vNodes 165 W Web Ontology 182 WebShell 200 Wide Column Stores 6 Worker 20 X X.509 88 Xindice 270 XLINK 182 XML-Datenbank 6, 270 XMPP 227 XPRISO 210 Y Yahoo 1, 55 YAML 8 Z zookeeper 57 289