Datenbanken
30.06.2023, 08:19 Uhr
Die Wirtschaftlichkeit des Datenbankbetriebs
Der Umstieg auf eine Dokumentendatenbank und die Anwendung geeigneter Datenmodellierungstechniken kann mehr Leistung für weniger Geld bringen.
(Quelle: dotnetpro)
Steigende Kosten und unsichere wirtschaftliche Zeiten veranlassen viele Unternehmen dazu, nach Einsparmöglichkeiten zu suchen. Datenbanken sind da keine Ausnahme. Glücklicherweise gibt es Möglichkeiten, die Effizienz zu steigern und Geld zu sparen. Eine Möglichkeit kann der Umstieg auf eine Dokumentendatenbank sein und die Anwendung passender Datenmodellierungstechniken.
Dokumentendatenbanken bringen punkto Kosten gleich zwei Vorteile:
1. Objektzentrierte, sprachübergreifende SDKs und Schema-Flexibilität ermöglichen es Entwicklern, Produktionscode schneller zu entwickeln und zu iterieren – das führt zu niedrigeren Entwicklungskosten.
2. Für einen bestimmten Transaktionsdurchsatz ist weniger Hardware erforderlich – das senkt die Betriebskosten.
Entwicklereffizienz
Alle modernen Entwicklungsprojekte basieren auf dem Konzept der objektorientierten Programmierung. Objekte definieren eine Reihe zusammenhängender Werte und Methoden zum Lesen, Ändern und Ableiten von Ergebnissen aus diesen Werten. Kunden, Rechnungen oder Zugfahrpläne sind Beispiele für solche Objekte. Objekte sind, wie alle Programmvariablen, vergänglich, und müssen daher auf einem Speichermedium gesichert werden.
Inzwischen serialisieren wir Objekte nicht mehr manuell in lokale Dateien, wie es Windows-Desktop-Entwickler noch in den 1990er-Jahren getan haben. Heutzutage werden Daten nicht mehr auf dem Computer gespeichert, auf dem eine Anwendung ausgeführt wird, sondern an einem zentralen Ort, auf den mehrere Anwendungen oder Instanzen einer Anwendung zugreifen. Dieser gemeinsame Zugriff bedeutet, dass wir Daten effizient über ein Netzwerk lesen und schreiben können müssen. Zudem müssen wir Mechanismen implementieren, um gleichzeitige Änderungen an diesen Daten zu ermöglichen, ohne dass ein Prozess die Änderungen eines anderen überschreibt.
Relationale Datenbanken gab es bereits vor der breiten Anwendung und Umsetzung der objektorientierten Programmierung. In einer relationalen Datenbank bestehen die Datenstrukturen aus Tabellen mit Werten. Die Interaktion mit den Daten erfolgt über eine spezielle Sprache, die Structured Query Language (SQL). Diese hat sich in den letzten 40 Jahren weiterentwickelt, um alle Arten der Interaktion mit den gespeicherten Daten zu ermöglichen: Filtern, Umstrukturieren und Konvertieren von deduplizierten, zusammenhängenden Modellen in tabellarische, das Ausgeben von über Joins wieder duplizierte, verknüpfte Informationen, die dann den Anwendungen zur Verfügung gestellt werden. Die Daten werden dann aus diesen Zeilen eventuell redundanter Werte wieder in die Objekte konvertiert, die das Programm benötigt.
All dies erfordert eine bemerkenswerte Menge an Entwicklerarbeit, Fähigkeiten und Fachwissen. Die Entwickler müssen die Beziehungen zwischen den Tabellen verstehen. Sie müssen wissen, wie sie unterschiedliche Informationssätze abrufen und dann ihre Datenobjekte aus diesen Datenzeilen neu erstellen können. Es wird zwar meist davon ausgegangen, dass sie das lernen, bevor sie ins Arbeitsleben starten, und es daher auf Anhieb können. Aber das stimmt selten. Selbst wenn Entwickler eine formale Ausbildung in SQL absolviert haben, ist es unwahrscheinlich, dass sie wissen, wie man anspruchsvollere Anwendungen effizient entwickelt.
Dokumentdatenbanken basieren auf der Idee der Persistenz von Objekten. Sie ermöglichen die Speicherung eines stark typisierten Objekts in einer Datenbank mit sehr wenig Code. Darüber hinaus erlauben sie es, Ergebnisdaten anhand von Beispielobjekten zu filtern, umzuwandeln und zu aggregieren, anstatt eine Beschreibung mittels SQL zu erstellen.
Stellen Sie sich vor, wir möchten ein Kundenobjekt speichern, in dem Kunden über ein Array mit sich wiederholenden Attributen verfügen, in diesem Fall Adressen. Eine Adresse ist hier eine schwache Entität, die nicht zwischen Kunden geteilt wird. Hier ist der Code in C# /Java-ähnlichem Pseudocode:
class Address : Object {
Integer number;
String street, town, type;
Address(number, street, town, type) {
this.number = number
this.street = street
this.town = town,
this.type = type
}
//Getters and setters or properties as required
}
class Customer : Object {
GUID customerId;
String name, email
Array < Address > addresses;
Customer(id, name, email) {
this.name = name;
this.email = email;
this.customerId = id
this.addresses = new Array < Address > ()
}
//Getters and setters or properties as required
}
Customer newCustomer = new Customer(new GUID(),
"Sally Smith", "sallyport@piratesrule.com")
Address home = new Address(62, 'Swallows Lane', 'Freeport', 'home')
newCustomer.addresses.push(home)
Um dieses Kundenobjekt in einem relationalen Datenbankmanagementsystem (RDBMS) zu speichern und dann alle Kunden an einem bestimmten Ort abzurufen, benötigen wir den folgenden oder einen ähnlichen Code:
//Connect
RDBMSClient rdbms = new RDBMSClient(CONNECTION_STRING)
rdbms.setAutoCommit(false);
// Add a customer
insertAddressSQL = "INSERT INTO Address
(number,street,town,type,customerId) values(?,?,?,?,?)"
preparedSQL = rdbms.prepareStatement(insertAddressSQL)
for (Address address of newCustomer.addresses) {
preparedSQL.setInt(1, address.number)
preparedSQL.setString(2, address.street)
preparedSQL.setString(3, address.town)
preparedSQL.setString(4, address.type)
preparedSQL.setObject(5, customer.customerId)
preparedStatement.executeUpdate()
}
insertCustomerSQL = "INSERT INTO Customer
(name,email,customerId) values(?,?,?)"
preparedSQL = rdbms.prepareStatement(insertCustomerSQL)
preparedSQL.setString(1, customer.name)
preparedSQL.setString(2, customer.email)
preparedSQL.setObject(3, customer.customerId)
preparedStatement.executeUpdate()
rdbms.commit()
//Find all the customers with an address in freeport
freeportQuery = "SELECT ct.*, ads.* FROM address ad
INNER JOIN address ads ON ad.customerId=ads.customerId AND ad.town=?
INNER JOIN customer ct ON ct.customerId = ad.customerId"
preparedSQL = rdbms.prepareStatement(freeportQuery)
preparedSQL.setString(1, 'Freeport')
ResultSet rs = preparedSQL.executeQuery()
String CustomerId = ""
Customer customer;
//Convert rows back to objects
while (rs.next()) {
//New CustomerID value
if rs.getObject('CustomerId').toString != Customerid) {
if (customerId != "") { print(customer.email) }
customer = new Customer(rs.getString("ct.name"),
rs.getString('ct.email'),
rd.getObject('CustomerId')
}
customer.addresses.push(new Address(rs.getInteger('ads.number'),
rs.getString("ads.street"),
rs.getString('ads.town'),
rs.getString("ads.type")))
}
if (customerId != "") { print(customer.email) }
Dieser Code ist umständlich und wird mit zunehmender Tiefe oder Anzahl der Felder im Objekt immer komplexer. Zudem erfordert das Hinzufügen eines neuen Feldes auch eine Reihe korrespondierender Änderungen.
Im Gegensatz dazu würde der Code bei einer Dokumentendatenbank keine Änderungen an der Datenbankinteraktion erfordern, wenn Sie Ihrem Objekt neue Felder oder Tiefe hinzufügen. Er würde wie folgt aussehen:
mongodb = new MongoClient(CONNECTION_STRING)
customers = mongodb.getDatabase("shop")
.getCollection("customers",Customer.class)
//Add Sally with her addresses
customers.insertOne(newCustomer)
//Find all the customers with an address in freeport
FreeportCustomer = new Customer()
FreeportCustomer.set("addresses.town") = "Freeport"
FindIterable < Customer > freeportCustomers = customers.find(freeportCustomer)
for (Customer customer : freeportCustomers) {
print(customer.email) //These have the addresses populated too
Wenn Entwickler auf eine Diskrepanz zwischen dem Programmiermodell (Objekte) und dem Speichermodell (Zeilen) stossen, liegt es nahe, eine Abstraktionsschicht zu schaffen. Code, der automatisch Objekte in Tabellen und wieder zurück konvertiert, wird als objektrelationaler Mapper oder ORM bezeichnet. Leider sind ORMs in der Regel sprachspezifisch. Das bindet die Entwicklungsteams an diese Sprache und erschwert die Verwendung zusätzlicher Tools und Technologien mit den Daten.
Die Verwendung eines ORM befreit Sie auch nicht von SQL, wenn Sie eine komplexere Operation durchführen wollen. Zudem bringt ein ORM in der Regel auch nicht viel Effizienz bei der Datenspeicherung und -verarbeitung, da die zugrundeliegende Datenbank keine Objekte kennt.
Dokumentdatenbanken bewahren die Objekte, mit denen Entwickler bereits vertraut sind, sodass eine Abstraktionsschicht wie ein ORM nicht erforderlich ist. Wenn man einmal weiss, wie man MongoDB in einer Sprache verwendet, weiss man auch, wie man die Datenbank in allen anderen Sprachen verwendet.
Viele der aktuellen Versionen relationaler Datenbanken wie PostgreSQL, Oracle oder SQL Server können mit JSON-Daten umgehen. Aber auch in diesen Fällen kommt man nicht von SQL weg. In einem relationalen Datenbankmanagementsystem steht JSON für unstrukturierte Daten.
Weniger Hardware für die gleiche Arbeitslast
Eine moderne Dokumentendatenbank ist einem RDBMS im Kern sehr ähnlich. Aber im Gegensatz zum normalen relationalen Modell, bei dem das Datenbankschema vorschreibt, dass alle Anfragen gleich behandelt werden, optimiert eine Dokumentendatenbank dieses Schema für eine bestimmte Aufgabe auf Kosten anderer Aufgaben.
Das Dokumentenmodell bringt die Idee der indexorganisierten Tabelle und des geclusterten Index auf eine höhere Ebene. Denn hier werden nicht nur verknüpfte Datensätze wie im relationalen Modell zusammengeführt, sondern alle Daten, die Sie voraussichtlich für eine bestimmte Aufgabe verwenden wollen. Es basiert auf der Idee, dass ein sich wiederholendes untergeordnetes Attribut einer Relation nicht in einer separaten Tabelle (und damit im Speicher) enthalten sein muss, wenn Sie einen Array-Typ erster Ordnung haben. Oder anders ausgedrückt: Sie können einen Spaltentyp als «eingebettete Tabelle» verwenden.
Diese gleichzeitige Anordnung oder, wie manche es nennen, die implizite Verbindung schwacher Tabellen von Entitäten, reduziert die Kosten für den Abruf von Daten aus dem Speicher, da oft nur ein einziger Cache- oder Festplattenspeicher gelesen werden muss, um ein Objekt an den Client zurück zu übermitteln oder einen Filter darauf anzuwenden.
Vergleichen Sie dies mit dem Aufwand, der nötig ist, um eine hohe Anzahl von Datensätzen zu identifizieren, zu lokalisieren und zu lesen, und die entsprechenden Daten zurück zu übertragen, sowie der clientseitigen Hardware, die erforderlich ist, um ein Objekt aus diesen Datensätzen zu rekonstruieren. Der ist so hoch, dass viele Entwickler ihrer primären Datenbank einen sekundären, einfacheren Schlüsselwertspeicher vorschalten, der als Cache fungiert.
Diese Entwickler wissen, dass die primäre Datenbank die Anforderungen an die Arbeitslast nicht allein erfüllen kann. Eine Dokumentendatenbank hingegen benötigt keinen vorgelagerten externen Cache, um die Performance-Ziele zu erreichen, kann aber dennoch alle Aufgaben eines RDBMS erfüllen, nur effizienter.
Wie viel effizienter? Ich habe versucht, durch die Erstellung eines Testmodells zu ermitteln, wie viel effizienter und kostengünstiger die Verwendung einer Dokumentendatenbank im Vergleich zu einer relationalen Standarddatenbank ist. In diesen Tests habe ich das Transaktionsvolumen pro Dollar für ein branchenführendes, in der Cloud gehostetes RDBMS mit einer in der Cloud gehosteten Dokumentendatenbank wie MongoDB Atlas verglichen.
Der von mir gewählte Anwendungsfall basiert auf einer gängigen, realen Anwendung, bei der ein Datensatz häufig aktualisiert und noch häufiger gelesen wird: eine Implementierung des britischen Fahrzeuginspektionssystems (MOT) und seiner öffentlichen und privaten Schnittstellen, die eigene, bereits publizierte Daten verwendet.
Die Tests ergaben, dass die Entwicklung, die Aktualisierung und die Lesevorgänge in MongoDB Atlas erheblich schneller sind. Insgesamt bewältigt MongoDB Atlas auf ähnlich spezifizierten Serverinstanzen mit ähnlichen Instanzkosten etwa 50 Prozent mehr Transaktionen pro Sekunde. Dieser Wert erhöht sich mit zunehmender Komplexität der relationalen Struktur, sodass die Kosten für die Verknüpfungsprozesse noch steigen.
Zusätzlich zu den Basis-Instanzkosten betrugen allein die stündlichen Betriebskosten der relationalen Datenbank bei diesen Tests aufgrund zusätzlicher Gebühren für die Festplattennutzung zwischen 200 und 500 Prozent der Atlas-Kosten. Die Kosten für das Hosting des Systems, das hochverfügbar sein muss, um die vorgegebenen Leistungsziele zu erreichen, waren bei Atlas insgesamt drei- bis fünfmal niedriger. In einfachen Worten: Mit Atlas konnten pro Dollar erheblich mehr Transaktionen pro Sekunde durchgeführt werden.
Unabhängige Tests bestätigen die Effizienz des Dokumentenmodells. Temenos, ein Software-Unternehmen mit Sitz in der Schweiz, das von den grössten Banken und Finanzinstituten der Welt genutzt wird, führt seit mehr als 15 Jahren Benchmark-Tests durch. In seinem jüngsten Test wickelte das Unternehmen 74'000 Transaktionen pro Sekunde (TPS) mit MongoDB Atlas ab.
Die Tests ergaben einen bis zu viermal höheren Durchsatz als bei einem ähnlichen Test vor drei Jahren, wobei 20 Prozent weniger IT-Infrastruktur benötigt wurde. Der Test wurde mit einer produktionsgerechten Benchmark-Architektur mit Konfigurationen durchgeführt, die Produktionssysteme widerspiegeln, einschliesslich nicht-funktionaler Anforderungen wie hoher Verfügbarkeit, Sicherheit und geschützten Verbindungen.
Während des Tests las MongoDB 74'000 TPS mit einer Antwortzeit von 1 Millisekunde, während gleichzeitig weitere 24'000 TPS eingelesen wurden. Da Temenos eine Dokumentendatenbank verwendet, gab es auch kein zwischengeschaltetes Caching. Alle Abfragen liefen direkt über die Datenbank.
Zusammenfassung
Die gängige objektorientierte Programmierung legt die Verwendung von Dokumentendatenbanken nahe, da es keinen Impedance Mismatch gibt: Die Hin- und Hertransformation zwischen Objekt und Tabelle entfällt. Somit können Sie durch die Umstellung Ihrer Arbeitsabläufe von einem relationalen auf ein Dokumentenmodell mit der gleichen Anzahl von Entwicklern in kürzerer Zeit mehr Aufgaben erledigen und gleichzeitig die Kosten für den Betrieb erheblich senken.
Quelle: John Page
Autor(in)
Redaktion
dotnetpro