Skalierbare Persistenz mit Azure Cosmos DB 12.05.2023, 00:00 Uhr

NoSQL für die Cloud

Ein Überblick über die NoSQL-Datenbank Azure Cosmos DB.
(Quelle: dotnetpro)
Bei der Entwicklung einer Lösung für die Cloud nimmt das Thema Skalierbarkeit einen besonderen Stellenwert ein, da die Lösung meist für eine sehr große Nutzerzahl ausgelegt werden soll. Hilfreich bei der Wahl der geeigneten Datenbank ist in diesem Fall eine Übersicht wie bei [1]. Von den geeigneten Kandidaten für solche Fälle, bei denen die Anforderungen an das Datenbanksystem eine geringe Latenz in Kombination mit einer hohen Skalierbarkeit sind, soll in diesem Artikel einer näher vorgestellt werden, nämlich Azure Cosmos DB.

NoSQL – was ist damit gemeint?

1998 erschien eine Open-Source-Datenbank, die keine SQL-Zugriffsmöglichkeiten bereitstellte – damals wurde erstmals der Begriff NoSQL verwendet, im Sinne von „No SQL“. Dabei ist zu unterscheiden zwischen der Verwendung dieses ­Begriffs für eine Datenbank, die nicht mit der Sprache SQL verwendet werden kann, und dem NoSQL-Konzept, welches sich vom relationalen Modell abwendet.
Der Begriff NoSQL im Sinne von „Nicht nur SQL“ (Not only SQL) wurde 2009 bei einem Treffen über verteilt strukturierten Datenspeicher von Johan Oskarsson neu eingeführt. Der Name war hierbei als Findung eines gemeinsamen Begriffs zu verstehen, der die wachsende Zahl an nicht relationalen, verteilten Datenspeichersystemen abdecken sollte [2].

NoSQL- und RDBM-Systeme

Bevor Cosmos DB in den Fokus rückt, soll eine Gegenüberstellung allgemeiner Merkmale den Unterschied zwischen RDBM-, also Relational-Database-Management-Systemen, und NoSQL-Datenbanken verdeutlichen (Tabelle 1).
Tabelle 1: Vergleich von NoSQL- und RDBM-Systemen
RDBMS NoSQL
Datenspeichermodell Tabellen mit fixen Zeilen und Spalten Dokumente im JSON-Format
Beispiele Microsoft SQL Server, Oracle, Maria DB Cosmos DB, Mongo DB
Schema Starr Flexibel
Skalierung Vertikal (Ausbau erfolgt durch einen leistungsstärkeren Server) Horizontal (Ausbau erfolgt durch zusätzliche Server)
Joins Werden typischerweise benötigt Häufig nicht benötigt
Tabelle 1: Vergleich von NoSQL- und RDBM-Systemen
RDBMS NoSQL
Datenspeichermodell Tabellen mit fixen Zeilen und Spalten Dokumente im JSON-Format
Beispiele Microsoft SQL Server, Oracle, Maria DB Cosmos DB, Mongo DB
Schema Starr Flexibel
Skalierung Vertikal (Ausbau erfolgt durch einen leistungsstärkeren Server) Horizontal (Ausbau erfolgt durch zusätzliche Server)
Joins Werden typischerweise benötigt Häufig nicht benötigt

Cosmos – ein erster Überblick

Cosmos DB kann als Microsofts NoSQL-Datenbank in Azure beschrieben werden. Die Datenbank weist unter anderem folgende Merkmale auf:
  • Weltweit verteilt: Cosmos kann auf die global verteilten Rechenzentren verteilt und somit näher beim Einsatzort des Kunden ausgeführt werden.
  • Horizontale Partitionierung: Hierdurch kann ein enormer Umfang ermöglicht werden. Was im Detail damit gemeint ist, wird in den folgenden Absätzen erläutert.
  • Bereitgestellter Durchsatz: Hierdurch kann sichergestellt werden, dass die benötigte Performance auch bei einer hohen Last zur Verfügung steht.
  • Schemalos: Beliebige Datentypen können persistiert und abgefragt werden. Auch solche, die aktuell noch nicht berücksichtigt werden. Die Datenbank wird somit zukunfts­sicherer, da sie vermutlich einfacher mit zukünftigen Anforderungen zurechtkommt.

Horizontale Skalierbarkeit

Bei der Verwendung von Cosmos erfolgen die Lese-/Schreibzugriffe auf einen Container (siehe Bild 1). Unterhalb des Containers werden die Daten auf einem Server-Cluster physischer Maschinen mit eigener CPU und eigenem Speicher verwaltet. Dies erfolgt vollkommen transparent, das heißt, der Entwickler arbeitet lediglich mit seinem Container. Cosmos verwaltet automatisch die Server je nach Bedarf. Dies ist die horizontale Skalierbarkeit, wodurch Folgendes erreicht wird:
Container und Server-Cluster (Bild 1)
Quelle: Autor
  • eine logisch uneingeschränkte Speicherkapazität und
  • uneingeschränkter Durchsatz.

Nicht relational

Bei RDBM-Systemen wird mit Tabellen gearbeitet, die mit Fremdschlüsseln in Beziehung zueinander gesetzt werden (Bild 2). Um diese Fremdschlüsselbeziehungen aufzulösen, werden die betroffenen Tabellen gejoint.
Beziehungen bei Tabellen (Bild 2)
Quelle: Autor
Bei Cosmos wird jedoch anstelle von Tabellen mit Dokumenten im JSON-Format gearbeitet. Grundsätzlich wäre es natürlich auch möglich, ein normalisiertes Datenmodell zu ­erstellen, in welchem ein Dokument ein anderes referenziert (Bild 3), jedoch wäre dies nicht besonders effizient, da die JSON-Dokumente auf verschiedene Server im Cluster verteilt sein können. Das heißt, um eine Fremdschlüsselbeziehung aufzulösen, müsste die Information von verschiedenen Servern abgerufen werden, was sich natürlich negativ auf die Performance auswirkt.
Beziehungen bei Dokumenten (Bild 3)
Quelle: Autor
Sowohl die Performance als auch der Durchsatz sind ausgesprochen wichtige Merkmale von Cosmos. Um den Anforderungen an die Performance gerecht zu werden, unterstützt Cosmos DB weder das Konzept von Joins noch ist es möglich, ­relationale Beschränkungen über Dokumente hinweg zu erzwingen.

Können Relationen überhaupt abgebildet werden?

Nach dem vorangegangenen Abschnitt könnte sich der eine oder andere Leser die Frage stellen, ob Cosmos überhaupt geeignet ist, relationale Beziehungen abzubilden. Dies kann sicherlich mit einem „Ja“ beantwortet werden. Es gibt jedoch einiges zu lernen und zu beachten. Es ist als ein Paradigmenwechsel zu betrachten, da andere Techniken benötigt werden, um Relationen zwischen Entitäten darzustellen. Und auch die meisten Best Practices aus der RDBMS-Welt lassen sich nicht leicht oder gar nicht in die NoSQL-Welt übertragen.

Erstellung eines nicht relationalen
Datenmodells

Für den Einstieg in die NoSQL-Modellierung wird der Einfachheit halber von einem relationalen Datenmodell ausgegangen, welches Bild 4 zeigt.
Relationales Beispielmodell (Bild 4)
Quelle: Autor
Die Entität Customer kann über mehrere Adressen verfügen (Addresses, 1:n). Ein Produkt ist einer Kategorie zuzuordnen (Category, 1:n). Ein Produkt kann über mehrere Tags (ProductTag) verfügen und ein ProductTag kann von mehreren Produkten verwendet werden. Diese n:n-Beziehung wird durch die Junction Table ProductTags umgesetzt, sie enthält die Fremdschlüssel zu den anderen beiden Tabellen.

Tabellen und Container

Im Vorangegangenen sind Cosmos-Container bereits zur Sprache gekommen. Nun könnte man vielleicht auf die Idee kommen, je Tabelle einen eigenen Container zu erstellen. Was möglich wäre – aber sollte dies auch so getan werden? Die Antwort darauf lautet, dass dies sicherlich der schlechteste Ansatz überhaupt wäre.
Der Grund dafür wurde ebenfalls bereits angesprochen: Cosmos ist horizontal skalierbar und nicht relational. Das heißt, die JSON-Dokumente, die in Beziehung zueinander stehen, wären auf verschiedene physische Rechner verteilt, und dann würde es schwierig, diese Daten zu joinen beziehungsweise mit Einschränkungen zu versehen. Es wäre mit Performanceeinbußen zu rechnen, und auch die Pflege wäre sehr schwierig.

Die Modellierung

Bei der Modellierung bilden wir im ersten Schritt die Tabellen Customer und Address in JSON-Dokumenten ab, was Bild 5 zeigt. Es besteht eine Relation zwischen diesen beiden Tabellen. Die Namen der Attribute wurden in Lower-Camel-Case-Notation übernommen, mit Ausnahme der rot markierten Primary-Key-Attribute. Diese wurden in id umbenannt, was dem Standard in Cosmos entspricht.
Tabellen in JSON abgebildet (Bild 5)
Quelle: Autor
Ein besonderes Augenmerk soll auf die übertragene Fremdschlüsselbeziehung gelegt werden (Bild 6). Bei der Namensgebung wird in einem solchen Fall stattdessen der Begriff „Referenz“ verwendet, da Cosmos die Datenintegrität nicht berücksichtigt [3].
Foreign Keys als Referenzen (Bild 6)
Quelle: Autor
Ergänzend zu den Fremdschlüsselbeziehungen ist noch zu erwähnen, dass bei allen Beziehungen, die zwischen den Dokumenten existieren, diese als Weak Reference zu betrachten sind und nicht von der Datenbank überprüft werden. Das heißt, wenn es zwingend ist, dass die aktuell referenzierten Daten existieren, muss dies durch die Anwendung behandelt werden. Hierzu stehen Trigger oder Stored Procedures zur Verfügung.

Einbetten oder referenzieren?

In JSON-Dokumenten können Informationen eingebettet werden, wie es Bild 7 zeigt, wo eine Liste von Adressen ein Bestandteil der Entität Customer ist. Es stellt sich dann natürlich die Frage, wann die Daten eingebettet und wann sie besser referenziert werden sollten. Sowohl in [3] als auch in [4] lautet die generelle Empfehlung, sich wenn irgend möglich für die Einbettung der Daten zu entscheiden.
Eingebettete Informationen in JSON (Bild 7)
Quelle: Autor
Konkret sollten die Daten unter den folgenden Bedingungen eingebettet werden:
  • Bei den verknüpften Daten ist mit wenigen Änderungen zu rechnen.
  • Es handelt sich um eine Beziehung, bei der eine Entität ein Bestandteil einer anderen Entität ist.
  • Das zu erwartende Wachstum der eingebetteten Daten ist begrenzt.
  • Die eingebetteten Daten werden häufig gemeinsam mit abgefragt.
  • Es handelt sich um eine „One-to-few“-Beziehung.
Für das Referenzieren von Entitäten sprechen dagegen unter anderem folgende Argumente:
  • Es sind sehr viele Entitäten, die eingebettet werden sollen. Zum Beispiel können zu einem Blog-Eintrag sehr viele Kommentare hinzugefügt werden. Ein geeigneter Ansatz für solche Fälle wäre beispielsweise einer, bei dem zum Beispiel die ersten drei Kommentare eingebettet sind und die restlichen referenziert werden.
  • Die eingebettete Entität wird sich sehr oft ändern. Ist diese sehr häufig in JSON-Dokumente eingebettet, müssen bei einer Änderung alle eingebetteten Entitäten gefunden und aktualisiert werden.

Physische und logische Partitionen

Im Abschnitt „Horizontale Skalierbarkeit“ wurden bereits die physischen Rechner erwähnt, die sich unterhalb des Containers befinden. Die von der Applikation geschriebenen JSON-Dokumente landen schlussendlich auf einem der physischen Rechner. Diese werden als physische Partitionen bezeichnet (auch gelegentlich unter dem Begriff physikalische Partitionen zu finden). Da liegt die Frage nahe, wie Cosmos DB die Entscheidung fällt, in welcher physischen Partition ein JSON-Dokument abgelegt werden soll.
Hier kommen die logischen Partitionen ins Spiel. Auf einer physischen Partition werden logische Partitionen abgelegt. Logische Partitionen werden aufgrund des Wertes des Partitionsschlüssels erstellt, der bei der Modellierung durch ein Attribut in dem JSON-Dokument angegeben wird.
JSON-Dokumente, die denselben logischen Partitionsschlüssel-Wert aufweisen, werden in einer logischen Parti­tion zusammengefasst.
Cosmos DB sorgt dafür, dass alle Dokumente, die den gleichen Wert im Partition-Key haben, auch auf dem gleichen physischen Server abgelegt sind, was sich auf die Performance beim Zugriff natürlich positiv auswirkt.
Es ist sinnvoll, sich auf die logischen Partitionen zu konzentrieren, da es im Verantwortungsbereich von Cosmos DB liegt, wie diese auf den physischen Partitionen verteilt werden. Das Partition-Key-Attribut, dessen Wert für die Erstellung der logischen Partitionen verwendet wird, wird beim Anlegen des Conainers definiert.
Bild 8 zeigt dies im Cosmos Emulator, der unter [5] heruntergeladen werden kann.
Einen Container mit einem Partition-Key anlegen (Bild 8)
Quelle: Autor
In Bild 9 sind die drei logischen Partitionen zu sehen. In der logischen Partition Mayer sind alle Dokumente enthalten, bei denen das Partition-Key-Attribut lastname mit dem Wert Mayer befüllt ist. Gleiches gilt auch für die zwei anderen logischen Partitionen Huber und Mustermann.
Gruppierte logische
Partitionen (Bild 9)
Quelle: Autor
Es ist zu berücksichtigen, dass für die Dokumente eine maximale Größe von 2 MB gilt, was auch bei der Entscheidung, ob Entitäten eingebettet oder referenziert werden, von Bedeutung ist. Je logische Partition gilt eine Obergrenze von 20 GB, was auch der maximalen Storage-Kapazität in Azure entspricht. Somit gilt für die Wahl des logischen Partitionsschlüssels, das heißt das Attribut des JSON-Dokuments, eines zu wählen, das ein großes Spektrum an unterschiedlichen Werten bietet. Hierdurch werden viele logische Partitionen erstellt, was wiederum Cosmos ermöglicht, diese logischen Partitionen möglichst effizient auf den physischen Partitionen zu verteilen und somit gut zu skalieren.

Hot Partitions

Bei der Wahl des Partitionsschlüssels ist Sorgfalt gefragt. Es gilt den Zustand aus Bild 10 zu vermeiden. Die linke Partition ist überfüllt mit Dokumenten und unter Umständen sehr groß, wodurch es für Cosmos schwieriger wird, diese auf den physischen Partitionen zu verteilen. Eine derartige Partition wird auch als „Hot Partition“ bezeichnet.
Hot
Partitions (Bild 10)
Quelle: Autor
Die für den Container bereitgestellten physischen Partitionen können somit nur schlecht genutzt werden. Es ist für Cosmos einfacher, kleinere logische Partitionen auf den physischen Partitionen zu verteilen.
Wird als Partition-Key beispielsweise das aktuelle Tagesdatum gewählt, kann dies zur Folge haben, dass sowohl Lese- als auch Schreibzugriffe sich auf eine Partition beschränken. Dies hat möglicherweise keinen negativen Einfluss bezüglich des Speichers, jedoch bezüglich des Durchsatzes, da die Zugriffe nicht auf verschiedene Partitionen verteilt werden können, die Last konzentriert sich lediglich auf eine physische Partition. Aus diesem Grund ist der optimale Partition-Key derjenige, der zu einer möglichst einheitlichen Verteilung von Speicher und Durchsatz führt.

Single- und Cross-Partition-Abfragen

Bezieht sich der Partition-Key auf zum Beispiel den Vornamen und wird eine Suche mit einem Filter für einen Vornamen abgesetzt, ist es für Cosmos klar, auf welcher logischen Partition die Dokumente zu suchen sind, und somit ist es ebenfalls klar, auf welcher physischen Partition die Daten abgelegt wurden. Der Zugriff kann sehr schnell erfolgen. In diesem Fall ist die Rede von einer Single-Partition-Abfrage.
Etwas anderes ist es, wenn nach einem Attribut gesucht wird, für welches kein Partition-Key definiert wurde. Dann ist Cosmos gezwungen, die Suche über alle Partitionen auszuführen, was den Vorgang sehr langsam und aufwendig macht. Bei der Modellierung sollten solche Cross-Partition-Abfragen daher so weit wie möglich vermieden und nur auf das absolut Notwendige beschränkt werden.

Geeigneten Partition-Key wählen

Nach dem bisher Gesagten dürfte klar sein, dass die Wahl des Partitionsschlüssels wohl überlegt sein will. Er ist zu wählen in Abhängigkeit davon, nach welchem Attribut des Dokuments hauptsächlich gesucht wird. Wird zum Beispiel häufig nach dem Nachnamen gesucht, wie in den bisher gezeigten Beispielen, bietet es sich an, diesen als Partition-Key zu definieren. Führt das gewählte Partition-Key-Attribut dazu, dass sehr viele logische Partitionen erzeugt werden, ist dies nicht von Nachteil, es bedeutet nicht, dass hierdurch die gleiche Menge an physischen Partitionen erstellt wird. Cosmos DB wird hingegen eher in die Situation gebracht, die logischen Partitionen sinnvoll auf den physischen Partitionen zu verteilen.

Kosten

Bei der Ermittlung der Kosten wird man mit der Einheit RU konfrontiert, was für „Request Unit“ [6], zu Deutsch Anforderungseinheit, steht. Request Units stellen die Währung für die beanspruchten Systemressourcen wie CPU, IOPS und Arbeitsspeicher dar, die für die Ausführung der Cosmos-Datenbankvorgänge erforderlich sind. So belaufen sich die Kosten für einen Punktlesevorgang, das heißt den Abruf eines einzelnen Elements mit der Größe von 1 KB anhand seiner ID und des Partition-Keys, auf 1 RU.
Die Kosten werden unabhängig vom Datenbankvorgang immer in RU ausgedrückt, also ungeachtet dessen, ob es sich um einen Schreib-, Lese- oder Abfragevorgang handelt.
Um eine gute Planung der benötigten Ressourcen zu ermöglichen, stellt Cosmos sicher, dass die Anzahl von RUs für einen dedizierten Datenbankvorgang und einen bestimmten Datensatz immer deterministisch ist. Der Response-Header transportiert die Information der benötigten RUs.
Wie die verbrauchten RUs in Rechnung gestellt werden, hängt von der Wahl der Bereitstellung ab. Zur Auswahl stehen die folgenden drei Möglichkeiten:
  • Bereitgestellter Durchsatz. Die Anzahl von RUs wird auf Sekundenbasis bereitgestellt. Die Schrittlänge beträgt 100 RUs pro Sekunde. Die Skalierung des Durchsatzes kann jederzeit in Schritten von 100 RUs erhöht oder verringert werden. Die Abrechnung der bereitgestellten RUs pro Sekunde erfolgt auf Stundenbasis.
  • Serverless Mode. Im serverlosen Modus brauchen Sie beim Erstellen von Containern keinen bereitgestellten Durchsatz zu konfigurieren. Die von den Datenbankvorgängen genutzten RUs werden am Monatsende in Rechnung gestellt.
  • Autoscale Mode. Der Durchsatz (RUs) der Datenbank beziehungsweise des Containers wird automatisch und sofort entsprechend der Nutzung skaliert, ohne dass die Verfügbarkeit, die Latenz oder der Durchsatz den Workload beeinträchtigen. Für unternehmenskritische Workloads, die mit einer nicht vorhersehbaren Last auftreten, ist dieser Modus geeignet.

Fazit

Cosmos DB ist eine interessante Lösung eines Datenbanksystems in der Cloud, das mit Lastspitzen aufgrund der horizontalen Skalierung gut zurechtkommt.
Dokumente
Artikel als PDF herunterladen
Fußnoten
  1. Types of Databases on Azure
  2. Pramod Sadalage, NoSQL Distilled: A Brief Guide to the Emerging World of Polyglot Persistence, Addison-Wesley, 2013, ISBN 978-0-321-82662-6
  3. Azure Cosmos DB | Mongo DB – Embedding vs Reference
  4. Data modeling in Azure Cosmos DB
  5. Cosmos DB Emulator Download
  6. Anforderungseinheiten in Azure Cosmos DB


    Das könnte Sie auch interessieren