Konzeption und Implementierung einer Sichtenverwaltung für NoSQL–Datenbanken Diplomarbeit Universität Rostock Fakultät für Informatik und Elektrotechnik Institut für Informatik vorgelegt von: Matrikelnummer: geboren am: Erstgutachter: Zweitgutachter: Betreuer: Abgabedatum: Norman Soetbeer 6201050 18.01.1985 in Schwerin PD Dr. -Ing. habil. Meike Klettke Prof. Dr.-Ing. Thomas Kirste PD Dr. -Ing. habil. Meike Klettke 1. Oktober 2014 Zusammenfassung NoSQL-Datenbanken sind in der Regel als schemalos oder schemaflexibel anzusehen. Mit dieser Flexibilität wächst jedoch auch der Aufwand, heterogene Daten innerhalb von Anwendungen zu verarbeiten. In dieser Diplomarbeit wird untersucht, inwiefern Sichtenkonzepte aus der SQL-Welt auf NoSQL-Datenbanken übertragen und genutzt werden können, um diese Heterogenitäten zu beseitigen. Hierzu werden Sichtendefinition für NoSQL-Systeme mit Hilfe von Beispielen beschrieben. Anschließend wird ein Konzept für die Sichtenverwaltung und -anfrage entwickelt und in Form eines Prototyps für die Dokumentdatenbank MongoDB umgesetzt. Die Implementierung für eine Middleware auf Node.js-Basis wird schrittweise beschrieben Es erfolgt eine Evaluierung dieses Sichtenkonzepts hinsichtlich der praktischen Nutzung sowie der Übertragbarkeit auf andere NoSQL-Datenbanken. Schlüsselwörter NoSQL, MongoDB, Sichtenverwaltung, Sichtdefinition 3 Inhaltsverzeichnis 1 Einleitung 9 2 Begleitendes Beispiel 2.1 Beispielanwendung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.2 Schema-Evolution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.3 Verwendete Datenstrukturen . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 11 12 12 3 State of the Art 3.1 Sichten in SQL . . . . . . . . . . . . . 3.1.1 Aufgaben von Sichten in SQL . 3.1.2 Arten von Sichten . . . . . . . 3.2 NoSQL-Datenbanken . . . . . . . . . . 3.2.1 Dokumentdatenbanken . . . . . 3.2.2 Spaltenorientierte Datenbanken 3.3 NoSQL-Anfragesprachen . . . . . . . . 3.3.1 Map-Reduce . . . . . . . . . . 3.4 MongoDB . . . . . . . . . . . . . . . . 3.4.1 Dokumentverwaltung . . . . . . . . . . . . . . . 15 15 15 16 17 17 18 18 18 19 19 . . . . . . 25 25 25 26 29 30 31 . . . . . . . . . . 33 33 33 33 34 34 35 35 35 36 36 4 Sichtdefinitionen für NoSQL-Systeme 4.1 Anforderungen an Sichtdefinitionen 4.1.1 Auswahl von Attributen . . 4.1.2 Generierung von Attributen 4.1.3 Selektion von Dokumenten 4.2 Entwicklung einer Sichtdefinition . 4.2.1 Updates auf Sichten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 Konzeption einer Sichtenverwaltung in MongoDB 5.1 Sichtenverwaltung . . . . . . . . . . . . . . . . 5.2 Sicht-Anfrage . . . . . . . . . . . . . . . . . . . 5.3 Materialisierte Sichten . . . . . . . . . . . . . . 5.4 Virtuelle Sichten . . . . . . . . . . . . . . . . . 5.5 Architektur . . . . . . . . . . . . . . . . . . . . 5.6 Architektur A: Client-Middleware-Server . . . . 5.6.1 Aufgaben des Clients . . . . . . . . . . . 5.6.2 Aufgaben der Middleware . . . . . . . . 5.7 Architektur B: Client-Server . . . . . . . . . . . 5.8 Vergleich der Architekturen . . . . . . . . . . . 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Inhaltsverzeichnis 6 Implementierung eines Prototypen für die Sichtenverwaltung 6.1 Client . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.1.1 Projekt aufsetzen . . . . . . . . . . . . . . . . . . . 6.1.2 Erweiterung durch Prototyping . . . . . . . . . . . 6.2 Middleware . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2.1 Proxyserver . . . . . . . . . . . . . . . . . . . . . . 6.2.2 Wire Protocol . . . . . . . . . . . . . . . . . . . . . 6.2.3 Interceptor . . . . . . . . . . . . . . . . . . . . . . 6.2.4 Abfangen von Sichten-Operationen . . . . . . . . . . . . . . . . . 39 39 39 40 41 41 42 43 44 . . . . . . . . 49 49 49 49 49 51 51 51 52 8 Zusammenfassung 8.1 Aussichten und Erweiterbarkeit . . . . . . . . . . . . . . . . . . . . . . . . . . 53 53 A MongoDB Wire Protocol 55 B Installation und Nutzung der Middleware 59 Literaturverzeichnis 62 Abbildungsverzeichnis 63 Tabellenverzeichnis 65 Glossar 67 7 Evaluierung 7.1 Bewertung der Sichtdefinitionen . . . . . . . . 7.2 Performance-Analyse . . . . . . . . . . . . . . 7.2.1 Datenbankoperationen . . . . . . . . . 7.2.2 Ansätze zur Performanceverbesserung 7.3 Unit- und Integrationstests . . . . . . . . . . 7.3.1 Unit-Tests . . . . . . . . . . . . . . . . 7.3.2 Integrationstests . . . . . . . . . . . . 7.4 Homogenisierung von Dokumenten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . in MongoDB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 Abkürzungsverzeichnis API Application Programming Interface JSON JavaScript Object Notation BSON Binary JSON CQL Cassandra Query Language HQL Hypertable Query Language SQL Structured Query Language SOA Serviceorientierte Architektur 7 1. Einleitung In der Regel können NoSQL-Datenbanken als schemalos angesehen werden. Dies bedeutet, dass die gespeicherten Dokumente beliebig verschachtelte Strukturen aufweisen können und keinem übergeordneten Schema unterliegen. Die Anwendungen hingegen, welche NoSQLDatenbanken als Speicher verwenden, sind meist auf solch eine Struktur angewiesen, um den einzelnen Attributen Bedeutungen zuweisen zu können. Ein Problem stellt nun also die Strukturüberbrückung zwischen einzelnen Datensätzen der gleichen Art dar. Weiterhin ist es denkbar, dass mehrere Anwendungen die gleichen Basisdaten verwenden sollen, aber jeweils in einer anderen Repräsentation. Schaut man in den Bereich der SQL-Datenbanken, so findet man dort zumindest für das Problem der verschiedenen Repräsentationen bereits eine Lösung, nämlich Sichten (Views). Das andere Problem der Strukturunterschiede hingegen existiert dort nicht, da alle Datensätze einem Tabellenschema unterliegen. Im Rahmen dieser Diplomarbeit wird untersucht, inwiefern das Konzept der SQL-Sichten auch auf NoSQL-Datenbanken übertragbar ist und ob es auch oder insbesondere für die Überbrückung der Strukturunterschiede geeignet ist. Hierzu gilt es zunächst, den Aufbau einer möglichen Sichtdefinition für NoSQL-Systeme zu beschreiben. Es muss dabei geklärt werden, welche Informationen diese Definition enthalten soll und in welchem Format sie erstellt und abgespeichert wird. In einem weiteren Schritt soll untersucht werden, wie die Datensätze gemäß einer dazugehörigen Sichtdefinition transformiert werden können. Anschließend soll die Verwaltung von Sichtdefinitionen sowie die Ausführung der Transformationen inform eines Prototypen für die NoSQL-Datenbank MongoDB entwickelt werden. Der weitere Aufbau dieser Arbeit ist wie folgt unterteilt: • Kapitel 2: Begleitendes Beispiel In diesem Kapitel wird ein Beispielszenario beschrieben, welches im Rahmen dieser Arbeit zur Veranschaulichung von Konzepten dient. • Kapitel 3: State of the Art Das dritte Kapitel bietet eine Übersicht über bekannte Konzepte aus dem Datenbankbereich, die als Ausgangspunkt für diese Arbeit dienen. • Kapitel 4: Sichtdefinitionen für NoSQL-Systeme Im vierten Kapitel werden Anforderungen für den Aufbau einer Sichtdefinition für NoSQL-Datenbanken beschrieben. Darauf basierend wird solch eine Sichtbeschreibung entwickelt. • Kapitel 5: Konzeption einer Sichtenverwaltung in MongoDB Das fünfte Kapitel beschreibt die Konzeption einer Sichtenverwaltung für die Doku- 9 1. Einleitung mentdatenbank MongoDB. Hierzu werden Architekturen vorgestellt, die die Verwaltung von Sichtdefinitionen und Anfragen auf Sichten ermöglichen sollen. • Kapitel 6: Implementierung eines Prototypen für die Sichtenverwaltung in MongoDB Im sechsten Kapitel werden die Arbeitsschritte beschrieben, mit denen die Implementierung des Prototypen erfolgt. Hierzu werden die ausgearbeiteten Konzepte aus dem fünften Kapitel angewandt. • Kapitel 7: Evaluierung Im siebten Kapitel werden die Konzepte und die prototypische Implementierung bewertet. Es wird diskutiert, inwiefern die Konzepte dieser Arbeit die Probleme der Schemafreiheit von NoSQL-Systemen lösen können. • Kapitel 8: Zusammenfassung Das letzte Kapitel fasst die Abläufe und Ergebnisse dieser Arbeit zusammen und gibt einen Ausblick. 10 2. Begleitendes Beispiel Zur Veranschaulichung der Konzepte in dieser Arbeit wird ein begleitendes Beispielszenario entwickelt. Dieses beinhaltet typische Dokumente und Anwendungsfälle für Sichten, wie sie in heutigen Webanwendungen vorkommen. Die hier vorgestellten Dokumente dienen später ebenfalls zum Testen der prototypischen Implementierung. Aktuelle Webanwendungen sind oft modular aufgebaut. Teilweise wird diese Modularisierung auf Software-Ebene erreicht, manchmal aber auch durch dien Aufbau einer Serviceorientierten Architektur (SOA) [16]. Bei letzterem besteht diese - nach außen als Ganzes wirkende - Anwendung aus mehreren kleineren Service-Anwendungen, die auf bestimmte Aufgabenbereiche spezialisiert sind und über interne Schnittstellen miteinander kommunizieren. Über die einzelnen Softwaremodule bzw. Services hinweg sollen aber oft die gleichen Basisdaten verwendet werden. 2.1. Beispielanwendung In diesem Beispiel gehen wir von einer größeren Webarchitektur aus, die aus drei kleineren Services (Anwendungen) besteht: einem Blog, einem Onlineshop und einer API zum Datenaustausch mit externen Anwendungen. Die Schichten dieser Architektur und der Datenfluss zwischen den einzelnen Komponenten ist in Abbildung 2.1 dargestellt. Als Benutzer werden hier alle Kunden, Mitarbeiter und auch externe Anwendungen bezeichnet, die Daten über eine der bereitgestellten Anwendungen beziehen. Die Anwendungen bereiten die Rohdaten über die jeweilige Geschäftslogik auf und übernehmen Aufgaben wie Authentifizierung und Autorisierung. Dadurch wird der Zugriff auf die Bestandsdaten eingeschränkt. Das Datenbanksystem wird hier durch zwei Schichten dargestellt. Die unterste wird durch eine NoSQL-Datenbank repräsentiert und stellt nur die Funktionalität zur persistenten Speicherung und Anfrage von Dokumenten bereit. Anwendungen können entweder direkt auf die heterogenen Daten zugreifen oder über Sichten, welche somit den Zugriff auf homogenisierte Daten erlauben. Im Blogmodul dieser Beispielarchitektur veröffentlicht der Seitenbetreiber informative Einträge, die von registrierten Benutzern der Seite kommentiert werden können. Für die Anmeldung im Blogsystem werden Benutzername und Passwort benötigt. Für den Betrieb des Shopsystems werden unter anderem die, vom Benutzer hinterlegten, Kreditkartendaten benötigt. Außerdem müssen Produktinformationen verwaltet werden. Eine API soll Entwicklern externer Anwendungen die Möglichkeit geben, Produktinformationen automatisiert abzufragen. Der Zugriff soll jedoch auf freigeschaltete Produkte beschränkt sein. 11 2. Begleitendes Beispiel Abbildung 2.1.: Architektur und Datenfluss einer Webanwendung 2.2. Schema-Evolution Oft verändern sich die Anforderungen an Software im Laufe der Zeit. Durch die Einführung neuer oder die Erweiterung bestehender Funktionalitäten kann es erforderlich sein, die Schemata der gespeicherten Daten ebenfalls anzupassen. Typischerweise werden einzelne Datensätze in NoSQL-Datenbanken dabei separat versioniert. Die Einführung eines ”version”Attributs zu jedem Dokument ist eine bewährte Methode, um das jeweils verwendete Schema zu identifizieren [18]. Es beinhaltet die jeweilige Versionsnummer des Schemas, in dem das Dokument vorliegt. Wird das Dokument einer Schemaänderung unterzogen, so muss die Versionsnummer jeweils inkrementiert werden. 2.3. Verwendete Datenstrukturen Nachfolgend werden die benötigten Datenstrukturen in Form von JSON-Objekten beschrieben. Es werden Attribute aufgeführt, die für die Veranschaulichung der Beispiele relevant sind. Benutzer (Version 1). In Listing 2.1 ist ein Dokument dargestellt, das einen Benutzer repräsentiert. Es liegt in der Schemaversion 1 vor und beinhaltet die notwendigen Daten für die Blog- und Shopanwendungen. 12 2.3. Verwendete Datenstrukturen Listing 2.1: Struktur eines Benutzer-Dokuments, Schemaversion 1 1 { " name " : " Peter Petersen " , " passwort " : " s3cr3t " , " kreditkarten " : [ { " typ " : " visa " , " kartennummer " : "123456789012" } ], " version " : 1 2 3 4 5 6 7 8 } Benutzer (Version 2). Im Zuge umfangreicher Softwareänderungen haben sich die Entwickler darauf verständigt, zukünftig nur noch englische Attributbezeichnungen zu verwenden. Die Daten aller neu registrierten Benutzer werden nach dem neuen, englischsprachigen Schema abgespeichert. Listing 2.2 zeigt solch ein Dokument, welches nach der 2. Schemaversion erstellt wurde. Listing 2.2: Struktur eines Benutzer-Dokuments, Schemaversion 2 1 { " name " : " Hans Hansen " , " password " : " t0ps3cr3t " , " creditcards " : [ { " type " : " visa " , " cardnumber " : "987654321098" } ], " version " : 2 2 3 4 5 6 7 8 } Benutzer (Version 3). Wenn sich ein Benutzer im Blogsystem anmeldet, soll er mit seinem Vornamen begrüßt werden. Das Aufteilen des Namens anhand des Leerzeichens erwies sich in Sonderfällen als unzuverlässig. Es wird eine erneute Schemaänderung vorgenommen. Neue Benutzer müssen bei ihrer Registrierung ihren Vor- und Nachnamen separat angeben. Existierende Benutzer werden bei ihrer nächsten Anmeldung nach ihrem Vor- und Nachnamen gefragt und die entsprechenden Daten in die neue Schemaversion überführt. Listing 2.3 stellt die Benutzerdaten dieses neuen Schemas dar. Listing 2.3: Struktur eines Benutzer-Dokuments, Schemaversion 3 1 { " firstname " : " Paul " , " lastname " : " Paulsen " , " password " : " sup3rs3cr3t " , " creditcards " : [ { " type " : " visa " , " cardnumber " : "314159265359" } ], " version " : 3 2 3 4 5 6 7 8 9 } 13 2. Begleitendes Beispiel Produktbeschreibung. Produkte in Shopsystemen werden zumeist durch eine Vielzahl an Attributen beschrieben. In diesem Beispiel besteht diese Produktbeschreibung lediglich aus einem Namen, einer Artikelnummer und einem Attribut ”isPublic”. Letzteres wird verwendet, um einzelne Produkte, die noch nicht im Shop angeboten werden sollen, zu markieren. Die API-Komponente für externe Anwendungen soll nur den Zugriff auf freigeschaltete Artikel ermöglichen. In Listing 2.4 ist die Struktur solch einer Produktbeschreibung dargestellt. In dieser Beispielanwendung gibt es keine weiteren Schemaversionen für Produkte. Das ”version”-Attribut wird dennoch mit aufgeführt, da eine Versionierung in einer ”echten” Anwendung vorstellbar ist. Listing 2.4: Struktur eines Produkt-Dokuments 1 { " name " : " Rote Hose " , " articlenumber " : "23987" , " isPublic " : true , " version " : 1 2 3 4 5 6 } 14 3. State of the Art 3.1. Sichten in SQL Im Bereich der SQL-Datenbanken sind Konzepte der Sichtenverwaltung bereits seit langem bekannt. So sieht der SQL-Standard bereits seit den frühen Versionen Befehle zur Verwaltung von Sichten vor, wie z.B. CREATE VIEW für das Erstellen einer Sicht. Zur Veranschaulichung wird in Listing 3.1 eine Sicht namens ”app users v2” erstellt. In dieser wird aus den Attributen ”firstname” und ”lastname”, welche sich im Basis-Schema ”app users” befinden, per Zeichenketten-Verknüpfung (Concat) das Attribut ”name” erzeugt. Listing 3.1: Sichtdefinition in SQL 1 2 3 CREATE VIEW app_users_v2 AS SELECT id , CONCAT ( firstname , " " , lastname ) AS name , password FROM app_users Eine Anwendung, welche die Attribute ”id”, ”name” und ”password” erwartet, kann auf die Sicht ”app users v2” zugreifen, während andere Anwendungen die Basistabelle ”app users” verwenden können. Dort sind die Attribute ”firstname” und ”lastname” weiterhin separat enthalten. SQL-Anfragen auf Sichten sind syntaktisch equivalent zu Anfragen auf Tabellen. 3.1.1. Aufgaben von Sichten in SQL Sichtdefinitionen werden mit Hilfe von SELECT-Anfragen beschrieben. Somit können diese auch die selben Anfragekomponenten beinhalten, welche nachfolgend beschrieben werden. Projektion. Die Projektion wird verwendet, um Attribute zu filtern oder neue Attribute durch arithmetische, Logik- oder Zeichenketten-Operationen zu erzeugen. In SQL wird die Projektion durch Auflistung der jeweiligen Attribute hinter dem Schlüsselwort SELECT beschrieben. Selektion. Datensätze, also Zeilen, können über die Selektion gefiltert werden. Hierzu stehen diverse Vergleichsoperatoren zur Verfügung. Trifft ein Vergleich zu, so resultiert dies in dem booleschen Wert ”wahr” bzw. ”true”; ansonsten ”falsch” bzw. ”false”. Mehrere solcher Vergleiche können durch logische Operatoren wie AND, OR oder NOT zu komplexeren Ausdrücken verkettet werden. Wird solch ein Ausdruck auf einen Datensatz angewandt und ergibt einen wahren Rückgabewert, wird dieser Datensatz in die Ergebnismenge aufgenommen. Die Selektion wird in SQL über die WHERE-Klausel beschrieben. 15 3. State of the Art Verbund. Die Datensätze mehrerer, möglicherweise normalisierter, Tabellen können mit Hilfe von Verbundoperationen verknüpft werden. Somit stehen in der Ergebnismenge alle Attribute und die dazugehörigen Werte der einzelnen Tabellen zur Verfügung. Wie genau die Verknüpfung der Tabellen durchgeführt wird, hängt von der Art der jeweiligen Verknüpfung (LEFT JOIN, RIGHT JOIN, INNER JOIN, OUTER JOIN) ab. Gruppierung und Aggregation. Eine weitere Komponente, die in einem SQL-Query angewandt werden kann, stellt die Aggregation dar. Hierzu lassen sich Aggregatfunktionen wie Summenbildung (SUM()), Durchschnittsbildung (AVG()) oder die Anzahlbestimmung (COUNT()) auf die komplette Ergebnismenge oder vorher gebildete Gruppen von Datensätzen anwenden. Gruppen können mit Hilfe der GROUP BY-Klausel gebildet werden. 3.1.2. Arten von Sichten Bezüglich des Zeitpunkts ihrer Ausführung werden Sichten in virtuelle und materialisierte Sichten unterteilt. Ihre unterschiedliche Funktionsweisen werden nachfolgend beschrieben. Virtuelle Sichten Virtuelle Sichten werden zum Zeitpunkt der Anfrage aufgelöst. Hierzu wird eine sogenannte Sichtexpansion auf die Anfrage des Clients durchgeführt. Das Ziel dieser Sichtexpansion ist es, die einzelnen Anfragekomponenten wie Projektion und Selektion aus der Sichtdefinition ebenfalls auf die Client-Anfrage anzuwenden. [17] Die Sichtexpansion kann entweder im Parsebaum, also auf Syntax-Ebene, vorgenommen werden oder aber im Anfragebaum, welcher die Anfrageoperatoren beinhaltet. Außerdem muss sie rekursiv angewandt werden, um Szenarien abzudecken, in denen sich Sichten auf andere Sichten beziehen. Das Beispiel in Listing 3.2 zeigt eine Anfrage auf die vorher definierte Sicht ”app users v2” aus Listing 3.1. Die Anfrage enthält eine Projektion auf die Attribute ”id”, ”name” und ”password”, sowie eine Selektion auf das ”status”-Attribut. Nach der Anwendung der Sichtexpansion auf Syntax-Ebene enthält die Anfrage zwei Projektionen, nämlich die der Sicht und die der Anfrage selbst. Listing 3.2: Ergebnis einer Sichtexpansion in SQL 1 2 3 4 -- Anfrage auf Sicht " app_users_v2 " SELECT id , name , password FROM app_users_v2 WHERE status = " active " 5 6 7 8 9 10 11 12 -- Anfrage nach Anwendung der Sichtexpansion SELECT id , name , password FROM ( SELECT id , CONCAT ( firstname , " " , lastname ) AS name , password FROM app_users ) WHERE status = " active " 16 3.2. NoSQL-Datenbanken Materialisierte Sichten Materialisierte Sichten zeichen sich dadurch aus, dass sie - im Gegensatz zu virtuellen Sichten - tatsächlich zusätzlichen Speicher für die Datensätze belegen. Sie können als die abgespeicherte Ergebnismenge einer ausgeführten Anfrage zu einem bestimmten Zeitpunkt angesehen werden. Anfragen auf materialisierte Sichten sind in der Regel schneller als die auf virtuelle Sichten, da Schritte wie die Sichtexpansion hier entfallen und die Ergebnismenge bereits zu einem vorherigen Zeitpunkt berechnet wurde. Um Datensätze einer materialisierten Sicht mit denen der Basistabellen synchron zu halten, muss die Ergebnismenge stets aktualisiert werden. Hierfür gibt es verschiedene Vorgehensweisen. Eine Neuberechnung der Ergebnismenge kann in definierten Zeitintervallen vorgenommen werden. Dies hat den Vorteil, dass die Berechnungen asynchron zur Sichtanfrage geschehen, wodurch die Anfrage selbst schnell ausgeführt werden kann. Allerdings bedeutet dies ebenfalls, dass Datensätze, die nach der letzten Neuberechnung verändert wurden, nicht synchron sind mit der abgespeicherten Ergebnismenge. Ein anderer Ansatz hierfür ist die Neuberechnung der Ergebnismenge nach jeder SchreibOperation auf die Basistabellen. Der Vorteil liegt hierbei in der Aktualität der abgespeicherten Daten der materialisierten Sicht. Dies bringt aber auch den Nachteil mit sich, dass die UpdateOperation selbst mehr Zeit in Anspruch nehmen. 3.2. NoSQL-Datenbanken Es gibt verschiedene Arten von NoSQL-Datenbanken, die sich unter anderem im Datenmodell, der Anfrage oder ihrer Architektur unterscheiden [13]. Im Rahmen dieser Arbeit richtet sich der Fokus auf die Dokument- und spaltenorientierten Datenbanken. Diese beiden Datenbanktypen können als schemafrei oder schemaflexibel angesehen werden. 3.2.1. Dokumentdatenbanken In Dokumentdatenbanken wie MongoDB [9] oder CouchDB [1] werden Daten als SchlüsselWert-Paare gespeichert. Bei dem Schlüssel handelt es sich um eine Zeichenkette, anhand derer ein Dokument eindeutig identifiziert werden kann. Der Wert stellt hierbei das Dokument dar. Diese Dokumente besitzen eine hierarchische Struktur, wobei jedes Attribut als Wert entweder eine Liste (Array), ein Objekt (Teildokument) oder einen skalareren Wert (Zeichenketten, Zahlen, boolesche Werte, Null, etc.) beinhalten kann. Meist wird JSON [20] als Austauschformat für die Dokumente verwendet. Die Anfragesprachen unterscheiden sich bei den verschiedenen Datenbanken. So erfordert bspw. CouchDB das Anlegen von Sichten in Form von Map-Reduce-Funktionen, welche in JavaScript geschrieben sind. MongoDB hingegen stellt verschiedene APIs für die Abfrage von (Teil-)Dokumenten bereit. Diese werden im Abschnitt 3.4.1 näher erläutert. Unabhängig von der Anfragesprache unterstützen Dokumentdatenbanken oft mehrere Indizes, welche die Selektion durch Attributvergleiche beschleunigen können. Dokumente werden in Sammlungen (Collections) organisiert, welche mit Tabellen aus SQLDatenbanken vergleichbar sind. Man nutzt diese um gleichartige Dokumente in je einer Collection zusammen zu verwalten. Dennoch unterliegen diese Dokumente keinem übergeordneten 17 3. State of the Art Schema. Jedes Dokument kann somit eine beliebige Hierarchie aufweisen. Dies bezeichnet man als schemafrei. 3.2.2. Spaltenorientierte Datenbanken Spaltenorientierte Datenbanken, auch ”Extensible Record Stores” oder ”Wide Column Stores” genannt, ähneln bzgl. des Datenmodells zunächst den relationalen Datenbanken. Die Datensätze innerhalb einer Tabelle unterliegen einem Schema. Darüber hinaus können jedoch auch weitere Spalten pro Datensatz hinzugefügt werden. Dies ist beispielsweise bei HBase [2] oder BigTable [4] der Fall. Einen etwas anderen Ansatz hingegen verfolgt CassandraDB [19]. Hier muss zunächst ein Schema definiert werden und sämtliche Datensätze beinhalten nur eine Untermenge von den - im Schema festgelegten - Attributen. Das Hinzufügen von Attributen, die nicht Teil des Schemas sind, ist nicht möglich. Attribute, die in einem Datensatz nicht benötigt werden, können jedoch leer gelassen werden. Diese Ansätze der spaltenorientierten Datenbanken zeigen, dass NoSQL-Datenbanken nicht zwangsläufig schemafrei sind, aber zumindest schemaflexibel. 3.3. NoSQL-Anfragesprachen Es gibt eine Vielzahl von Anfragemodellen und -sprachen für NoSQL-Systeme. Datenbanken wie CassandraDB [19] oder Hypertable [7] bringen jeweils eine eigene Sprache mit. Mit der Cassandra Query Language (CQL) und Hypertable Query Language (HQL) wurden zwei Anfrage- und Updatesprachen entwickelt, die zunächst viel Ähnlichkeit mit der standardisierten Structured Query Language (SQL) aufweisen, sich aber im Detail durch systemspezifische Einschränkungen oder Erweiterungen unterscheiden. 3.3.1. Map-Reduce Weit verbreitet ist das Anfrage-Modell ”Map-Reduce”. Das Konzept wurde von Google, Inc. entwickelt und beschreibt ein Modell, mit dem große Datenmengen parallel in Computerclustern verarbeitet werden können [5]. Die Verarbeitung erfolgt dabei in zwei wesentlichen Schritten, dem Mapping und dem Reducing. Schritt 1: Mapping. Beim Mapping werden Datensätze auf Schlüssel-Wert-Paare abgebildet. Diese Abbildung muss nicht zwangsläufig eine 1:1-Abbildung sein. Ein Datensatz kann beispielsweise auch in mehreren solcher Tupel resultieren. In vielen Datenbanksystemen wird dieses Mapping durch die Angabe einer JavaScript-Funktion beschrieben, welche auf den jeweiligen Datenbankservern für jeden Datensatz ausgeführt wird. In diesem JavaScriptKontext steht die entsprechende emit()-Funktion zur Verfügung, mit der solch ein SchlüsselWert-Paar erzeugt werden kann. Schritt 2: Reducing. Das sogenannte Reducing stellt den zweiten wichtigen Schritt dar. Die reduce-Funktion wird für jeden Schlüssel, der beim Mapping-Schritt erzeugt wurde, mit allen 18 3.4. MongoDB dazugehörigen Werten ausgeführt. Dieser Schritt wird ebenfalls oft durch eine JavaScriptFunktion dargestellt. Diese nimmt als Argumente den jeweiligen Schlüssel und eine Liste (Array) von dazugehörigen Werten entgegen. Der Rückgabewert dieser Funktion stellt dann einen Datensatz des Endergebnisses dar. Höhere Programmiersprachen. Ein Vorteil dieses Ansatzes ist, dass innerhalb der mapbzw. reduce-Funktionen ein Großteil des Umfangs der verwendeten Programmiersprache zur Verfügung steht. Somit sind auch komplexere Berechnungen umsetzbar, die mit Hilfe der anderen Anfrage-APIs nur aufwendig oder gar unmöglich zu formulieren sind. Dazu zählt unter anderem der Zugang zu mathematischen Funktionen und Konstanten, sowie Arrayund Zeichenketten-Operationen. Darüber hinaus können meist auch Kontrollstrukturen wie Schleifen (for, while, do ... while) oder die bedingte Ausführung von Code-Blöcken (if ... else, switch-case) genutzt werden. 3.4. MongoDB Bei MongoDB [9] handelt es sich um eine schemafreie Dokumentdatenbank. Sie ist auf die Verwaltung von JSON-basierten Dokumenten ausgelegt und unterstützt Adhoc-Anfragen, welche im Abschnitt 3.4.1 detaillierter beschrieben sind. 3.4.1. Dokumentverwaltung Das JSON-Format spielt in MongoDB eine große Rolle. Sowohl die Dokumente selbst als auch die Anfragen auf die Datenbank erfolgen in Form von JSON-codierten Objekten. Die Dokumente werden in Sammlungen (Collections) verwaltet, welche vergleichbar sind mit Tabellen aus dem Bereich der SQL-Datenbanken. Für Anfragen auf die darin gespeicherten Dokumente stehen in MongoDB verschiedene APIs zur Verfügung, welche nachfolgend vorgestellt werden. Query-API Die einfachste API, die MongoDB anbietet, um (Teil-)Dokumente aus einer Collection auszulesen, ist die sogenannte Query-API. In den offiziellen Client-Implementierungen steht diese API über die find-Methode zur Verfügung. Sie gestattet lediglich die Angabe einer Projektion sowie einer Selektion, welche jeweils über ein JSON-Objekt beschrieben werden können. Selektion. Für Selektionen stehen u. a. Vergleichsoperatoren wie $eq (Test auf Gleichheit), $exists (Test auf Existenz eines Attributs) oder $gt (größer als), sowie die Verknüpfung dieser Vergleiche durch $and (Verundung) bzw. $or (Veroderung) zur Verfügung. Diese können genutzt werden, um Dokumente zu filtern. Projektion. Die Projektion erfolgt auf Attribute, welche entweder einen skalaren Wert oder wiederum ein Teildokument beinhalten können. Pfade zu einem Attribut lassen sich durch Auflistung aller einzelnen Attributsnamen dorthin beschreiben. So führt bspw. der Pfad a.b.c 19 3. State of the Art in dem Dokument in Listing 3.3 zu dem skalaren Wert ”foo”, während der Pfad a.b das Teildokument unterhalb des b-Attributes enthält. Listing 3.3: Projektion in MongoDB 1 2 3 4 5 6 7 8 9 10 // Dokument { "a": { "b": { " c " : " foo " , " d " : " bar " } }, " e " : " baz " } 11 12 13 14 15 16 17 18 19 // Projektion auf " a . b . c " ergibt : { "a": { "b": { " c " : " foo " } } } 20 21 22 23 24 25 26 27 28 29 // Projektion auf " a . b " ergibt : { "a": { "b": { " c " : " foo " , " d " : " bar " } } } 30 31 32 33 34 // Projektion auf " e " ergibt : { " e " : " baz " } Weiterhin ist die Projektion lediglich auf die Anwesenheit oder Abwesenheit von Attributen beschränkt. Umfangreichere Operationen wie die Umbenennung von Attributen oder Anwendung von Aggregatsfunktionen sind hier nicht möglich. Das Beispiel 3.4 zeigt eine Anfrage mit Hilfe der Query-API auf die Collection ”app users”. In dieser Anfrage werden Dokumente selektiert, deren Attribut ”firstname” dem Wert ”Max” oder deren Attribut ”status” dem Wert ”active” entspricht. 20 3.4. MongoDB In der Projektionsbeschreibung werden alle Attribute mit dem Wert 1 markiert, welche im Ergebnis zurückgegeben werden sollen. Attribute, die den Wert 0 zugewiesen bekommen, erscheinen nicht im Ergebnis. Eine Ausnahme stellt die generierte Objekt-ID unter dem Attribut id dar, welche automatisch im Ergebnis erscheint, wenn sie nicht explizit als abwesend markiert wird. Listing 3.4: find-Anfrage in MongoDB 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 db . collection ( " app_users " ) . find ( // Selektion { " $or " : [ { " firstname " : " Max " }, { " status " : " active " } ] }, // Projektion { " firstname " : 1 , " lastname " : 1 , " password " : 1 , " _id " : 0 } ); Aggregation-Pipeline Seit der Version 2.2 ist in MongoDB die sogenannte ”Aggregation-Pipeline” [10] verfügbar. Eine Anfrage über diese API gestattet die Angabe einer oder mehrerer Pipelines, welche nacheinander ausgeführt werden. Innerhalb dieser Pipelines stehen diverse Anfrageoperatoren zur Verfügung, die nachfolgend beschrieben werden. Projektion. Über die Angabe des Schlüssels $project kann die Projektion beschrieben werden. Über die Angabe bzgl. der An- bzw. Abwesenheit einzelner Attribute hinaus, ist die Umbenennung von vorhandenen Attributen und die Erzeugung neuer Attribute möglich. Hierzu stehen u. a. arithmetische Operationen und Zeichenketten-Funktionen wie $concat (Verkettung von Zeichenketten) zur Verfügung. Selektion. Die Beschreibung einer Selektion kann über den Schlüssel $match zur Pipeline hinzugefügt werden. Diese entspricht dem Aufbau der Selektion der Query-API aus Abschnitt 3.4.1. Gruppierung und Aggregate. Analog zur GROUP BY-Klausel aus SQL sind auch in MongoDB Gruppierungen von Dokumenten über die Angabe des Schlüssels $group möglich. Diese 21 3. State of the Art Gruppen können anschließend für die Bildung von Aggregaten verwendet werden, wie z.B. die Summe ($sum) oder der Durchschnitt ($avg). Weitere Operatoren. Weiterhin können Dokumente anhand eines oder mehrerer Attribute sortiert werden ($sort). Die Angabe des Schlüssels $out und dem Namen einer Collection als Wert sorgt dafür, dass das Ergebnis der aggregate-Anfrage nicht zurückgegeben wird, sondern stattdessen in der angegebenen Collection abgespeichert wird. Im Gegensatz zu SQL-Datenbanken stehen in MongoDB keine Verbund-Operationen (Joins) zur Verfügung. Eine Verknüpfung von Dokumenten aus mehreren Collections muss durch die Anwendung selbst erfolgen. Als Alternative gibt es Objektreferenzen. Ein Attribut kann somit eine Referenz auf ein anderes Objekt (Dokument) aus der selben oder einer anderen Collection enthalten. Da es sich jedoch nur um eine Referenz handelt, muss der Client das referenzierte Dokument in einer separaten Anfrage holen. Listing 3.5 zeigt, wie eine Anfrage mit Hilfe der Aggregation-Pipeline erfolgt. Die Selektion entspricht der aus dem Beispiel der Query-API (3.4). In der Projektion wird hier definiert, dass das Attribut ”name” aus den Werten der Attribute ”firstname” und ”lastname”, getrennt durch ein Leerzeichen, zusammengesetzt wird. Das Feld ”status” wird übernommen und die automatisch generierte Dokument-ID wird als abwesend markiert. Listing 3.5: aggregate-Anfrage in MongoDB 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 db . collection ( " app_users " ) . aggregate ([ { // Projektion " $project " : { " name " : { " $concat " : [ " $firstname " , " " , " $lastname " ] }, " password " : 1 , " _id " : 0 } }, { // Selektion " $match " : { " $or " : [ { " firstname " : " Max " }, { " status " : " active " } ] } } ]) ; 22 3.4. MongoDB Map-Reduce MongoDB stellt ebenfalls eine Map-Reduce-API bereit. Das Map-Reduce-Modell wurde in Abschnitt 3.3.1 bereits vorgestellt. Die map- und reduce-Funktionen werden in MongoDB jeweils in Form einer JavaScript-Funktion definiert. Weitere Anfrageoperatoren. Neben der Selektion, die sich durch die JavaScript-Funktionen formulieren lässt, können Dokumente beim Verwenden der Map-Reduce-API bereits im Vorfeld durch Angabe eines zusätzlichen Query-Ausdrucks gefiltert. Dies erlaubt eine effizientere Selektion durch die Nutzung von Indizes auf entsprechenden Attributen. Nach der Ausführung der Map-Reduce-Schritte kann das Ergebnis auch sortiert oder in der Anzahl limitiert werden. 23 4. Sichtdefinitionen für NoSQL-Systeme Wie in den Abschnitten 3.2 und 3.3 erwähnt, gibt es für die unterschiedlichen NoSQLSysteme auch verschiedene Anfrage- und Updatesprachen. In diesem Kapitel soll der Aufbau einer Sichtdefinition erarbeitet werden. 4.1. Anforderungen an Sichtdefinitionen In Datenbanksystemen, die bereits eine Sichtenverwaltung besitzen, werden Sichten in der Regel in Form einer Anfrage beschrieben. In SQL-Datenbanken handelt es sich dabei um SELECT-Anfragen. Sichten in CouchDB werden durch Map-Reduce-Funktionen abgebildet [8]. Dieser Ansatz soll in diesem Kapitel ebenfalls verfolgt werden. Dies bedeutet jedoch auch, dass die Menge der möglichen Sichtoperationen vom Beschreibungsumfang der jeweiligen Anfragesprachen beschränkt wird. Eine Sicht kann somit nur die Transformationen vornehmen, die sich durch eine Anfrage formulieren lassen. Um eine systemunabhängige Sichtdefinition zu beschreiben, ist es notwendig, einige Anforderungen an die NoSQL-Datenbanken zu stellen. Nachfolgend werden die angestrebten Transformationsschritte anhand von Beispielen beschrieben und die dazu notwendigen Voraussetzungen erläutert. 4.1.1. Auswahl von Attributen Zu den Aufgaben einer Projektion gehört die Filterung von Attributen. Es soll möglich sein, einzelne Attribute aufzuzählen, die in den Ergebnisdokumenten (bzw. -datensätzen) vorhanden sind. Zum Vergleich: In SQL-verwandten Sprachen wie CQL oder HQL erfolgt die Projektion in der Regel über die SELECT-Klausel, wie in Listing 4.1 dargestellt. Listing 4.1: Einfache Projektion von Attributen in SQL-verwandten Sprachen 1 SELECT name , password Die meisten Dokumentdatenbanken unterstützen eine solche Projektion, z.B. über entsprechende Map-Reduce-Funktionen oder die zusätzliche Angabe einer Projektion wie bei MongoDB (siehe Abschnitt 3.4.1). Beispiel: Filtern von Attributen. Die Webanwendung aus dem Beispielszenario (Kapitel 2) verwaltet Kreditkarteninformationen in den Benutzer-Objekten. Aus Sicherheitsgründen soll das Blogsystem nur Zugriff auf die Daten haben, die für die Anmeldung notwendig sind. Eine Projektionssicht soll dafür sorgen, dass nur die Attribute ”name” und ”password” in den Anfrageergebnissen enthalten sind. Listing 4.2 zeigt das erwartete Ergebnis. 25 4. Sichtdefinitionen für NoSQL-Systeme Listing 4.2: Auswahl von Attributen 1 { " name " : " Peter Petersen " , " password " : " s3cr3t " 2 3 4 } 4.1.2. Generierung von Attributen Eine weitere Aufgabe der Projektion ist es, neue Attribute zu generieren. Nachfolgend werden drei Anwendungsbeispiele beschrieben, in denen es nützlich sein kann, Attribute zu erzeugen. Attribut mit konstantem Wert Ein Attribut kann einen konstanten Wert (Zahl, Boolean oder Zeichenkette) zugewiesen bekommen. Wenn das Attribut bereits vorher existiert, wird dessen Wert in diesem Schritt überschrieben. Existiert das Attribut noch nicht, so wird es erzeugt. Dies ist nützlich, wenn die Anwendung das Vorhandensein eines Attributs erfordert, welches im Ausgangsschema nicht vorhanden ist. Eine vergleichbare SQL-Anfrage könnte so aussehen, wie in Listing 4.3 dargestellt. In dieser Projektion werden zwei Attribute erzeugt und ihnen eine Zeichenkette und eine Fließkommazahl als Wert zugewiesen. Listing 4.3: Erzeugung eines Attribut mit konstantem Wert in SQL-verwandten Sprachen 1 SELECT " 1970 -01 -01 " AS birthday , 3.141 AS pi Beispiel: Attribut mit konstantem Wert. In der Beispielanwendung wird eine externe Bibliothek verwendet. Diese kann oder darf nicht verändert werden. Sie ist für ein Schema konzipiert worden, welche das Geburtsdatum eines Benutzers im Attribut ”birthday” beinhaltet. Das Geburtsdatum wird sonst an keiner Stelle der Anwendung benötigt und somit auch nicht gespeichert. Das Attribut für den Geburtstag kann mit einem Dummy-Wert erzeugt werden. Das zu erwartende Ergebnis ist in Listing 4.4 dargestellt. Listing 4.4: Auswahl von Attributen 1 { " name " : " Hans Hansen " , " password " : " t0ps3cr3t " , " creditcards " : [ { " type " : " visa " , " cardnumber " : "987654321098" } ], " version " : 2 , " birthday " : "1970 -01 -01" 2 3 4 5 6 7 8 9 } 26 4.1. Anforderungen an Sichtdefinitionen Attributkopie Ein Attribut kann mit dem Wert eines existierenden Attributs erzeugt werden. Dabei wird der Wert des vorhandenen Attributs zum neu erzeugten kopiert. Beispiel: Umbenennung von Attributen. Die Umbenennung von Attributen stellt eine wichtige Aufgabe von Sichten da. Erst dadurch ist es möglich, gleiche Basisdaten auf verschiedene Weisen zu repräsentieren. Eine Umbenennung erfolgt durch das Kopieren eines Attributwerts zu einem neuen Attribut und das Weglassen des originalen Attributnamens aus der Projektion. Ein Anwendungsfall hierfür ist die Transformation eines Benutzer-Dokuments der ersten Schemaversion aus dem Beispielszenario in die zweite. In Listing 4.5 ist zunächst das Ergebnis der Attributkopie zu sehen. Es werden die deutschen Attributbezeichnungen in die englischen übersetzt und die jeweiligen Werte kopiert. Listing 4.5: Erzeugung von Attributen durch Kopie 1 { " name " : " Peter Petersen " , " passwort " : " s3cr3t " , " kreditkarten " : [ { " typ " : " visa " , " kartennummer " : "123456789012" } ], " version " : 1 , " password " : " s3cr3t " , " creditcards " : [ { " type " : " visa " , " cardnumber " : "123456789012" } ] 2 3 4 5 6 7 8 9 10 11 12 } Wendet man nun noch die bereits vorgestellten Transformationsschritte aus Abschnitt 4.1.1 (Auswahl von Attributen) und 4.1.2 (Attribut mit konstantem Wert) an, so lässt sich ein Ergebnis, wie in Listing 4.6 dargestellt, konstruieren. Die Projektion beinhaltet nur noch die Attribute ”name”, ”password”, ”creditcards” und ”version”. Die Attribute ”passwords” und ”creditcards” (zusammen mit den darunterliegenden Objekten) sind durch die Attributkopie entstanden. Die Versionsnummer wurde mit Hilfe eines konstanten Wertes überschrieben. Auf diese Weise kann ein Dokument so transformiert werden, dass es der zweiten Schemaversion entspricht. Listing 4.6: Anwendung von Attributkopie, Projektion und konstantem Attributwert 1 2 3 4 5 6 { " name " : " Peter Petersen " , " password " : " s3cr3t " , " creditcards " : [ { " type " : " visa " , " cardnumber " : "123456789012" } ], 27 4. Sichtdefinitionen für NoSQL-Systeme " version " : 2 7 8 } Attribute durch String-Verknüpfung Eine weitere Art, Attributwerte zu erzeugen, ist die Verkettung von Zeichenketten. Dabei können die Zeichenketten konstant sein oder aus anderen Attributwerten bestehen. Hierdurch lassen sich normalisierte Attribute wieder vereinen. Das nachfolgende Beispiel veranschaulicht diesen Schritt anhand der Beispielanwendung. Beispiel: Denormalisierung von Attributen Ein Modul der Webanwendung ist bereits veraltet und soll demnächst durch ein neues Modul abgelöst werden. Dieses ältere Modul benötigt das Benutzer-Schema in der zweiten Version, bei dem der vollständige Benutzername in einem einzigen Attribut steckt. Bis zum Austausch dieses Moduls muss es weiterhin möglich sein, den Vor- und Nachnamen aus der dritten Schemaversion zu diesem einen Attribut zu vereinen. Listing 4.7 zeigt das zu erwartende Ergebnis aus dieser Art der Attribut-Erzeugung. Listing 4.7: Erzeugung eines Attributs durch String-Verknüpfung 1 { " name " : " Paul Paulsen " , " password " : " sup3rs3cr3t " , " creditcards " : [ { " type " : " visa " , " cardnumber " : "314159265359" } ], " version " : 3 2 3 4 5 6 7 8 } Bedingte Werterzeugung Eine Problemstellung, die insbesondere in NoSQL-Systemen über Sichttransformationen gelöst werden soll, ist die Überbrückung von Strukturunterschieden zwischen mehreren Dokumenten einer Art. Um gleichstrukturierte Dokumente als Anfrage-Ergebnis zu erhalten, müssen Attributwerte abhängig vom aktuellen Schema erzeugt werden. Die Erfüllung oder Nichterfüllung einer Bedingung soll darüber entscheiden, welcher von zwei möglichen Werten als Attributwert verwendet wird. In der Beispielanwendung wurde bereits eine Versionierung über das zusätzliche ”version”Attribut in allen Dokumenten vorgenommen. Anhand dessen Wertes soll es möglich sein, weitere Attribute zu definieren. Beispiel: Bedingte Werterzeugung Die Entwickler der vorgestellten Webarchitektur müssen mehrere einzelne Anwendungen pflegen. Eine Schematransformation innerhalb der Anwendungen ist zwar umsetzbar, bringt jedoch einige Nachteile mit sich. So müssen die Transformationsregeln in jeder Anwendung separat implementiert werden. Mit jeder neu eingeführten Schemaversion wächst die Komplexität innerhalb der Anwendungen. Es wird gewünscht, dass 28 4.1. Anforderungen an Sichtdefinitionen diese Schematransformationen außerhalb der Anwendung erfolgen. So kann für jede genutzte Schemaversion eine eigene Sicht erstellt werden. Möchte man Dokumente aus der dritten Schemaversion in die zweite transformieren, so kann dies mit dem Pseudocode aus Listing 4.8 umgesetzt werden. Hierzu wird ein Konstrukt der Form IF Bedingung THEN Ausdruck1 ELSE Ausdruck2 ENDIF benötigt. Die Bedingung muss, wie die Selektion, durch Wertevergleiche formuliert werden können. Die Ausdrücke hingegen können konstante oder konkatenierte Werte darstellen oder andere existierende Attribute. Auch eine Verschachtelung von IF ... ELSE-Blöcken ist denkbar. Hierdurch können auch Vergleiche mit weiteren Versionen erfolgen. So kann eine Vielzahl an Schemaversionen auf ein Zielschema transformiert werden. Listing 4.8: Pseudocode für die Transformation eines Schemas 1 2 3 4 5 6 7 8 9 PROJECT ( IF version = 3 THEN CONCAT ( firstname , " " , lastname ) ELSE name ENDIF ) AS name , password , creditcards , 2 AS version Die Bedingungsprüfung innerhalb der Projektion ist notwendig für diese Art der Transformation. Nur wenige NoSQL-Anfragesprachen bieten die Möglichkeit, diese zu formulieren. Systeme mit einer Map-Reduce-Schnittstelle unterstützen in der Regel solche Ausdrücke. Die Aggregation-API von MongoDB sieht ebenfalls solch eine Projektion über eine entsprechende JSON-Struktur vor. 4.1.3. Selektion von Dokumenten Es gibt Anwendungsfälle, in denen bereits auf Datenbank-Ebene Datensätze selektiert werden sollen. Über eine Sichttransformation muss es möglich sein, Datensätze und Dokumente anhand von Attributwerten zu filtern. Es genügt zunächst die Voraussetzung, dass Attributwerte auf Gleichheit geprüft werden können. Mehrere solcher Attributvergleiche sollen auf ein Dokument angewendet werden können. Nur wenn alle Vergleiche zutreffen, soll der Datensatz in der Ergebnismenge aufgenommen werden (UND-Verknüpfung). Beispiel: Selektion von Dokumenten. Die API der Webanwendung soll nur den Zugriff auf freigeschaltete Produktinformationen gestatten. Damit versehentliche Implementierungsfehler innerhalb der API-Komponente nicht dazu führen, dass noch nicht-öffentliche Produkte frühzeitig in Erfahrung gebracht werden können, soll eine Selektion bereits vorher stattfinden. Die API-Komponente bekommt nur die Berechtigung, lesend auf diese Sicht zuzugreifen. Ein Direktzugriff auf die Basisdaten ist ausgeschlossen. Die meisten NoSQL-Systeme bieten die Möglichkeit, solch eine Selektion über eine Anfragesprache zu beschreiben. 29 4. Sichtdefinitionen für NoSQL-Systeme 4.2. Entwicklung einer Sichtdefinition Als Ausgangsbasis für eine Sichtdefinition soll eine Anfragebeschreibung dienen. Diese sollte möglichst systemunabhängig sein, was sich jedoch als schwierig erweist, da es keine einheitliche API für Anfragen in NoSQL-Datenbanken gibt. Deshalb wird nun versucht, eine existierende Anfrage-API dahingehend zu erweitern, dass Sichtdefinitionen mit ihr verwaltet und Sichten angefragt werden können. Die Query- und Aggregation-API von MongoDB scheinen, hierfür geeignet zu sein. Wie im Abschnitt 3.4.1 bereits dargelegt wurde, unterstützt die Query-API von MongoDB keine Möglichkeiten der Umbenennung von Attributen. Diese sind jedoch essenziell für die Transformation eines Ausgangsschemas in ein Zielschema. Erst die, in Abschnitt 3.4.1 beschriebene, Aggregation-Pipeline stellt diese erweiterte Projektion zur Verfügung und dient somit als Grundlage für die Sichttransformationen. Anfragen werden in MongoDB in Form von JSON-Objekten beschrieben. Analog dazu kann eine Sichtendefinition ebenfalls durch solch ein Anfrageobjekt beschrieben werden. Listing 4.9 stellt dar, wie eine API zum Anlegen einer Sichtdefinition aussehen und verwendet werden könnte. Es wird der Name der Sicht und die Zielcollection benötigt, sowie die Anfrage selbst. Außerdem muss ein Flag angegeben werden, welches signalisiert, ob es sich dabei um eine virtuelle oder eine materialisierte Sicht handelt. Listing 4.9: API zum Anlegen einer Sichtdefinition 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 db . createView ( // Name der Sicht " app_users_v2 " , // Zielcollection " app_users " , // Definition ( Aggregate - Anfrage ) [ { " $project " : { " name " : { " $ifNull " : [ " $name " , { " $concat " : [ " $firstname " , " " , " $lastname " ]} ] }, " password " : 1 , " _id " : 0 } } ], // materialisiert true ); 30 4.2. Entwicklung einer Sichtdefinition 4.2.1. Updates auf Sichten Update-Operationen auf Sichten werden im Rahmen dieser Arbeit nicht behandelt. Hierfür wäre es notwendig, 1:1-Abbildungen des Originalschemas zum transformierten Schema zu schaffen. Diese müssten bei einem Update zurückgeführt werden, was - abhängig von den Transformationsschritten - nur schwer oder gar nicht umsetzbar ist. Hierzu zählen Attribute, die durch Aggregationen oder die Verkettung von Zeichenketten entstanden sind. 31 5. Konzeption einer Sichtenverwaltung in MongoDB In diesem Kapitel wird nachfolgend das Konzept für die Implementierung des Prototyps beschrieben. Dabei wird zunächst auf die Verwaltung der Sichtdefinition eingegangen. Anschließend werden verschiedene Architekturen vorgestellt und bewertet. 5.1. Sichtenverwaltung Das Format für die Sichtendefinition wurde in Abschnitt 4.2 auf JSON-codierte Anfrageobjekte festgelegt. Dies entspricht somit genau dem gleichen Format, in dem auch Dokumente gespeichert werden. Sichtendokumente können somit in einer extra Collection verwaltet werden. 5.2. Sicht-Anfrage Die Anfrage auf eine Sicht erfolgt durch das Absetzen einer Query-Operation mit dem Namen der Sicht und der Zeichenkette ”VIEW:” als Präfix, z.B. ”datenbankname.VIEW:app user v2”. Bei der Betrachtung der verschiedenen Anfrage-APIs (Abschnitt 3.4.1) fällt auf, dass nicht alle APIs für die prototypische Implementierung geeignet sind. Es wird mindestens eine API mit dem Umfang der Aggregation-Pipeline benötigt, um solch eine Sichtenverwaltung umzusetzen. Die Query-API, welche nur Selektion und Projektion unterstützt, stellt somit eine Untermenge der möglichen Anfragekomponenten der Aggregation-Pipeline dar. Dies bedeutet, dass eine Anfrage, die über die Query-API abegsetzt wird, innerhalb des Prototypen in eine Anfrage über die Aggregation-Pipeline übersetzt und ausgeführt werden kann. Die dritte vorgestellte Anfrage-API, Map-Reduce, wird aufgrund ihrer hohen Komplexität im Rahmen dieser Arbeit nicht weiter betrachtet. 5.3. Materialisierte Sichten Materialisierte Sichten können durch die Ausführung einer Sicht-Anfrage und anschließendem Abspeichern des Ergebnisses erstellt werden. Die Aggregation-Pipeline stellt ein optionales Attribut $out zur Verfügung, worunter sich eine Zielcollection angeben lässt. Dort wird dann das Ergebnis der Anfrage gespeichert. Um dieses gespeicherte Ergebnis mit den Basisdaten zu synchronisieren, muss diese Anfrage wiederholend ausgeführt werden. Dies kann entweder in einem festgelegten Intervall geschehen oder nach jedem Update auf die Basiscollection. Ersteres kann bspw. durch ein dauerhaft laufendes Programm (Daemon) geschehen. Dieses setzt mit Hilfe eines Intervall-Timers die selbe Anfrage in vorgegebenen Zeitabständen regelmäßig ab. 33 5. Konzeption einer Sichtenverwaltung in MongoDB Updates auf die Basisdaten lassen sich hingegen schwieriger erfassen. Hierzu müsste - bei jeglicher Änderung eines Dokuments - der Client selber ein dauerhaft laufendes Programm über die erfolgte Schreiboperation informieren. Alternativ kann ein Proxyserver zwischen Client und MongoDB-Server geschaltet werden, welcher sämtliche Befehle decodiert und im Falle von Update-Operationen die jeweiligen Sichtanfragen erneut ausführt. 5.4. Virtuelle Sichten Virtuelle Sichten werden erst bei der eigentlichen Anfrage durch den Client ausgeführt. Die hierfür erforderliche Sichtexpansion kann im Falle von MongoDB bereits mit Hilfe der Aggregation-Pipeline umgesetzt werden, da es auch gestattet ist, mehrere Pipelines anzugeben, die hintereinander ausgeführt werden. Somit lässt sich die Sichtexpansion durch eine Nacheinanderausführung der Sichtdefinition sowie der eigentlichen Anfrage realisieren. Listing 5.1 zeigt, wie solch eine Anfrage manuell ausgeführt werden kann. Der Prototyp, welcher in dieser Arbeit entwickelt wird, muss nun lediglich Anfragen, welche über die Query-API bzw. die Aggregation-Pipeline gestellt werden, in solch eine erweiterte Anfrage überführen und an den MongoDB-Server senden. Listing 5.1: Aggregation mit zwei Pipelines 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 db . benutzer . aggregate ([ // 1. Pipeline ( Sichtdefinition ) { " $project " : { " name " : { " $ifNull " : [ " $name " , { " $concat " : [ " $firstname " , " " , " $lastname " ]} ] }, " password " : 1 , " _id " : 0 } }, // 2. Pipeline ( Anfrage ) { " $project " : { " nombre " : " $name " } } ]) ; 5.5. Architektur Es werden zwei Architekturen in Betracht gezogen, mit denen sich MongoDB um eine Sichtenverwaltung erweitern lässt. In Abschnitt 5.6 wird eine Client-Middleware-Server-Architektur vorgestellt, bei der die Sichtenlogik von der Middleware umgesetzt wird. Als Alternative dazu wird im Abschnitt 5.7 eine Client-Server-Architektur erläutert, bei welcher der Client selbst 34 5.6. Architektur A: Client-Middleware-Server die Sichtenverwaltung umsetzt. Im Anschluss daran werden beide Architekturen im Abschnitt 5.8 direkt miteinander verglichen und die jeweiligen Vor- sowie Nachteile aufgezeigt. 5.6. Architektur A: Client-Middleware-Server Für die Client-Middleware-Server-Architekture (Abb. 5.6) muss der MongoDB-Client um die Methoden zum Erstellen und Löschen von Sichtendefinitionen erweitert werden. Zudem wird eine Middleware, ein spezieller Proxyserver, benötigt. Diese Middleware wird zwischen den Client und Datenbankserver geschaltet, wo sie für die Logik rund um die Verwaltung von Sichtendefinitionen, sowie Anfragen auf Sichten verwantwortlich ist. Abbildung 5.1.: Architektur A: Sichtenverwaltung durch Middleware Die Sichtdefinitionen selbst liegen in Form von Dokumenten vor. Sie können über die nativen Operationen OP INSERT, OP UPDATE und OP DELETE - wie jedes andere Dokument auch - verwaltet werden. Eine Erweiterung des Protokolls ist somit nicht notwendig. 5.6.1. Aufgaben des Clients Die API des Clients wird um drei Methoden erweitert: createView(name, query, isMaterialized) zum Erstellen, updateView(name, query, isMaterialized) zum Updaten einer vorhandenen Sicht und deleteView(name) zum Löschen einer Sichtdefinition. Dies sind lediglich Wrapper-Methoden für insert(document, options), update(selector, document[, options]) und remove([selector][, options]) des Standard-Clients. 5.6.2. Aufgaben der Middleware Die Middleware muss in der Lage sein, eingehende Befehle des Clients zu decodieren und je nach Art des Befehls, wie nachfolgend beschrieben, darauf reagieren: Handelt es sich um Befehle der Sichtenverwaltung (Einfügen, Verändern, Löschen), so werden diese Operationen zwar ausgeführt, aber zusätzlich auch die dazugehörigen materialisierten Sichten, wie in Abschnitt 5.3 beschrieben, erstellt bzw. gelöscht. Erfolgt hingegen eine Anfrage, so wird rekursiv eine Sichtexpansion auf selbige ausgeführt. Im Falle einer materialisierten Sicht bedeutet es, dass lediglich der Name der Zielcollection umgeschrieben wird. Sollte aber eine virtuelle Sicht angefragt worden sein, wird, wie in Abschnitt 5.4 beschrieben, die Aggregation-Pipeline ausgeführt. 35 5. Konzeption einer Sichtenverwaltung in MongoDB Sämtliche anderen Operationen werden transparent an den Server weitergeleitet. Die jeweiligen Antworten des Servers werden zurück an den Client gesendet. 5.7. Architektur B: Client-Server Die Client-Server-Architektur (Abb. 5.7) stellt die übliche MongoDB-Architektur dar. Um hier eine Sichtenverwaltung vorzunehmen, ohne den Server selbst zu verändern, muss der Client die komplette Logik ausführen. Auch hier kann, wie schon bei der Client-MiddlewareArchitektur erwähnt, die Verwaltung der Sichtendefinitionen mit Hilfe nativer Operationen umgesetzt werden. Dazu ist es nicht notwendig, das bestehende Übertragungsprotokoll anzupassen. Die Sichtexpansion findet ebenfalls im Client statt. Abbildung 5.2.: Architektur B: Sichtenverwaltung im Client 5.8. Vergleich der Architekturen Stellt man beide Architekturen gegenüber, so ergibt sich daraus folgende Zusammenfassung (Tabelle 5.1): Tabelle 5.1.: Vergleich der Architekturen zur Sichtenverwaltung Eigenschaft Client-Middleware-Server Client-Server ProjektOrganisation 2 Projekte (je eins für Client und Middleware), die aufeinander abgestimmt und getestet werden müssen nur 1 Projekt Wiederverwendbarkeit Middleware kann ”wiederverwendet” werden; Clients in anderen Sprachen müssen nur WrapperMethoden implementieren nicht gegeben; Sichtenlogik muss für jeden Client neu implementiert werden muss Middleware kann Autorisierung übernehmen, somit kann Zugriff auf einzelne Sichten eingeschränkt werden Nicht möglich, da Client allein für Sichtenverwaltung zuständig Autorisierung Sichten 36 für 5.8. Vergleich der Architekturen Aus Sicht der Entwicklung, Testbarkeit und Projektorganisation liegen die Vorteile bei der Client-Server-Architektur. So muss z.B. die Projektstruktur nur einmal erstellt werden. Der Aufwand für die Versionskontrolle fällt geringer aus und es können Integrationtests für die komplette Komponente entwickelt werden. Auf der anderen Seite überwiegen die Vorteile der Erweiterbarkeit der Client-MiddlewareServer-Architektur. Weitere Clients lassen sich mit weniger Aufwand entwickeln. Der Client bleibt ”leichtgewichtig” und vermittelt lediglich Befehle, anstatt selbst komplexere Operationen auszuführen. Die Middleware kann durch zusätzliche Features wie z.B. einer Autorisierung für Verwaltung und Nutzung von Sichten erweitert werden. Nachfolgend wird deshalb nur noch die Variante mit der Middleware betrachtet. 37 6. Implementierung eines Prototypen für die Sichtenverwaltung in MongoDB Quelloffene Client-Implementierungen für MongoDB sind in vielen höheren Programmiersprachen verfügbar. Für einen Proxyserver scheint eine ereignisorientierte Programmiersprache geeignet zu sein. Deshalb wird für die Umsetzung der Middleware Node.js verwendet. Der Quellcode für den MongoDB Native Driver ist auf GitHub [6] veröffentlicht. Die Server-Implementierung von MongoDB wird im Laufe dieser Arbeit nicht verändert, was den Vorteil hat, dass die Serverversion jederzeit ausgetauscht werden kann. Somit ist die Middleware zu allen MongoDB-Servern ab Version 2.2 kompatibel. Es wird die aktuelle Serverversion 2.6.1 verwendet. Die Implementierung für den Client und die Middleware erfolgt separat. Deshalb wird nachfolgend zunächst die Umsetzung für die Erweiterung des offiziellen Node.js-Clients für Mongo DB beschrieben. Anschließend werden im zweiten Abschnitt 6.2 die Arbeitsschritte zur Erstellung der Middleware geschildert. 6.1. Client Die Entwicklung des Clients erfolgt in mehreren Schritten. Im ersten Schritt (Abschnitt 6.1.1) wird ein neues Projekt aufgesetzt und benötigte npm-Module werden installiert. Danach erfolgt die eigentliche Erweiterung des Clients. 6.1.1. Projekt aufsetzen Um den offiziellen MongoDB-Client für Node.js mittels npm zu installieren, wird eine Paketdefinition (package.json) im Hauptverzeichnis des Projekts angelegt. Dabei handelt es sich um eine JSON-formatierte Konfiguration für npm-Module, welche unter anderem Informationen wie den Paketnamen, Versionsnummer, Autor, Lizenz oder Modulabhängigkeiten beinhalten kann. Eine vollständige Dokumentation zum Aufbau dieser Definitionen ist auf der offiziellen Webseite des npm-Projekts verfügbar [15]. Der MongoDB-Client liegt zum Zeitpunkt der Implementierung in der npm-Registry unter dem Paketnamen ”mongodb” in der Version 1.4.5 vor. Deshalb wird die benötigte Versionsnummer auf 1.4 gesetzt, was bedeutet, dass die jeweils höchste verfügbare Version, die mit 1.4 beginnt, installiert wird. Außerdem wird für die Ausführung von Unit-Tests das Modul ”nodeunit” installiert, welches in der Version 0.9.0 verfügbar ist. Da die Unit-Tests nur während der Entwicklung, aber nicht für die eigentliche Verwendung des Clients benötigt werden, kann das entsprechende Modul im Abschnitt devDependencies der JSON-Datei angegeben werden. Die daraus resultierende Konfiguration ist in Listing 6.1 dargestellt und kann nun verwendet werden, um die Module zu installieren. Hierzu genügt es, den Befehl npm install auf der 39 6. Implementierung eines Prototypen für die Sichtenverwaltung in MongoDB Kommandozeile im Hauptverzeichnis des Projekts auszuführen. Listing 6.1: Client-Modulkonfiguration (package.json) 1 { " name " : " mongodb - views - client " , " version " : "0.1.0 - dev " , " description " : " Mongo DB client with support for views " , " main " : " index . js " , " scripts " : { " test " : " nodeunit tests / db . js " }, " keywords " : [ " mongodb " , " views " , " client " ] , " author " : { " name " : " Norman Soetbeer " , " email " : " norman . soetbeer@uni - rostock . de " }, " license " : " MIT " , " dependencies " : { " mongodb " : "~1.4" }, " devDependencies " : { " nodeunit " : "~0.9" } 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 } 6.1.2. Erweiterung durch Prototyping Im Gegensatz zu vielen anderen objektorientierten Programmiersprachen erfolgt die Vererbung in Javascript nicht durch klassische Vererbung, sondern durch prototypische. Dabei werden Attribute und Methoden erst zur Laufzeit durch das Klonen anderer Objekte vererbt. Weiterhin können diese Eigenschaften auch zur Laufzeit überschrieben oder sogar wieder vom Objekt entfernt werden. Jedes Objekt kann wieder als Prototyp zur Erstellung neuer Objekte dienen. Um den MongoDB-Client wie in Abschnitt 5.6.1 beschrieben um die notwendigen Methoden zur Sichtverwaltung zu erweitern, wird die vorhandene Datenbankkomponente erweitert. Diese stellt Methoden zum Verbindungsaufbau, zur Authentifizierung, zur Verwaltung von Collections und Indizes, sowie zur Benutzerverwaltung bereit. Nun werden die Implementierungen an das Db-Objekt gebunden, wie im Listing 6.2 ansatzweise abgebildet. Listing 6.2: Prototyping am Db-Objekt 1 2 // Db - Objekt des nativen MongoDB - Clients holen var Db = require ( ’ mongodb ’) . Db ; 3 4 5 // Funktionen zur Sic htenv erwalt ung hinzuf ü gen Db . prototype . createView = function ( name , collection , query , isMaterialized , callback ) { 40 6.2. Middleware // ... 6 7 }; 8 9 10 11 Db . prototype . deleteView = function ( name , callback ) { // ... }; 12 13 14 // Modifiziertes Db - Objekt zur Verf ü gung stellen module . exports = Db ; Damit dieses manipulierte Db-Objekt auch verwendet wird, kann im Modul-Einstiegspunkt eine Instanz des nativen Clients geladen und darin das Db-Objekt überschrieben werden, was in Listing 6.3 dargestellt ist. Listing 6.3: Modul-Einstiegspunkt des erweiterten Clients 1 2 // Instanz des nativen MongoDB - Clients holen var MongoClient = require ( ’ mongodb ’) . MongoClient ; 3 4 5 // Db - Objekt mit eigener , erweiterter Implementierung ü berschreiben MongoClient . Db = require ( ’ ./ db ’) ; 6 7 module . exports = MongoClient ; Für die Sichtanfragen wird die Collection-Komponente des MongoDB-Clients als Basis verwendet. Eine Anfrage auf eine Sicht entspricht im Grunde einer Anfrage auf eine Collection mit dem Präfix ”VIEW:”. Die neu eingeführte View-Komponente setzt also lediglich dieses Präfix und delegiert alle weiteren Aufrufe weiter an die Collection-Komponente. 6.2. Middleware Die Middleware besteht aus einem dauerhaft laufenden Prozess, welcher in der Lage sein soll, Verbindungen von MongoDB-Clients anzunehmen und eine Verbindung zu einem MongoDBServer aufzubauen. Zur Kommunikation zwischen Client, Middleware und Server kommt stets das MongoDB Wire Protocol [11] zum Einsatz. Nachfolgend werden die einzelnen Schritte der Implementierung beschrieben, um diese Middleware zu entwickeln. 6.2.1. Proxyserver Zunächst wird ein Proxyserver (Abb. 6.1) implementiert, welcher eingehende TCP-Verbindungen von MongoDB-Clients entgegennimmt. TCP-Verbindungen werden in NodeJS, ähnlich wie in anderen Sprachen auch, durch sogenannte Sockets abstrahiert. Es können Daten aus SocketVerbindungen gelesen oder in sie hinein geschrieben werden. In einem ersten Schritt wird für jede eingehende Client-Verbindung eine neue Verbindung zum Zielserver aufgebaut und sämtliche Netzwerkdaten an die jeweils andere SocketVerbindung weitergesendet. Dies wird mittels der pipe-Methode umgesetzt. Diese verwendet die Ausgabe der ersten Verbindung als Eingabe für die zweite. Die - zwischen Client und 41 6. Implementierung eines Prototypen für die Sichtenverwaltung in MongoDB Abbildung 6.1.: Datenfluss innerhalb der Middleware (Proxyserver) Server ausgetauschten - Daten können zum Debuggen auf der Konsole ausgegeben werden, sind jedoch weiterhin über das MongoDB Wire Protocol [11] codiert. 6.2.2. Wire Protocol Um diese Datenpakete zu decodieren, findet das MongoDB Wire Protocol [11] Verwendung. In diesem ist beschrieben, wie die Nachrichtensequenzen (de-)serialisiert werden. Dabei ist der Header immer gleich aufgebaut, während sich die darauffolgenden, codierten Nutzdaten von Operation zu Operation unterscheiden. Listing 6.4: MongoDB Wire Protocol: Nachrichten-Header 1 2 3 4 5 6 7 struct MsgHeader { int32 messageLength ; // Nachrichtenl ä nge , inkl . Header int32 requestID ; // Nachrichten - ID int32 responseTo ; // ID der urspr ü nglichen Nachricht // ( nur f ü r Antworten , sonst 0) int32 opCode ; // Operations - Code ( Query , Insert , etc .) } Das Protokoll sieht die Verwendung von sieben verschiedenen Operationen für den Client vor: • OP QUERY: allgemeine Query-Anfragen, kann aber auch für Insert, Update, Delete verwendet werden • OP UPDATE: Änderung vorhandener Dokumente • OP INSERT: Einfügen von Dokumenten • OP DELETE: Löschen von Dokumenten • OP GET MORE: Abfrage von Dokumenten anhand eines Cursors aus vorheriger QueryAnfrage • OP KILL CURSORS: Löschen eines Cursors aus vorheriger Anfrage • OP MSG: Diagnose-Nachrichten (veraltet) 42 6.2. Middleware Für den Server hingegen ist lediglich eine Operation vorgesehen, OP REPLY, welche für sämtliche Antworten verwendet wird. Eine vollständige Liste der Datenstrukturen für diese Nachrichten befindet sich im Anhang A. Für die Decodierung einer Nachricht des Clients werden zunächst jeweils 4 Bytes für die Nachrichtenlänge, Nachrichten-ID, ursprüngliche Nachrichten-ID und den Operations-Code ausgelesen und anschließend in den entsprechenden Ganzzahlwert (32-Bit Integer) umgewandelt. Danach wird der Operationscode ausgewertet, der z.B. bei einem Query den Wert 2004 enthält. Es folgt das Auslesen der restlichen Datenstruktur. Zur Veranschaulichung wird diese für ein Query in Listing 6.5 dargestellt. Listing 6.5: MongoDB Wire Protocol: Query 1 2 3 4 5 6 7 8 9 struct OP_QUERY { MsgHeader header ; // int32 flags ; // cstring fu llC ol le ct io nN am e ; // int32 numberToSkip ; // int32 numberToReturn ; // document query ; // [ document r e t u r n F i e l d s S e l e c t o r ; ] } Nachrichten - Header Query - Optionen Name der Collection Offset Max . Anzahl an Dokumenten Anfrage - Objekt , meist Selektion // Objekt f ü r Projektion Zeichenketten, wie der Name der angefragten Collection, liegen als UTF-8 codierte, NullByte-terminierte Zeichenketten vor. Bei den Dokumenten handelt es sich um BSON-codierte Objekte. BSON (Binary JSON) ist eine Codierung, die auf JSON (JavaScript Object Notation) basiert, aber insbesondere größere, verschachtelte Dokumente Speicher-effizienter serialisieren kann. 6.2.3. Interceptor Innerhalb der Middleware gibt es zwei sogenannte Interceptoren. Deren Aufgabe ist es, die einund ausgehenden Nachrichten zu decodieren und im Sinne der Sichtenlogik zu verändern. Sie stehen miteinander in Verbindung. Dadurch ist es möglich, Nachrichten mit einem Interceptor zu generieren und die dazugehörige Antwort mit dem anderen abzufangen. Abbildung 6.2 verdeutlicht den Nachrichtenfluss. Abbildung 6.2.: Datenfluss innerhalb der Middleware mit Interceptoren Implementiert wurden diese Interceptoren als sogenannte Stream-Transformer. Ein StreamTransformer kann in NodeJS zwischen je zwei Streams, und somit auch zwischen Socket- 43 6. Implementierung eines Prototypen für die Sichtenverwaltung in MongoDB Verbindungen, geschaltet werden und Datenpakete zurückhalten, verändern oder sogar neue Datenpakete erzeugen. Damit ist es also möglich, Client-Anfragen umzuschreiben oder über die bereits hergestellte Verbindung neue Anfragen an die Datenbank zu schicken. Die Antworten auf selbst erzeugte Anfragen können auf dem Rückweg wiederum abgefangen werden. Der Client erfährt somit nichts über die zusätzlichen Operationen, welche über die Middleware erzeugt wurden. 6.2.4. Abfangen von Sichten-Operationen Mit Hilfe des Interceptors aus Abschnitt 6.2.3 sollen Anfragen des Clients nun so transfomiert werden, dass materialisierte Sichten gemäß des Konzepts aus Abschnitt 5.3 automatisch angelegt bzw. gelöscht werden. Anfragen auf virtuelle Sichten sollen, wie in Abschnitt 5.4 vorgestellt, umgeschrieben werden. Nachfolgend werden die hierzu notwendigen Operationen genauer beschrieben. Anlegen einer Sicht Wird eine neue Sichtdefinition durch den Client angelegt, so entspricht dies einer EinfügeOperation in die Collection ”view.definitions”. Das einzufügende Dokument stellt hierbei die Sichtdefinition selbst dar (Abb. 6.6). Die Sichtdefinition beinhaltet den Namen der Sicht, die Basiscollection, die Anfrage selbst und die Information, ob die Sicht materialisiert werden soll. Listing 6.6: Dokument einer Sichtdefinition 1 { " name " : " benutzer_v1 " , " collection " : " benutzer " , " query " : { " $project " : { " name " : { " $ifNull " : [ " $name " , { " $concat " : [ " $vorname " , " " , " $nachname "] } ] }, " geburtstag " : 1 , " version " : { " $literal " : 1 } } }, " isMaterialized " : true 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 } Das Anlegen der Sicht erfolgt bereits durch das Einfügen dieses Dokuments. Plausibilitätsprüfen bzgl. der Struktur der Anfrage, sowie Prüfungen, ob die Basiscollection existiert, 44 6.2. Middleware werden im Rahmen dieses Prototyps nicht vorgenommen. Jedoch muss noch zwischen einer virtuellen und einer materialisierten Sicht unterschieden werden. Für eine materialisierte Sicht muss die dazugehörige Collection, in welcher das Anfrage-Ergebnis enthalten ist, angelegt und befüllt werden. Der Stream-Transformer lässt hierzu die ursprüngliche Einfüge-Operation unverändert passieren und generiert anschließend eine Query-Operation (Abb. 6.3). Genauer handelt es sich hierbei um eine aggregate-Anfrage, welche die Anfrage aus der Sichtdefinition enthält. Das Ergebnis wird in der Collection für die materialisierte Sicht gespeichert. In dem Beispiel 6.6 heißt die dazugehörige Collection ”views.materialized.benutzer v1”. Zukünftige Anfragen auf die View werden auf diese Collection umgeschrieben. Abbildung 6.3.: Datenfluss beim Anlegen einer materialisierten Sicht Außerdem muss der zweite Stream-Transformer, welcher die Server-Nachrichten auswertet, die Antwort auf die aggregate-Anfrage abfangen. Um diese Antwort zu identifizieren wird die RequestID aus der Anfrage verwendet, welche später mit der responseTo-ID der Antwort übereinstimmt. Bei einer virtuellen Sicht muss keine Anfrage generiert werden. Hier wird lediglich die Einfüge-Operation unverändert an den Server gesendet. Löschen einer Sicht Analog zum Anlegen einer Sicht, entspricht das Löschen einer Sicht dem Löschen des Dokuments aus der Collection ”views.definitions”, welches die jeweilige Sicht beschreibt. Wird eine Sichtdefinition gelöscht, so muss auch ggf. die dazugehörige materialisierte Sicht gelöscht werden. Hierzu wird zunächst die Nachricht zum Löschen des Dokuments zurückgehalten. Anschließend wird ein Query an die Datenbank gesendet, um die Sichtdefinition anzufragen. Aus dieser Sichtdefinition kann nun der Name der Basiscollection entnommen werden und im Falle, dass es sich um eine materialisierte Sicht handelt, eine weitere Nachricht erzeugt werden, um die Collection der materialisierten Sicht zu löschen. Außerdem wird die zurückgehaltene Lösch-Operation des Clients an den Server gesendet. Updaten einer Sicht Soll eine vorhandene Sichtdefinition verändert werden, so geschieht dies durch eine UpdateOperation auf dem dazugehörigen Dokument in der Collection der Sichtdefinitionen. 45 6. Implementierung eines Prototypen für die Sichtenverwaltung in MongoDB Eine Update-Operation kann hierbei wie eine Lösch- und eine anschließende EinfügeOperation betrachtet werden. Dies bedeutet, dass der Client-Befehl zum Updaten des Dokuments zunächst zurückgehalten wird. Anschließend wird die alte Sichtdefinition geladen und ggf. die bisherige materialisierte Sicht bzw. die dafür zuständige Collection gelöscht. Handelt es sich bei der neuen Sichtdefinition um eine materialisierte Sicht, so wird wieder eine Aggregate-Anfrage an die Datenbank gesendet, um die entsprechende Collection mit dem Anfrageergebnis zu erzeugen. Sind all diese Schritte durchlaufen, wird der zurückgehaltene Client-Befehl weiter an die Datenbank geschickt. Sicht-Anfragen Es werden Anfragen der Query-API, sowie der Aggregation-API abgefangen, sofern die ZielCollection mit dem Präfix ”VIEW:” beginnt. Zunächst wird dabei die Anfrage des Clients zurückgehalten und anschließend die Definition der entsprechenden Sicht mit Hilfe einer separaten Query-Operation geladen. Abhängig von der Art der Sicht werden weitere Operationen erzeugt. Anfrage auf eine materialisierte Sicht Abbildung 6.4 zeigt das Sequenzdiagramm für eine Anfrage auf eine materialisierte Sicht. Dabei wird der Name der Collection aus der ClientAnfrage (”benutzer”) gegen die Collection der materialisierten Sicht (”views.materialized.benutzer v1”) ausgetauscht. Nun kann diese Anfrage an den Server weitergeschickt werden. Die ServerAntwort ist mit der Client-Anfrage kompatibel und muss daher nicht abgefangen werden. Abbildung 6.4.: Sequenzdiagramm für die Anfrage auf eine materialisierte Sicht 46 6.2. Middleware Anfrage auf eine virtuelle Sicht Das Sequenzdiagramm für die Anfrage auf eine virtuelle Sicht (Abb. 6.5) hingegen zeigt auf, dass die Anfrage an den Server durch eine AggregateOperation ersetzt wird. Diese wird mit der Request-ID aus der ursprünglichen Nachricht versehen. Dadurch ist es dem Client möglich, die Serverantwort der vorher gestellten Anfrage zuzuordnen. Es werden zwei Pipelines in die Anfrage eingebunden: zuerst die der Sichtdefinition und anschließend die der ursprünglichen Anfrage. Das Ergebnis der AggregationOperation ist kompatibel mit dem einer Query-Operation. Somit kann die Antwort an den Client durchgereicht werden. Abbildung 6.5.: Sequenzdiagramm für die Anfrage auf eine virtuelle Sicht 47 7. Evaluierung Nachfolgend werden die Konzepte und Implementierungen dieser Arbeit ausgewertet. Hierzu werden Übersichten zu angestrebten und erreichten Zielen erstellt. Es wird mit Hilfe der Anforderungen des Beispiels aus Kapitel 2 bewertet, inwiefern diese Ansätze zur Lösung der Ausgangsprobleme beitragen. 7.1. Bewertung der Sichtdefinitionen Die Sichtdefinitionen erlauben die Formulierung der in Abschnitt 4.1 beschriebenen Szenarien und erfüllen somit die Anforderungen. Diese wurden mit Hilfe von Integrationstests in der Middleware getestet. 7.2. Performance-Analyse Die Middleware stellt eine zusätzliche Kommunikationsschicht zwischen Client und Server dar. Jede zwischengeschaltete Schicht bringt gewisse Performanceeinbußen mit sich, wie z.B. erhöhte Netzwerklatenzen. In diesem Abschnitt wird versucht, solche Performanceverluste aufzuführen und die Auswirkungen einzuordnen. 7.2.1. Datenbankoperationen Durch die Speicherung der Sichtdefinitionen außerhalb der Middleware sind zusätzliche Datenbankanfragen notwendig. Die nachfolgende Übersicht (Tab. 7.1) zeigt auf, wie viele solcher Anfragen für die jeweiligen Sichtoperationen notwendig sind. Das Anlegen einer neuen Sicht ist meist nur dann erforderlich, wenn neue Schemaversionen eingeführt oder neue Anwendungen in ein bestehendes System integriert werden. Zusammen mit dem Löschen einer Sichtdefinition stellt dies eine eher selten ausgeführte Aufgabe dar. Zusätzliche Anfragen durch die Middleware sind im Zusammenhang mit diesen Operationen zu vernachlässigen. Anders sieht es bei den Anfragen auf virtuelle und materialisierte Sichten aus. Hier sind spürbare Performanceeinbußen zu erwarten. Zum einen wird der Datenbankserver durch die vielen zusätzlichen Anfragen einer größeren Belastung ausgesetzt. Zum anderen erhöhen sich die Antwortzeiten deutlich, da sämtliche Anfragen durch die Middleware nacheinander ausgeführt werden. 7.2.2. Ansätze zur Performanceverbesserung Da die Sichtdefinitionen nur selten verändert werden, kann eine Verbesserung der Performance durch den Einsatz eines Caches erzielt werden. Somit müssten die Definitionen nur 49 7. Evaluierung Tabelle 7.1.: Bewertung zusätzlicher Anfragen für Sichtoperationen Sichtoperation zusätzliche Anfragen Bewertung Erstellen einer virtuellen Sicht keine weitere Operation Es wird nur die Einfügeoperation ausgeführt. keine negative Beeinflussung Erstellen einer materialisierten Sicht +1 Operation Es wird eine aggregate-Operation ausgeführt, um die dazugehörige Collection zu befüllen. vernachlässigbar, Operation da seltene Löschen einer virtuellen Sicht +1 Operation Die Sichtdefinition muss geladen werden, um festzustellen, welche Sichtart vorliegt. vernachlässigbar, Operation da seltene Löschen einer materialisierten Sicht +2 Operationen Die Sichtdefinition muss geladen werden. Die Collection der materialisierten Sicht wird gelöscht. vernachlässigbar, Operation da seltene Anfrage auf virtuelle Sicht +n Operationen Mindestens einmal muss die Sichtdefinition geladen werden. Ist die Sicht über eine weitere Sicht definiert, müssen weitere Definitionen rekursiv geladen werden, bis Zielcollection eine materialisierte Sicht oder ”echte” Collection ist. häufige Operation, kann sich negativ auf die Performance auswirken, insbesondere bei Sichten auf Sichten Anfrage auf materialisierte Sicht +1 Operation Die Sichtdefinition muss geladen werden. Danach wird die Client-Anfrage nur umgeschrieben. häufige Operation, kann sich negativ auf die Performance auswirken jeweils einmal vom Datenbankserver angefordert werden. Mit dem Einfügen oder Löschen einer Sichtbeschreibung muss der dazugehörige Cache-Eintrag invalidiert werden. Dieser Ansatz ist geeignet, wenn nur eine Instanz der Middleware betrieben wird, weil nur so gewährleistet werden kann, dass die Cache-Einträge mit den tatsächlichen Definitionen übereinstimmen. Ein weiterer Lösungsansatz besteht darin, die Sichtdefinitionen durch die Middleware persistent abspeichern zu lassen. Mehrere Middleware-Instanzen könnten mit Hilfe eines internen Protokolls Änderungen auf Sichtdefinitionen untereinander bekannt machen. Invalide CacheEinträge, wie sie im vorherigen Ansatz vorkommen können, werden somit vermieden. Durch den Einsatz mehrerer Middleware-Prozesse kann eine horizontale Skalierbarkeit erreicht werden. 50 7.3. Unit- und Integrationstests 7.3. Unit- und Integrationstests Um die Middleware auf korrekte Implementierung zu prüfen, wurden verschiedene Tests entwickelt. 7.3.1. Unit-Tests Um die funktionale Korrektheit der Middleware zu gewährleisten, wurden einige Komponenten mit Hilfe von Unit-Tests getestet. Hierzu zählt die BSON-Erweiterung zum Codieren und Decodieren von Objekten innerhalb von Nachrichten. Mit Hilfe dieser Tests war es möglich, Fehler schnell zu erkennen und zu beheben. 7.3.2. Integrationstests Integrationstests gestatten, das Zusammenspiel mehrerer Komponenten automatisiert zu testen. Diese wurden ebenfalls verwendet, um das Erstellen und Löschen von Sichtdefinitionen und Anfragen auf Sichten zu prüfen. In Listing 7.1 ist solch ein Testfall dargestellt. Dort wird eine virtuelle Sicht mit einer Selektion angelegt und später eine Anfrage an diese Sicht gestellt. Nicht hier dargestellt sind die Fixtures, also Dummy-Dokumente, die vorher in die Basiscollection eingefügt wurden. Nach der Anfrage auf die Sicht werden die erwarteten Ergebnisse mit den tatsächlichen verglichen. Listing 7.1: Auszug eines Integrationstests 1 describe ( ’ Virtual views ’ , function () { 2 3 4 5 6 7 8 9 10 11 it ( ’ Create a virtual view ’ , function ( done ) { var definition = { name : ’ base_v1_virtual ’ , collection : ’ base ’ , query : { $project : { version : 2} }, isMaterialized : false }; 12 definitions . insert ( definition , function ( err , result ) { assert . ifError ( err ) ; assert . ok ( result , ’ The definition should be inserted ’) ; done () ; }) ; 13 14 15 16 17 18 }) ; 19 20 21 22 23 it ( ’ Query a virtual view using find () w / o query ’ , function ( done ) { db . collection ( ’ VIEW : base_v1_virtual ’) . find () . toArray ( function ( err , documents ) { assert . ifError ( err ) ; assert . equal (2 , documents . length ) ; 51 7. Evaluierung done () ; 24 }) ; 25 }) ; 26 27 28 }) ; 7.4. Homogenisierung von Dokumenten Die Hauptziele dieser Arbeit lagen darin, heterogene Daten zu homogenisieren und verschiedene Repräsentationen von gleichen Basisdaten zu mit Hilfe von Sichten zu erzeugen. Die Konzeptionen haben mit Beispielen verdeutlicht, dass die Übertragung von Sichtkonzepten aus relationalen Datenbanken auf NoSQL-Datenbanken möglich ist. Mit Integrationstests für den Prototypen wurden einige dieser Szenarien nachgebildet und somit auch die praktische Machbarkeit nachgewiesen. Der praktische Nutzen dieser Möglichkeiten wurde durch ein begleitendes Beispiel einer Webarchitektur dargelegt. 52 8. Zusammenfassung In dieser Diplomarbeit wurde untersucht, inwiefern die Übertragung von Sichtkonzepten aus dem Bereich der SQL-Datenbanken auf NoSQL-Datenbanken möglich ist. Die Hauptziele waren hierbei, Heterogenitäten zu beseitigen und verschiedene Repräsentationen von Basisdaten zu ermöglichen. Nach einer Erfassung der Anforderungen konnte im Kapitel ?? der Aufbau von Sichtdefinitionen beschrieben werden. Mit Hilfe einer Middleware ist es möglich, diese Sichtdefinitionen auf einem MongoDB-Server zu verwalten. Basierend auf diesen Sichtbeschreibungen können Anfrageergebnisse so transformiert werden, dass sie homogene Strukturen aufweisen. Die dazu notwendigen Anfragetransformationen wurden ebenfalls über diese Middleware realisiert. Mit dem Erreichen der Hauptziele wurden die Sichtkonzepte erfolgreich auf eine NoSQLDatenbank übertragen. 8.1. Aussichten und Erweiterbarkeit Der, in dieser Arbeit entwickelte, Middleware-Prototyp wurde mit der Intention erstellt, Sichtenkonzepte in NoSQL-Datenbanken umzusetzen. Für die Verwendung in einer Produktivumgebung müssten Performanceoptimierungen vorgenommen werden, welche möglicherweise Änderungen an der Architektur erfordern. Ansätze hierzu wurden im Abschnitt 7.2.1 vorgestellt. Da die Sichttransformation nicht im Client stattfindet (vgl. Abschnitt 5.7), ist eine Autorisierung für einzelne Sichten denkbar, ähnlich wie es sie auch für SQL-Datenbanken gibt. Somit ließe sich der Zugriff auf eine Teilmenge der Attribute oder Datensätze für Benutzer und Anwendungen einschränken. Die Client-Middleware-Server-Architektur wurde u.a. für die Implementierung ausgewählt, weil es dadurch einfacher ist, Clients in anderen Programmiersprachen zu entwickeln. Dies bietet die Möglichkeit, die Sichtenverwaltung auch in Webanwendungen zu nutzen, die bspw. in PHP oder Java entwickelt werden. 53 A. MongoDB Wire Protocol 1 2 Datentypen ---------- 3 4 5 6 7 int32 : vorzeichenlose 32 - bit Ganzzahl , " Little Endian " codiert int64 : vorzeichenlose 64 - bit Ganzzahl , " Little Endian " codiert cstring : UTF -8 codierte Zeichenkette , Null - Byte terminiert document : BSON codiertes Objekt 8 9 10 11 Nachrichtenkopf --------------- 12 13 14 15 16 17 18 19 struct MsgHeader { int32 messageLength ; // Nachrichtenlänge , inkl . Header int32 requestID ; // Nachrichten - ID int32 responseTo ; // ID der ursprünglichen Nachricht // ( nur für Antworten , sonst 0) int32 opCode ; // Operations - Code ( Query , Insert , etc .) } 20 21 22 23 Anfrage - Operation ----------------- 24 25 26 27 28 29 30 31 32 33 struct OP_QUERY { MsgHeader header ; int32 flags ; cstring fu llC ol le ct io nN am e ; int32 numberToSkip ; int32 numberToReturn ; document query ; [ document r e t u r n F i e l d s S e l e c t o r ; ] } // // // // // // // Nachrichten - Header Query - Optionen Name der Collection Offset Max . Anzahl an Dokumenten Anfrage - Objekt , meist Selektion Objekt für Projektion 34 35 36 37 Update - Operation ---------------- 38 39 40 41 42 43 struct OP_UPDATE { MsgHeader header ; int32 ZERO ; cstring fu llC ol le ct io nN am e ; int32 flags ; // // // // Nachrichten - Header für zukünftige Nutzung reserviert Name der Collection Update - Optionen 55 A. MongoDB Wire Protocol document document 44 45 selector ; update ; 46 47 // Selektor - Objekt // Beschreibung , welche Attribute wie // verändert werden sollen } 48 49 50 51 Einfüge - Operation ----------------- 52 53 54 55 56 57 58 struct OP_INSERT { MsgHeader header ; int32 flags ; cstring fu ll Co lle ct io nN am e ; document * documents ; } // // // // Nachrichten - Header Einfüge - Optionen Name der Collection ein oder mehrere Dokumente // // // // // Nachrichten - Header für zukünftige Nutzung reserviert Name der Collection Lösch - Optionen Selektor für zu löschende Dokumente // // // // // Nachrichten - Header für zukünftige Nutzung reserviert Name der Collection max . Anzahl an Dokumenten ( Limit ) Cursor - ID aus vorheriger Anfrage 59 60 61 62 Lösch - Operation ---------------- 63 64 65 66 67 68 69 70 struct OP_DELETE { MsgHeader header ; int32 ZERO ; cstring fu llC ol le ct io nN am e ; int32 flags ; document selector ; } 71 72 73 74 Nachlade - Operation ------------------ 75 76 77 78 79 80 81 82 struct OP_GET_MORE { MsgHeader header ; int32 ZERO ; cstring fu llC ol le ct io nN am e ; int32 numberToReturn ; int64 cursorID ; } 83 84 85 86 Cursor - Lösch - Operation ---------------------- 87 88 89 90 91 92 93 struct OP_KILL_CURSOR { MsgHeader header ; int32 ZERO ; int32 numbe rOfCur sorIDs ; int64 * cursorIDs ; } 94 56 // // // // Nachrichten - Header für zukünftige Nutzung reserviert Anzahl der zu löschenden Cursor Sequenz mit zu löschenden Cursor - IDs 95 96 97 Diagnose - Operation ------------------ 98 99 100 101 102 struct OP_MSG { MsgHeader header ; // Nachrichten - Header cstring message ; // Diagnose - Nachricht } 103 104 105 106 Antwort - Operation ----------------- 107 108 109 110 111 112 113 114 115 struct OP_REPLY { MsgHeader header ; int32 responseFlags ; int64 cursorID ; int32 startingFrom ; int32 numberReturned ; document * documents ; } // // // // // // Nachrichten - Header diverse Flags , u . a . für Fehler Cursor - ID für Nachlade - Operation Offset des aktuellen Cursors Anzahl der Ergebnis - Dokumente Ergebnis - Dokument ( e ) 57 B. Installation und Nutzung der Middleware Um die Middleware testen zu können, muss folgende Software auf dem System installiert sein: • Node.js (Version 0.10 oder neuer) • MongoDB (Version 2.2 oder neuer) • npm (Node.js Paketmanager, ist in der Installation von Node.js enthalten) Die Installationspakete können auf den Webseiten von Node.js (http://nodejs.org/) und MongoDB (http://www.mongodb.org/) heruntergeladen werden. Die Middleware wurde unter OS X 10.9 entwickelt und getestet, sollte jedoch auch unter Linux oder Windows lauffähig sein. Beide Projekte, Client und Middleware, sind ähnlich strukturiert. Im Hauptverzeichnis befinden sich diverse Konfigurationsdateien, die Einstellungen für IDEs (Integrated Development Environment) enthalten. Erwähnenswerte Dateien sind hier die packages.json (npm Paketbeschreibung) und die index.js, welche den Einstiegspunkt für die Projekte darstellen. Im Verzeichnis lib befindet sich jeweils die Implementierung für den Client bzw. die Middleware. Im test-Verzeichnis liegen die Unit- und Integrationstests. Unterhalb von node modules liegen externe Bibliotheken wie Unit-Test-Frameworks oder der ”MongoDB Native Driver”, die über den Paketmanager installiert wurden. Die Middleware ist so konfiguriert, dass sie Verbindungen auf dem Port 27018 entgegennimmt und Verbindungen zum MongoDB-Server auf Port 27017 aufbaut. Sie kann auf der Kommandozeile über den Befehl npm start gestartet werden. Zum Beenden kann die Tastenkombination STRG+C gedrückt werden. Die Tests können mit dem Befehl npm test ausgeführt werden. Für die Ausführung der Tests ist es nicht notwendig, die Middleware vorher zu starten. Sie wird intern automatisch zu Beginn der Tests gestartet und nach dem Durchlauf wieder beendet. 59 Literatur [1] Apache CouchDB. url: http://couchdb.apache.org/. [2] Apache HBase. url: http://hbase.apache.org/. [3] BSON - Binary JSON. url: http://bsonspec.org/. [4] Fay Chang u. a. Bigtable: A Distributed Storage System for Structured Data Bigtable: A distributed storage system for structured data. Google, 2006. [5] Jeff Dean und Sanjay Ghemawat. MapReduce: Simplified Data Processing on Large Clusters. 2004. url: http://research.google.com/archive/mapreduce.html. [6] Git Repository ”Mongo DB Native NodeJS Driver” auf GitHub. url: https://github. com/mongodb/node-mongodb-native. [7] Hypertable. url: http://hypertable.com/. [8] Introduction Into The Views - Apache CouchDB 1.7.0 Documentation. url: http : //docs.couchdb.org/en/latest/couchapp/views/intro.html. [9] MongoDB. url: http://www.mongodb.org/. [10] MongoDB Aggregation Pipeline. url: http : / / docs . mongodb . org / manual / core / aggregation-pipeline/. [11] Inc. MongoDB. MongoDB Wire Protocol. url: http : / / docs . mongodb . org / meta driver/latest/legacy/mongodb-wire-protocol/ (besucht am 09. 04. 2014). [12] Node.js. url: http://nodejs.org/. [13] NoSQL Databases. url: http://nosql-database.org/. [14] npm (Node Package Modules). url: https://www.npmjs.org/. [15] npm package.json Dokumentation. url: https://www.npmjs.org/doc/json.html. [16] Till Rausch. Service Orientierte Architektur: Übersicht und Einordnung. url: http: //web.archive.org/web/20081010033719/http://www.till-rausch.de/assets/ baxml/soa_akt.pdf (besucht am 10. 10. 2008). [17] Gunter Saake, Andreas Heuer und Kai-Uwe Sattler. Datenbanken: Implementierungstechniken. Bd. 2. mitp-Verlag, Bonn, 2005. [18] Stefani Scherzinger, Meike Klettke und Uta Störl. Managing Schema Evolution in NoS” QL Data Stores Managing Schema Evolution in NoSQL Data Stores“. In: Proceedings of the 14th International Symposium on Database Programming Proceedings of the 14th International Symposium on Database Programming Languages (DBPL 2013), August 30, 2013, Riva del Garda, Trento, Italy. 2013. [19] The Apache Cassandra Project. url: http://cassandra.apache.org/. 61 Literatur [20] The JSON Data Interchange Format. url: http://www.ecma-international.org/ publications/files/ECMA-ST/ECMA-404.pdf. 62 Abbildungsverzeichnis 2.1 Architektur und Datenfluss einer Webanwendung . . . . . . . . . . . . . . . . 12 5.1 5.2 Architektur A: Sichtenverwaltung durch Middleware . . . . . . . . . . . . . . Architektur B: Sichtenverwaltung im Client . . . . . . . . . . . . . . . . . . . 35 36 6.1 6.2 6.3 6.4 6.5 Datenfluss innerhalb der Middleware (Proxyserver) . . . . . . . Datenfluss innerhalb der Middleware mit Interceptoren . . . . . Datenfluss beim Anlegen einer materialisierten Sicht . . . . . . Sequenzdiagramm für die Anfrage auf eine materialisierte Sicht Sequenzdiagramm für die Anfrage auf eine virtuelle Sicht . . . 42 43 45 46 47 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 Tabellenverzeichnis 5.1 Vergleich der Architekturen zur Sichtenverwaltung . . . . . . . . . . . . . . . 36 7.1 Bewertung zusätzlicher Anfragen für Sichtoperationen . . . . . . . . . . . . . 50 65 Glossar API Bei einer API (Application Programming Interface) handelt es sich um eine Programmierschnittstelle auf Quelltext-Ebene.. 17, 21 BSON Bei BSON (Binary JSON) handelt es sich um ein Datenaustauschformat, welches auf JSON basiert. Durch die binäre Codierung von Datentypen können Dokumente meist mit weniger Speicherbedarf serialisiert werden. [3]. 43 JSON JSON (JavaScript Object Notation) ist ein leichtgewichtiges Datenaustauschformat, welches auf einer Untermenge der Programmiersprache JavaScript basiert. Es ist für die Serialisierung hierarchischer Strukturen geeignet. [20].. 39, 43, 67 Node.js Node.js [12] ist eine Plattform, welche auf die für den Browser ”Google Chrome” entwickelte Javascript-Laufzeitumgebung ”V8” aufbaut. Aufgrund ihres Event-basierten, nicht-blockierenden Eingabe/Ausgabe-Models ist sie besonders für datenintensive Netzwerkanwendungen geeignet.. 3, 67 npm npm [14] ist der offizielle Paket-Manager für Node.js. Mit ihm lassen sich Node.jsModule, die auf der Plattform npmjs.org registriert sind, automatisch installieren und updaten. Eventuelle Abhängigkeiten zu anderen Modulen werden automatisch aufgelöst und ebenfalls installiert. Die Abkürzung ”npm” steht für das rekursive Backronym ”npm is not an acronym”.. 39 67 Selbständigkeitserklärung Ich erkläre, dass ich die vorliegende Arbeit selbständig und nur unter Vorlage der angegebenen Literatur und Hilfsmittel angefertigt habe. Rostock, den 30. September 2014