Transformation von NoSQL-Daten (JSON) in relationale Datenbanken
geschrieben von Felix Beuster amIn diesem Semester habe ich mein Bachelor-Studium an der Universität Rostock mit einer Bachelorarbeit abgeschlossen, die sich damit befasst hat JSON Daten in ein relationales Datenbankschema zu übertragen. Sowohl die Arbeit als auch die prototypische Implementierung können auf meinem GitHub-Profil eingesehen werden. Einige der nachfolgenden Ausführungen, habe ich auch hier im Video erklärt:
Motivation
Vielleicht vorweg zur Motivation des Themas ein kleines Beispiel. Betrachten wir mal einen Onlineshop, bspw. den von Tesla. (Anm.: Das Beispiel ist fiktiv und die tatsächliche Vorgehensweise von Tesla kann abweichen.) Hier kann man sich ein Auto konfigurieren, Zubehör und Funktionen auswählen und bekommt am Ende auch einen Preis angezeigt. Im weiteren Verlauf werden dann Name und weitere Kundendaten abgefragt. All diese Daten werden an den Server übermittelt und müssen gespeichert werden. Dies geschieht in einer JSON-Datei, die wie folgt aussehen kann:
1{
2 "order id" : 26541,
3 ...
4 "model" : {
5 "name" : "Model S",
6 "year" : 2015
7 },
8 "fetures" : {
9 "color" : "Obsidian Black Metallic",
10 ...
11 "ludicrous speed upgrade" : true,
12 "carbon fiber spoiler" : true,
13 "red breakes" : true
14 },
15 "price" : 142750,
16 "owner" : {
17 "name" : "Felix Beuster",
18 "address" : {
19 "street" : "0000 Murray Ave",
20 "city" : "Pittsburgh",
21 "state" : "PA",
22 "zip" : "15217",
23 "country" : "United States"
24 },
25 "orders" : ["742", 26541],
26 "complaints" : []
27 }
28}
Im weiteren Produktionsverlauf wird diese Datei genutzt um weitere Daten über das Fahrzeug zu speichern: Ein allgemeiner Produktionsstatus, Details zu einzelnen Produktionsschritten, zugewiesene Mitarbeiter, Berichte von Qualitätskontrollen. Nach Auslieferung können Telemetriedaten wie bspw. der Kilometerstand oder auch Fahrerprofile dort gespeichert werden, im Servicefall werden die Reparaturberichte hier abgelegt usw. Kurzum: Am Ende liegt eine einzelne Datei vor, die alle wichtigen Daten über das Fahrzeug bereit hält. Dies kann vorteilhaft sein, sollte ein Problem mit dem Fahrzeug auftreten und die Daten benötigt werden.
Schwierig sind bei diesem Konzept allerdings unter anderem Suchen in den Dateien, oder sogar Suchen und Verknüpfungen zwischen Dateien. Alle roten Fahrzeuge aus 2015 zu finden, welche einen Kratzer in der Fahrertür haben, die von Mitarbeiter 73 eingebaut wurde ist sehr aufwendig. Hier haben relationale Datenbanken einen erheblichen Vorteil.
Aufgabe
Daher sollte es in meiner Arbeit darum gehen, für einen bestehenden JSON-Datenbestand, von dem kein Schema bekannt ist, ein relationales Datenbankschema zu erstellen und die Daten dann auch in diese neue Datenbank zu transformieren.
Die Aufgabe ordnet sich in die Data Integration Pipeline ein. Gegeben waren mir bereits Grundlagen für die Schemaextraktion aus den JSON-Daten, diese Grundlagen kamen aus einer vorhergehenden Masterarbeit. Schemaextraktion bedeutet in diesem Fall, dass ein allgemeines Schema aus allen Daten gebildet wird und im Ergebnis jede einzelne Datei mit diesem Schema konform sein muss.
Auf diese Extraktion sollte ich nun aufbauen und aus dem auf JSON-Schema basierendem Schema ein relationales Schema erstellen, daraus eine Datenbank anlegen und die Daten übertragen.
Weitere Grundlagen
Weitere Grundlagen sind die vielfältigen Objektmapping-Strategien aus dem XML-Bereich, sowie verschiedene Systeme im JSON-Bereich. Argo ist bereits ein existierendes Transformationssystem, allerdings ist das relationale Schema doch relativ beschränkt.
Ein besserer Ansatz wurde dieses Jahr auf der SIGMOD vorgestellt. Hierbei wurden strukturelle Abhängigkeiten dann auf Basis der Daten extrahiert. Ob das eher vorteil- oder eher nachteilhaft ist, darf gerne diskutiert werden. Eine Universaltabelle, die alle Daten enthält, umzustrukturieren dürfte aber definitiv ein arbeitsintensiver Prozess für die Datenbank sein. Ebenfalls wurden in dieser Arbeit Arrays in JSON-Dokumenten kaum berücksichtigt.
Abbildungsansatz
Mein Ansatz für die Abbildung für die JSON-Strukturen ergibt sich nun wie folgt:
- JSON-Objekte > Relationen
- JSON-Arrays > Relationen
- JSON-Primitives (Daten) > Attribute von Relationen
Grundsätzlich ist dieser Abbildungsansatz relativ logisch und einleuchtend. Besonders interessant ist hierbei vielleicht die Abbildung der Arrays. Während ein Objekt immer durch ein Tupel repräsentiert wird, wird ein einzelnes Array immer durch mehrere Tupel dargestellt, genauer gesagt durch ein Tupel je Element des Arrays.
Das bedeutet aber auch, dass hier keine einfache Array ID als Primärschlüssel für die Relation genutzt werden kann. Mit Hilfe der Ordnungsnummer eines Elements können wir einen kombinierten Schlüssel erstellen, welcher wiederum eindeutig ist. Bei Objekt-Relationen kann ein einfacher Schlüssel genutzt werden, bspw. ein Integer-Wert, welcher automatisch inkrementiert wird.
Auftretende Verschachtlungen werden über eine Rekursion gelöst. Das verschachtelte Objekt oder Array wird auf eine Kindrelation abgebildet. Die Elternrelation erhält für das Objekt ein Fremdschlüssel-Attribut, welches auf die neue Relation verweist.
Ein Problem welches in JSON-Datenbeständen auftreten kann, ist eine hohe Datenredundanz. Diesem möchte ich ebenfalls begegnen und zumindest Redundanz in einzelnen Relationen vermeiden.
Hierbei war mein erster Ansatz, einen UNIQUE INDEX zu erstellen, der alle Datenattribute umfasst. Damit würden doppelte Einträge automatisch erkannt und nicht eingefügt. Allerdings scheiterte diese Variante an Index Limitierungen von MySQL, ein Thema was ich separat in einem anderen Artikel aufgreifen kann. Kurz gesagt hätte ich meinen Index sehr kurz halten und auf Grund dessen viele Kollisionen in Kauf nehmen müssen. Diese false-positives sind aber nicht wirklich wünschenswert.
Genutzt wird am Ende daher ein einfaches SELECT bevor ein INSERT durchgeführt wird. Dies ist zwar ein langsames Verfahren, dafür aber eindeutig. Beschleunigen lässt sich dieses Verfahren durch den Einsatz eines regulären Indexes. Dieser unterliegt zwar den selben Limitierungen, allerdings sind Kollisionen hier kein Problem, da am Ende Referenzen auf alle Einträge in einer Liste aufbewahrt werden.
Mehr Details zu dem und anderen Problemstellungen können am besten direkt in der Arbeit nachgelesen werden.
Am Ende liegt ein relationales Schema vor, dass wie folgt in CREATE TABLE Anweisungen exportierten werden kann. Dies ist dann auch direkt für die Erstellung einer Datenbank nutzbar. Im Folgenden sind ein JSON-Dokument und das dazu passende relationale Schema dargestellt:
1{
2 "model" : {
3 "name" : "Model S",
4 "year" : 2015
5 }
6}
1CREATE TABLE `myDB`.`model`(
2 `ID` INT NOT NULL AUTO_INCREMENT,
3 `name` MEDIUMTEXT NULL,
4 `year` DOUBLE NULL,
5 PRIMARY KEY (`ID`))
6CREATE TABLE `myDB`.`tesla_orders`(
7 `ID` INT NOT NULL AUTO_INCREMENT,
8 `model_ID` INT NULL,
9 PRIMARY KEY (`ID`),
10 FOREIGN KEY `fk_model_ID`(`model_ID`)
11 REFERENCES `model`(`ID`) ON UPDATE CASCADE ON DELETE SET NULL)
Optimierung des Schemas
Die reine Transformation, wie zuvor beschrieben, resultiert nicht zwangsläufig in einem idealem Schema für den vorliegenden Datenbestand. Daher gibt es verschiedene Möglichkeiten, dieses Schema zu optimieren. Im Detail sind diese ebenfalls der Bachelorarbeit zu entnehmen. Kurz zusammengefasst sind meine erläuterten und auch umgesetzten Ansätze die folgenden:
- Verschmelzen gleicher oder sehr ähnlicher Relationen
- Einbetten von Kind-Relationen in Eltern-Relationen
- Entfernen von Attributen auf Grundlage statistischer Informationen
Bei vielen Optimierungen sind jedoch semantische Kenntnisse über diese Daten nötig, die dieses Verfahren nicht hat. Daher sollten in einer Implementierung Anpassungsmöglichkeiten für Anwender geschaffen werden, sodass diese das Verfahren auf ihren Datenbestand zuschneiden kann.
Implementierung
Meine Implementierung wurde in Java durchgeführt. Als NoSQL-System diente MongoDB, das relationale Zielsystem war MySQL. Der Quellcode mit Anleitungen zur Ausführung ist wie erwähnt auf meinem GitHub-Profil vorhanden.
Die gerade angesprochene Konfigurationsmöglichkeit wurde mit YAML umgesetzt, ein sehr simples Dateiformat, dass sich hierfür ideal eignet.
Die Implementierung ist teils ausgereift, in vielen anderen Teilen aber auch sehr prototypisch und kann sicherlich noch viel optimiert werden. Also seid hier bitte nicht zu hart in der Beurteilung des Quellcodes.
Fazit
Eine Auswertung und Bewertung verschiedener Ansätze ist ebenfalls in der Arbeit enthalten, dies möchte ich an dieser Stelle einmal auslassen. Grundsätzlich bin ich mit dem Ergebnis des Verfahrens und der Note der Arbeit (1.5) zufrieden. Abgesehen vielleicht von einem Detail, wofür mir unser Datenbank Professor die Goldene Himbeere für verleihen würde.
Schlussendlich liegt hier aber nun ein Verfahren vor, mit dem NoSQL-Daten in ein relationales Schema übertragen werden können. Dieses Schema ist dabei relativ anpassbar, sodass es besser auf den eigenen Datenbestand zugeschnitten werden kann.
In Zukunft bieten sich hier noch weitere Ansätze dieses Verfahren weiter zu entwickeln. So könnte ein Schemamatching eingebunden werden, sodass also gewisse Teile des Schemas einer bestimmten Vorgabe entsprechen müssen. Auch ist es denkbar, dass das Verfahren mehrere Schemas erstellt und diese nach verschiedenen Gesichtspunkten bewertet werden und so eine Auswahl eines Schemas möglich wird.
Fragen und Anregungen gerne auch in die Kommentare.
Kommentare
Bisher keine Kommentare.Bitt warte 20 Sekunden, bevor du das Formular abschicken kannst.