Scala und NoSQL

Werbung
WP Scala WS2010/11: Scala und NoSQL
Tom Klonikowski, HAW Hamburg Department Informatik
17.12.2010
Zusammenfassung
Neben den traditionellen relationalen Datenbanksystemen haben sich
neue Arten von Datenspeichern entwickelt. Vor die Erfordernisse des Web
2.0 führten oenbar zu einem Innovationsschub in diesem Bereich. Dieser
Artikel gibt einen Überblick über die alternativen Datenbanktechnologien,
die mittlerweile gemeinhin unter dem Begri NoSQL - Not only SQL
zusammengefasst werden, und gibt eine Einführung in den Einsatz mit
Scala.
1
Motivation
Relationale Datenbanken speichern Daten in Datensätzen (n-Tupel), die in Tabellen
(zweidimensional) abgelegt werden. In der objektorientierten Programmierung
werden jedoch häug sehr komplexe Objekte verwaltet. Sollen diese in einem
RDBMS gespeichert werden führt dies zwangsläug zu einer Segmentierung über
mehrere verknüpfte Tabellen. Damit einher geht die Notwendigkeit, ein festgelegtes Schema zu erstellen und einzuhalten. Bibliotheken für objekt-relationales
Mapping sollen diese Konversion transparent machen, bringen dadurch aber
eigene Komplexität in Softwareprojekte.
Zudem entwickelten sich durch die Anwendungen des Web 2.0 (soziale
Netzwerke, (Micro-)Blogging) besondere Anforderungen an die Datenspeicher. Riesige Datenmengen mit weltweiter Zugrismöglichkeit erfordern ein hohes
Maÿ an Skalierbarkeit und Redundanz. Andererseits werden geringere Garantien
hinsichtlich der Konsistenz und Transaktionen gefordert.
2
Varianten alternativer Datenbankkonzepte
1
Die neuen Datenbankkonzepte lassen sich wie folgt unterteilen :
Dokumentenorientierte Datenbanken
: Lotus Notes, CouchDB, MongoDB,
Tamino
Graphendatenbanken
1 Auszug
: AllegroGraph, Core Data, Neo4j, GraphDB
aus http://de.wikipedia.org/wiki/NoSQL
1
(Sortierte) Key-Value-Speicher
: SimpleDB, BigTable, memcached, Mem-
cachedb oder Berkeley DB
Multivalue-Datenbanken
Objektdatenbanken
: OpenQM oder Rocket U2
: Db4o oder ZODB
Diese Konzepte bieten jeweils unterschiedliche Vorteile. Während dokumentenorientierte Datenbanken die Möglichkeit bieten, beliebige Dokumente zu speichern,
sind es bei Key-Value-Speichern nur Schlüssel-Wert-Paare, die jedoch hochefzient und redundant abgelegt werden können. Objektdatenbanken speichern
Objekte direkt und sind daher in der Regel an eine bestimmte Programmiersprache gebunden (Db4o
3
Java, ZODB
Python).
Dokumentenorientierte Datenbanken
Dokumentenorientierte Dankenbanken speichern Dokumente. Ein Dokument
kann aus einer Reihe von Key-Value-Paaren bestehen, die geschachtelt werden
können, z.B. deniert durch XML oder JSON. Dateien, strukturiert durch ein
bestimmtes Dateiformat (z.B. ODF) oder unstrukturiert im Sinn eines Datenzugris (z.B. MP4), können ebenfalls als Dokumente angesehen werden.
Die Idee ist, Abbildungen der Entitäten der realen Welt möglichst 1-zu1 in ein Dokument zu überführen und zu speichern. Die Daten einer Entität
Rechnung sollen also nicht in Empfänger, Adresse, eine Liste von Rechnungsposten usw. zerlegt und in verschiedenen Tabellen gespeichert werden,
sondern in einem Dokument - siehe Beispiel in Abbildung 1 auf der nächsten
Seite.
Hier ist sicher der Einwand naheliegend, dass der Empfänger bereits als
Kunde gespeichert sein wird und es deswegen inezient erscheint, ihn in jeder
Rechnung abzulegen. Auch bei dokumentenorientierten Datenbanken ist also
eine Verknüpfung von Dokumenten notwendig. Dies ist jedoch ausserhalb des
Fokus dieser Einführung.
Semistrukturierte Daten lassen sich jedoch schwer für eine eziente Suche
oder Auswertungen indexieren. Dazu sind Funktionen notwendig, die die unstrukturierten Daten strukturieren, auch Mapping genannt.
4
Beispiel: CouchDB2
Eine der neuesten dokumentenorientierten Datenbanken ist CouchDB, ein Projekt der Apache Software Foundation, welches mittlerweile in Version 1.0.1 vorliegt.
2 http://couchdb.apache.org
2
<?xml version="1.0" encoding="UTF-8"?>
<rechnung nr="12300034">
<datum>20.01.2010</datum>
<empfaenger>
<name>VEB Massenhuehnereierei Schlottbek</name>
<adresse>
<strasse>Dioxinstr. 2</strasse>
<ort plz="20099">Hamburg</ort>
</adresse>
</empfaenger>
<posten anzahl="12" einheit="t">
<artikel>
<bezeichnung>Futterdiesel</bezeichnung>
<preis waehrung="EUR" bezugseinheit="t">199</preis>
</artikel>
</posten>
</rechnung>
Abbildung 1: Rechnung als XML-Dokument
CouchDB ist ein verteilter Datenbankserver,
der
Dokumente
schemafrei
speichert
und
eine
inkrementelle Replikation bietet. Die Dokumente
sind indexierbar und durchsuchbar. Dazu bietet
CouchDB die Möglichkeit, Mapping-Funktionen in
JavaScript als sogenannte views zu denieren.
Der Zugri auf die Datenbank erfolgt über
eine REST-JSON-API. Somit werden Dokumente
durch
POST-
oder
PUT-Requests
geschrieben
und durch GET-Requests gelesen. Jedes Dokument
aus
hat
der
dabei
Adresse
eine
des
eindeutige
URL,
Datenbankservers,
die
einem
Kollektionsnamen und der Dokumentenid besteht:
http://localhost:5984/example/1252231234.
So erzeugt ein POST-Request an die Adresse
Abbildung
2:
CouchDB
Komponenten
der Kollektion das gesendete Dokument in der
Datenbank - siehe Abbildung 3 auf der nächsten Seite. Um das so gespeicherte
Dokument zu laden, ist ein GET-Request an die Adresse der Kollektion, erweitert um die zurückgelieferte id des Dokumentes, zu senden (Abbildung 4).
Komplexere Abfragen der Datenbank basieren auf dem MapReduce-Prinzip.
Dabei wird zunächst eine Mapping-Funktion auf die Daten angewendet, welche
die Dokumente strukturiert. Das Ergebnis daraus wird danach durch die ReduceFunktion geltert. Diese Funktionen werden als Query an den Server übergeben.
3
POST http://127.0.0.1:5985/posts/ HTTP/1.0
Content-Length: 245
Content-Type: application/json
{
}
"Subject": "I like Plankton",
"Author": "Rusty",
"PostedDate": "2006-08-15T17:30:12-04:00",
"Tags": [ "plankton", "baseball", "decisions" ],
"Body": "I decided today that I don't like baseball. I like plankton."
HTTP/1.0 201 Created
Server: CouchDB/1.0.1 (Erlang OTP/R13B)
Location: http://127.0.0.1:5984/posts/123ad4567
Date: Thu, 16 Dec 2010 20:02:42 GMT
Content-Type: text/plain;charset=utf-8
Content-Length: 95 Cache-Control: must-revalidate
{
}
"ok": true,
"id": "123ad4567",
"rev": "1-1b33f6c3"
Abbildung 3: POST-Request zum Anlegen eines neuen Dokumentes und ServerResponse
4
GET http://127.0.0.1:5985/posts/123ad4567 HTTP/1.0
HTTP/1.0 201 Created
Server: CouchDB/1.0.1 (Erlang OTP/R13B)
Location: http://127.0.0.1:5984/posts/123ad4567
Date: Thu, 16 Dec 2010 20:03:42 GMT
Content-Type: text/plain;charset=utf-8
Content-Length: 275 Cache-Control: must-revalidate
{
}
"id": "123ad4567",
"Subject": "I like Plankton",
"Author": "Rusty",
"PostedDate": "2006-08-15T17:30:12-04:00",
"Tags": [ "plankton", "baseball", "decisions" ],
"Body": "I decided today that I don't like baseball. I like plankton."
Abbildung 4: GET-Request zum Laden des Dokumentes und Server-Response
5
CouchDB und Scala
Das Erzeugen von HTTP-Requests und das Auslesen der Responses lässt sich
unter Scala natürlich mit der Java-Klasse java.net.HttpURLConnection bewerkstelligen. Allerdings lieferte das das Dokument nur als InputStream, der dann
selbst geparst werden müsste. Also muss auch hier ein Mapping stattnden: aus
dem Dokument im JSON-Format sollen möglichst Instanzen von Klassen werden. Dieses komplexe Problem sollte bedacht werden, bevor die Wahl auf Scala
fällt, wenn es um die Verwaltung unstrukturierter Dokumente geht. Hier bieten
dynamisch typisierte Sprachen einen ungleich leichteren Zugang.
Glücklicherweise gibts es bereits wenigstens eine benutzbare Bibliothek, die
3
dieses Scala-JSON-Databinding übernimmt: scouchdb . Diese verwendet Reection und Annotationen, um JSON in Scala-Objekte zu überführen (Beispiel
siehe Abbildung 5 auf Seite 7).
6
Fazit
NoSQL-Datenbanken im Allgemeinen und CouchDB im Besonderen sind kein
Ersatz für relationale Datenbanken. Sie können aber je nach Aufgabenstellung
die bessere Alternative sein. Die Bibliotheken für das notwendige JSON-ScalaDatabinding scheinen noch in den Kinderschuhen zu stecken. Entwicklen sich in
diesem Bereich ausgereifte Lösungen, so verbänden sich die Vorteile der statisch
typisierten Sprache Scala mit denen der dokumentenorientierten Datenbank
3 https://github.com/debasishg/scouchdb
5
CouchDB. Beim Autor bleibt aber der Eindruck, das die unstrukturierten Dokumente auf der einen und die streng typisierten Objekte auf der anderen Seite
ebenfalls ein Impedance Mismatch darstellen.
6
import
import
import
import
scouch.db._
scala.reflect._
scala.annotation.target._
dispatch._
@BeanInfo case class Name(first:String, last:String) {
private def this() = this(null, null)
}
@BeanInfo case class Student(matrNr:String, name:Name, semester:Number) {
private def this() = this(null, null, null)
}
def main(args: Array[String]) {
val http = new Http
// erzeuge Verbindung zur CouchDB (Standard: localhost:5984)
val couch = Couch()
// Datenbank "example""
val db = Db(couch, "example")
// ein neuer Student
val s1 = Student("1234567", Name("Max", "Mustermann"), 17)
// erzeuge CREATE Request
val create = db.doc(s1)
// fuehre Request aus
// cresponse: Tuple2 (doc_id, rev_id)
val cresponse = http(create)
val doc_id = cresponse._1
val rev_id = cresponse._2
// erzeuge GET Request fuer eben angelegtes Dokument
val get = db.get[Student](doc_id)
}
// lresponse: Tuple3 (doc_id, rev_id, object)
val lresponse = http(get)
val s2 = lresponse._3
println(s2)
println(s1 == s2)
Abbildung 5: Einfaches Beispiel für das Speichern und Laden von Instanzen
einer nicht trivialen Case-Klasse
7
Herunterladen