|
<< Click to Display Table of Contents >> Navigation: Programmiermodell > Einige Bemerkungen über Datenbanken |

Die Antwort auf diese Frage ist zentral und man sollte schon einmal darüber genauer nachdenken.
Nehmen wir mal den READ-Call eines CRUD Konstrukts: Hier erwarten wir eine Liste von Objekten zurück. Logisch. Aber wo finden wir die Quelle, ab der wir diese Objekte zusammensammeln ?
Bei relationalen Datenbanken kann das einfach sein. Es muß eine bestimmte Tabelle sein, über die eine Query gestartet wird mit gewissen Optionen (foreign-key Auswertung, sorting, paging). Das kann die Datenbank sehr gut schaffen. Mit dem Wissen der relationalen Struktur, kann der Entwickler das einfach bauen.
Bei objektorientierten Datenbanken kann das aber zum Problem werden.
Bei diesen Datenbanken haben wir einen persistenten Baum mit einem ROOT-Objekt, von dem aus alle anderen Objekte dieser Anwendung erreichbar sind.
Man braucht also Zugriffspfade ab der ROOT, um das Ausgangsobjekt zu finden, ab dem man die Ergebnisliste zusammensammeln kann. Nun - man könnte argumentieren, daß auch hier der Entwickler dieses mit der OO-Struktur sehr wohl wissen kann und ausprogrammieren kann. Das funktioniert auch - wenn die dabei durchsuchten Datenmengen relativ klein sind. Dann kann eine OO-DB immer noch schneller sein als eine Query in einer relationalen Datenbank.
Aber es gibt ein Muster bei den Datenstrukturen, wo das nicht mehr funktioniert - und ich bin natürlich auf dieses Muster reingefallen und mußte mir dann helfen.
Schwierig ist eine Struktur, wenn 10tausende Objekte aufgrund eines überragendes Attributes/Status in verschiedene Assoziationen unterhalb eines Hauptobjektes gespeichert werden und (obacht !) diese Objekte die Zugehörigkeit zu den Assoziationen regelmäßig ändern können.
Dadurch verlieren eventuelle Zugriffspfade regelmäßig die Gültigkeit, und man muß suchen: das ist teuer. Natürlich kann man Indices auf die Assoziationen definieren - aber das bedeutet dann intern halt auch mehrere Queries und die Stärke von Gemstone/S ist nicht unbedingt die Ausführung einer Query. Also baut man sich eine Hilfsstruktur, in der alle diese 10tausende Objekte eingetragen sind - und setzt nur darauf einen Index.
Und wenn diese Sache noch nicht schwierig genug ist: Die Aufteilung in mehrere Assoziationen reduziert die Concurreny-Problematik bei den Bewegungen zwischen den Assoziationen.
Diese Problematik des Findes existiert in allen OODBMS-Strukturen, wo man es mit einem persistenten Teilbaum zu tun hat - z.B. auch in VASTPlatform im Dumper-Format.
Aber Gemstone/S liefert für die Suche einen Attributwert, der mehr als Gold wert ist: den OOP Wert jede Objektes. Gemstone/S kann über diesen Wert und einem internen HASH-Lookup jedes Objekt sehr schnell finden. Die Lösung !
Aber eine Lösung, die - wenn man sie ernst meint - zu einer radikalen Neugestaltung der gesamten API führt. Überall in der API findet man nun Parameter, die nichts anderes sind, als die OOP-Werte des zu suchenden Objektes. Diese Werte sind Integerzahlen - sehr schön für die Performance und dem JSON-Format, aber auch fehleranfällig. Also baute ich überall Checks ein, ob das gefundene Objekt auch von der erwarteten Klasse abstammt.
In einem der ersten Projekte mit Gemstone/S habe ich die meisten API Calls erweitert um die OOP-Werte, was natürlich auch die Umprogrammierung von diversen Clients mit sich brachte.
Also die OOP Werte wollen wir behalten - der Gewinn ist überragend.
Aber der direkte "Sprung" via OOP auf das Objekt führt dazu, daß man das Objekt nun direkt hat - aber das Umfeld es Objektes kann man eventuell nicht erreichen. Daher muß das Model so definiert sein, das man von jedem Objekt heraus auch zurück in den Hauptbaum gelangen kann - via Navigation.
Beispiel: Nehmen wir mal an, daß es ein "Customer"-Objekt mit mehreren Projekten gibt (via einer Assoziation). Nun wollen wir ein Projekt löschen - via OOP gelangen wir schnell an das Projekt-Objekt. Aber die haltende Assoziation liegt im Customer. Wie gelangen wir zum Customer, wenn wir keine "Rück"-Assoziation definiert haben ? Man könnt eine weitere OOP (auf den "Customer") in der API hinzufügen. Habe ich alles in den Projekten am Anfang falsch gemacht - aber nun achte ich in den Modellen darauf, daß es immer Rückassoziationen gibt.
Nun gibt es natürlich Variationen bei OO-Implementierungen - man hat keinen globalen Persistenzbaum - sondern viele kleine Bäume und je nach Bedarf lädt man eben nur den Teilbaum. Ja, das ist schneller - aber wenn man das obige Beispiel nimmt: ich weiß nicht immer, in welchem Teilbaum das gesuchte Objekt sich befindet. Und man sollte sich fragen, ob die Identität im Arbeitsspeicher immer gewährleistet ist - oder "nur" die Gleichheit.
Persistenz ist ein schwieriges Gebiet und je nach Anwendungsbeispiel kann es einfachere oder schwierigere Lösungen geben. Den Dumper unter VASTPlatform nutze ich gerne - er ist schnell und einfach zu nutzen.
Diese Lösungen funktionieren sehr gut, wenn die Erstellung der UI im gleichen Context stattfindet wie die Speicherung. Wenn ich aber die Daten zur UI transferieren muß (Browser Client Server), dann gewinnt alles an Komplexität.
Pauschal kann man das natürlich nicht beantworten - aber man kann es an den Strukturen sehen, die man sich ausdenkt oder vorfindet. Tabellarische Strukturen (z.B. CSV) können in RDBMS i.d.R. schneller bearbeitet werden. Für beide Modelle gilt: Eine Tabelle mit weniger Einträgen (RDBMS) oder eine Assoziation mit weniger Einträgen (OODBMS) ist immer schneller.
Eine Partitionierung einer großen Tabelle in einer RDBMS kann daher einen Geschwindigkeitsvorteil bringen. Wenn man sich die Assoziationen in einer OODBMS als "Materialized Views" einer Tabelle denkt, dann kann klar werden, warum eine OODBMS bei einer korrekten Struktur "fliegen" könnte: Eine Query ist nicht notwendig und der Zugriff ist extrem schnell und benötigt bei den meisten Assoziationen keinen Index.