Installation des Couchbase Servers

Die Installation des Couchbase Servers, eines Dokumenten-orientierten Datenbanksystems, ist durch die Verwendung eines Docker-Images einfach durchzuführen. 

Dazu wird das zugehörige offizielle Image vom Docker Hub geladen:

docker pull couchbase

Als Datenbank-Server muss der daraus erzeugte Container natürlich durch Port-Mapping den Zugriff auf die internen Server-Endpoints gewährleisten. Sollen die Daten einen Upgrade des Containers überstehen, was in den allermeisten Fällen gewünscht ist, so wird ein Volume oder einen Mount verwendet:

docker run -d -p 8091-8093:8091-8093 -p 11210:11210 --name db -v couchbase_volume:/opt/couchbase/var couchbase

Beim ersten Aufruf der Web Konsole über 

http://localhost:8091

kann die Installation angeschlossen werden. Hierbei werden der Clustername sowie der Administrator-Account angelegt. Der Couchbase Server ist damit einsatzbereit.

Die Couchbase Web Console

 


Seminare zum Thema

Weiterlesen

Dokumenten-orientierte Datenbanksysteme

Dokumenten-orientierte Datenbanksysteme sind im Rahmen der  NoSQL-Bewegung entstanden und haben sich in den letzten Jahren zu stabilen und etablierten Produkten entwickelt. Im aktuellen Ranking von Datenbanksystemen steht mit der kommerziellen MongoDB ein Document-Store unter den Top 5, aber auch die in einer vollwertigen Community-Edition vorliegende Couchbase ist, wenn auch deutlich schwächer, vertreten.  

Was sind Dokumente?

Ein Dokument ist eine Datenstruktur, die ähnlich wie eine Datenbank-Tabelle einem definierten Schema genügt. Allerdings wird dieses Schema von der Datenbank in der Regel nicht über Constraints beim Schreiben des Datensatzes geprüft (“Schema on write”), sondern erst bei Abfragen (“Schema on read”):  Dokumente, die der Query entsprechen, werden in die Treffermenge aufgenommen, nicht-passende Dokumente eben nicht. Eine Dokumenten-orientierte Datenbank benötigt deshalb nicht unbedingt verschiedene Tabellen-Definitionen, sondern kann alle Dokumente in einer einzigen Collection oder einem “Bucket” ablegen.

So können beispielsweise in einer Datenbank sämtliche Dokumente einer Reiseagentur (Fluglinien, Flughäfen, aber auch Flugpläne und Routen) gemeinsam abgelegt werden.

Dokumente werden über eine innerhalb der Datenbank eindeutigen Document-ID identifiziert.

Links versus Joins

Im Gegensatz zu einem relationalen Modell unterstützen Dokumente Server-seitige Joins nicht unbedingt. Es ist eher üblich, Dokumente zu Verlinken und damit im Endeffekt dem Client das Nachladen von Assoziationen zu überlassen. 

Dokumenten-Formate

Als Quasi-Standard für das Format von Dokumenten hat sich JSON herauskristallisiert. Dies ist einesteils etwas überraschend, da JSON bis heute keinen wirklichen Standard für Links definiert hat. Hier ist XML klar überlegen. Andererseits werden Dokumente sehr häufig im Rahmen einer RESTful Architektur benutzt, so dass in der Praxis als Implementierung http genutzt wird. Und dafür ist JSON die natürliche Wahl.

Clusterbetrieb

Dokumenten-orientierte Datenbanksysteme sind immer auf einen Cluster-Betrieb ausgerichtet. Das ergibt sich klar aus dem Bezug zur NoSQL-Bewegung und damit dem “Big Data”-Umfeld. 

Für die Umsetzung eines dynamisch skalierenden Clusters bieten sich zwei Strategien an:

  • Sharding: Hier werden die Dokumente auf Grund eines Sharding Keys, der nicht unbedingt der Document-ID entsprechen muss, auf die verschiedene Knoten des Clusters verteilt. Ein zentraler Master oder Router nimmt alle Anfragen entgegen und verteilt diese dann an Hand von Konfigurations-Informationen auf die Knoten, die die Daten enthalten. Die MongoDB ist ein Beispiel für diese Cluster-Architektur. Die folgende Abbildung entstammt der Dokumentation unter https://docs.mongodb.com/manual/core/sharded-cluster-components/:
    Ein einfacher Mongo Cluster
     
  • Ring Cluster: Hier wird auf den Master und den Konfigurationsserver verzichtet; jeder Knoten des Clusters ist gleichberechtigt. Ein Beispiel hierfür ist der Couchbase Server, siehe https://docs.couchbase.com/server/5.0/architecture/architecture-intro.html:
    Couchbase Cluster

 


Seminare zum Thema

Weiterlesen

Eine Spring Data Anwendung mit einem Apache Cassandra Backend

Eine Spring Data Anwendung mit einem Apache Cassandra Backend ist recht einfach zu erstellen, da Spring Data hierfür ein eigenes Subprojekt etabliert hat. Weiterhin gibt es selbstverständlich einen Embedded Cassandra Server als Bestandteil von Spring Boot. Ein externer Server ist kann über einen Docker-Container realisiert werden, das offizielle Docker-Image liegt auf dem Docker Hub.  

Cassandra im Docker-Container

Für eine einfache Test-Anwendung wird eine einzelne Cassandra-Instanz über den Aufruf 

docker run --rm --name cassandra -p 9142:9142 cassandra

gestartet. Damit läuft ein einzelner Cassandra-Knoten.

Nach dem Starten des Containers kann eine Konsole im Container geöffnet werden, mit deren Hilfe die Cassandra Shell cqlsh geöffnet werden kann:

docker exec --it cassandra /bin/bash

cqlsh

Damit kann dann für das folgende Beispiel nötige KeySpace sowie die Tabelle angelegt werden:

CREATE KEYSPACE PUBLISHING_KEYSPACE
WITH REPLICATION = {
'class': 'SimpleStrategy',
'replication_factor': 1
};

CREATE TABLE PUBLISHING(
publisher_name text,
title text,
isbn text,
price double,
pages int,
available boolean,
primary key(publisher_name, title, pages)
);

 

Die Spring Boot Anwendung

Wie üblich ist der Einstiegspunkt in die Spring Boot-Anwendung die pom.xml. Diese definiert neben dem Spring Boot Parent den Starter für Cassandra:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>org.javacream.training</groupId>
<artifactId>org.javacream.training.apache.cassandra.springboot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>org.javacream.training.apache.cassandra.springboot</name>
<description>Cassandra project for Spring Boot</description>

<properties>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-cassandra</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

Damit ist die Konfiguration der Anbindung an den im Docker-Container laufenden Datenbankserver einfach:

@EnableCassandraRepositories
@Configuration
public class PublishingConfiguration

}

und die application.properties:

spring.data.cassandra.port=9142
spring.data.cassandra.keyspace-name=PUBLISHING_KEYSPACE

Entity und Repository

Die in der Cassandra-Datenbank abzulegende Datenstruktur wird über Annotationen gemapped:

@Table
public class Book {

@PrimaryKeyColumn(name = "publisher_name", ordinal = 0, type = PrimaryKeyType.PARTITIONED)
private String publisherName;
@PrimaryKeyColumn(name = "pages", ordinal = 2, type = PrimaryKeyType.CLUSTERED)
private int pages;
private double price;
private String isbn;
private boolean available;
@PrimaryKeyColumn(name = "title", ordinal = 1, type = PrimaryKeyType.CLUSTERED)
private String title;
//getter, setter, hashCode, equals, toString

}

Das Repository selbst ist dann eine einfache Schnittstellen-Erweiterung des CassandraRepository:

@Repository
public interface PublishingRepository extends CassandraRepository<Book, String>{

}

Die Anwendung

Die Anwendung selber schreibt nun nur 4 Bücher in die Datenbank:

@Component
public class PublishingApplication {

@Autowired private PublishingRepository publishingRepository;

@PostConstruct public void startApplication() {
Book cassandraInAction1 = new Book("Manning", 100, 19.99, "ISBN-1", false, "Cassandra in Action");
publishingRepository.save(cassandraInAction1);
Book cassandraInAction2 = new Book("Manning", 200, 29.99, "ISBN-2", true, "Cassandra in Action");
publishingRepository.save(cassandraInAction2);
Book cassandraInAction3 = new Book("Manning", 500, 39.99, "ISBN-3", true, "Cassandra in Action");
publishingRepository.save(cassandraInAction3);
Book java = new Book("Manning", 5000, 9.99, "ISBN-4", true, "Java");
publishingRepository.save(java);
}
}

Die geschriebenen Daten können nun einfach über die CQL-Shell abgefragt werden:

cqlsh:publishing_keyspace> select * from publishing where publisher_name='Manning' ;

publisher_name | title | pages | available | isbn | price
----------------+---------------------+-------+-----------+--------+-------
Manning | Cassandra in Action | 100 | False | ISBN-1 | 19.99
Manning | Cassandra in Action | 200 | True | ISBN-2 | 29.99
Manning | Cassandra in Action | 500 | True | ISBN-3 | 39.99
Manning | Java | 5000 | True | ISBN-4 | 9.99

(4 rows)

Oder mit einer zusätzlichen Selektion auf den Titel:

cqlsh:publishing_keyspace> select * from publishing where publisher_name='Manning' and title = 'Cassandra in Action';

publisher_name | title | pages | available | isbn | price
----------------+---------------------+-------+-----------+--------+-------
Manning | Cassandra in Action | 100 | False | ISBN-1 | 19.99
Manning | Cassandra in Action | 200 | True | ISBN-2 | 29.99
Manning | Cassandra in Action | 500 | True | ISBN-3 | 39.99

(3 rows)

Und schließlich noch auf die Seiten:

cqlsh:publishing_keyspace> select * from publishing where publisher_name='Manning' and title = 'Cassandra in Action' and pages = 200;

publisher_name | title | pages | available | isbn | price
----------------+---------------------+-------+-----------+--------+-------
Manning | Cassandra in Action | 200 | True | ISBN-2 | 29.99

(1 rows)

Die folgende Abfrage führt nun jedoch zu keinem sinnvollen Ergebnis:

cqlsh:publishing_keyspace> select * from publishing where pages = 200;
InvalidRequest: Error from server: code=2200 [Invalid query] message="PRIMARY KEY column "pages" cannot be restricted as preceding column "title" is not restricted"

 


Seminare zum Thema

Weiterlesen

Apache Cassandra

Teil 1: Das Datenbanksystem

Apache Cassandra ist ein horizontal skalierendes, Master-loses Datenbanksystem, das durch einen Ring-Cluster realisiert wird. Doch was genau bedeutet dies?

Der Cluster

  • Wie bereits in einem vorherigen Artikel einer allgemeinen Einführung zum Thema NoSQL beschrieben, bedeutet horizontale Skalierung, dass beim Auftreten eines Engpasses bezüglich CPU, Speicher oder Storage einfach ein weiterer Knoten in einen Cluster hinzugefügt werden kann. Dieser kann nach kürzester Zeit Last übernehmen und somit den Engpass beheben.
    Ein horizontaler Cluster

     

  • In einem Master-losen Ring-Cluster sind alle Knoten des Systems gleichberechtigt: Jeder Knoten kann Anfragen eines Clients entgegen nehmen und entweder direkt selber verarbeiten oder an einen anderen Knoten delegieren. Solange von einem Client aus auch nur ein Knoten im Netz sichtbar ist, wird der Cluster funktionieren und antworten.

 

 

Der Ring-Cluster

 

Ob diese Antwort konsistent ist, hängt von der internen Kommunikation der Knoten im Ring ab. Dies ist die Aussage des bekannten CAP-Theorems.  

Replikation

Im Ring-Cluster von Cassandra wird ein Datensatz auf jeweils einem Knoten gehalten. Um bei einem Ausfall eines Cluster-Knotens Informations-Verlust zu verhindern, werden Datensätze auf mehrere Knoten repliziert. Dies wird durch den Replikationsfaktor RF beschrieben. So werden im folgenden Bild die Daten mit RF=3 abgelegt:

 

 

BASE

Schreibt ein Client Daten in den Ring-Cluster und liest beispielsweise ein anderer Client kurz danach die Daten, so können Inkonsistenzen auftreten. Es dauert eine gewisse Zeit, bis alle beteiligten Knoten des Rings alle Datenänderungen mitbekommen haben. Ist so ein Zeitfenster der Inkonsistenz fachlich möglich, so spricht man von einer BASE-Architektur, in der das E für die “Eventual Consistency” steht. “Eventual” ist hier selbstverständlich mit “letztendlich” zu übersetzen; letztendlich wird ein Ring-Cluster einen konsistenten Zustand erreichen.  

Tunable Consistency

Wird fachlich Konsistenz gefordert, so kann dies in einem Ring-Cluster dadurch erreicht werden, dass die Antwort des Clients so lange verzögert wird, bis die Daten auf allen replizierten Knoten angekommen sind. 

Eine nahe liegende Lösung wäre also, bei einer Schreib-Operation W zu verlangen, dass RF Knoten die Daten geschrieben haben. 

Dann kann ein Lesevorgang von einem beliebigen Knoten ausgeführt werden.

Also insgesamt:

W = RF und R = 1

Aber auch “anders herum” wird Konsistenz erreicht:

W = 1 und R = RF

Auf diese Art und Weise kann ausbalanciert werden, ob Wert auf schnelle Schreib- oder auf schnelle Lese-Vorgänge gelegt werden soll. Dies wird als  “Tunable Consistency” bezeichnet.

Beide Verfahren haben jedoch den Nachteil, dass bei einem Partitionsfehler, also einem Ausfall der Kommunikation zwischen Knoten, der gesamte Ring-Cluster in einem Standby-Modus geschaltet werden muss und keine Antwort liefern kann. Dies kann vermieden werden, in dem für Schreib und Lesevorgänge das Quorum benutzt wird:

W = R = (RF + 1)/2

Auch hier ist Konsistenz garantiert, allerdings muss nur noch in die Majorität der Knoten geschrieben werden.


Seminare zum Thema

Weiterlesen

Datenmodellierung für Apache Cassandra

Cassandra ist eine Datenbank, die nicht eindeutig in die 5 Kategorien der NoSQL-Datenbanksysteme eingeordnet werden kann: Einesteils ist sie Spalten-orientiert, andererseits wird für den effizienten Zugriff auf Daten ein “Partition Key” benötigt, also ein eindeutiger Primärschlüssel. Dies entspricht damit eher einem Key-Value-Store. Die Datenmodellierung für Apache Cassandra muss diese Besonderheiten berücksichtigen, um eine effiziente Datenverarbeitung ermöglichen zu können.

CQL – Die Cassandra Query Language

Um die Datenmodellierung mit Cassandra zu erlernen scheint es in erster Näherung sinnvoll, sich mit der Abfragesprache CQL zu beschäftigen. Diese Sprache wird wie SQL sowohl zur Struktur-Definition als auch für lesende und schreibende Zugriffe benutzt. 

Ein erster naiver Eindruck von CQL scheint nun zu zeigen, dass CQL und SQL sehr viel Analogien aufweisen. Es gibt ein CREATE TABLE ebenso wie ein SELECT FROM mit einer fast identischen Parametrisierung. 

Allerdings zeigt schon eine erste konkrete Umsetzung, dass diese Analogie äußerst beschränkt ist.

So muss zu allererst ein so genannter Keyspace eingerichtet werden, der in jedem Falle einen Replikationsfaktor definiert.

CREATE KEYSPACE PUBLISHING
WITH REPLICATION = {
'class': 'SimpleStrategy',
'replication_factor': 1
};

 

Dies ist in der Ring-Cluster-Architektur von Cassandra begründet, die eine Ausfallsicherheit durch Daten-Replikation über Knoten hinweg realisiert.

Das Anlegen einer Tabelle erfolgt dann scheinbar analog zu SQL. So wird im Folgenden eine Tabelle PUBLISHING angelegt, deren Intention es sein soll, einem Verleger Bücher zuzuordnen:

CREATE TABLE PUBLISHING(
publisher_name text,
title text,
isbn text,
price double,
pages int,
available boolean,
primary key(publisher_name, title)
);

Aber eine etwas genauere Analyse zeigt, dass diese Tabelle einen komplett anderen Aufbau hat als bei einem relationalen Modell. So würde ja in einem relationalen Datenbanksystem wohl eine Tabelle PUBLISHERS über einen Fremdschlüssel mit der Tabelle BOOKS verknüpft werden. 

Was ist nun aber der Sinn der obigen Tabellen-Definition:

  1. Der angegebene Primärschlüssel ist komplett etwas anderes. Der erste Teil, hier der publisher_name, ist der so genannte Partition Key. Cassandra verteilt die Datensätze an Hand des Partition Keys auf die Knoten des Clusters. Damit müssen effiziente Abfragen stets mit einer einleitenden WHERE <PARTITION_KEY> = <VALUE> eingeleitet werden. Ohne diese zentrale Selektion muss der Cluster die Abfrage auf allen Knoten des Rings ausführen, also eine Art “Full Table Scan” ausführen, was nicht sonderlich effizient ist.
  2. Der zweite Teil des Primary Key ist eine Liste von so genannten “Clustering Columns”.  Nur mit Hilfe einer Clustering Column kann ergänzend zum Partition Key die Abfrage erweitert werden. So kann in obigem Beispiel die Where-Klausel geschrieben werden als WHERE pulisher_name = 'Manning' and title = 'Cassandra in Action'. Eine weitere Selektion, also beispielsweise AND price = 19.99 ist nicht möglich. 

Die zweite obige Anmerkung zeigt den Spalten-orientierten Charakter von Cassandra. Die Clustering Column title ist nämlich ein Key in einer Map, dessen Value dann den zusätzlichen Buch-Informationen entspricht. Dieses Value wird von Cassandra aber als unstrukturiertes Dokument angenommen und wird deshalb in effizienten Abfragen nicht benutzt. 

Was wäre aber die Konsequenz, wenn eine weitere Clustering Column, also beispielsweise die pages mit in den Primary Key aufgenommen werden würden: 

primary key(publisher_name, title)

Fangen wir mit der Spalten-orientierten Arbeitsweise an: Die über den Partition Key definierte Map enthält als Schlüssel beispielsweise den Titel  'Cassandra in Action' . Das zugehörige Value ist nun aber wiederum eine Map! Und diese enthält für jeden Key-Eintrag der Seiten wiederum die restlichen Informationen des Buchs. Oder anders formuliert: Die PUBLISHING-Tabelle enthält für jeden Buch-Titel eine Liste von Seiten, die dann jeweils die ISBN, den Preis sowie die Verfügbarkeit enthält. 

Eine Abfrage der Form WHERE publisher_name = 'Manning' AND title = 'Cassandra in Action' AND pages = 500 ist vollkommen korrekt, ebenso title = 'Cassandra in Action'. Falsch hingegen wäre  WHERE publisher_name = 'Manning' AND pages = 500: Hier wird in der Navigation über die verschachtelten Maps eine Ebene ausgelassen, so dass wiederum eine sehr ineffiziente Abfrage resultieren würde.

Query First

Wir sehen damit, dass bei der Datenmodellierung mit Cassandra der Ausgangspunkt nicht das statische Datenmodell sein muss, sondern die auf den Datenbestand auszuführenden Abfragen. Dies wird als “Query First”-Ansatz bezeichnet.

Notwendige Konsequenz von Query First ist eine komplette Denormalisierung des Datenbestandes. Sollen verschiedene Kombinationen von Abfrage-Kriterien unterstützt werden so muss in Cassandra jeweils eine eigene Tabelle angelegt werden, so dass Daten redundant abgelegt werden müssen. 

Moderne Cassandra-Versionen

Die durch den “Query First”-Ansatz entstandenen Daten-Redundanzen sind für viele Modellierer schwer zu akzeptieren. Die Cassandra-Community hat deshalb in den letzten Jahren immer mehr Features und Optimierungen eingeführt, die dieses Problem lösen sollen. So können beispielsweise durch Indizes sehr wohl auch Abfragen bis in die Ebene der Value-Dokumente durchgeführt werden. Weiterhin führen Abfragen ohne die Angaben des Partition Key “nur” zu einer Warnung. Trotzdem müssen für einen sinnvollen Einsatz des Apache Cassandra-Datenbanksystems auch in den neuesten Versionen die technische Grundlagen sicher beherrscht und berücksichtigt werden. Sonst ist eine Ergänzung oder sogar Ablösung eines relationalen Datenbanksystems durch Cassandra sinnlos bzw. sogar  nachteilig. 


Seminare zum Thema

Weiterlesen