Cassandra Einordnung
- Open-Source-Datenbank der Apache Software Foundation
- Ursprünglich von Facebook entwickelt (2008), 2009 Open Source
- Kombiniert Ideen aus Amazon Dynamo (Replikation, Peer-to-Peer) und Google Bigtable (Wide Column)
- Kategorie: Wide Column Store (auch: Wide-Column-Family-Store)
- Ausgelegt auf extreme Schreiblast, hohe Verfügbarkeit, lineare Skalierbarkeit
- Kein Single Point of Failure – vollständig masterlos
Wide Column Store Konzept
Vergleich der Modelle
- Relational: festes Schema, Zeilen mit gleichen Spalten, Joins
- Key-Value: Schlüssel → opaker Wert, keine Struktur
- Wide Column: Schlüssel → Menge von typisierten Spalten, pro Row variabel
Eigenschaften
- Sparse Data: fehlende Spalten belegen keinen Speicher
- Spalten innerhalb einer Row sortiert gespeichert
- Sehr effizient für Zeitreihendaten und Aggregationen über Spalten
Datenmodell: Keyspace
- Keyspace = Äquivalent zu einer Datenbank in relationalen Systemen
- Enthält mehrere Tabellen
- Replikationsfaktor (RF): Anzahl der Kopien jedes Datensatzes
- Replikationsstrategie bestimmt, auf welche Nodes Daten verteilt werden
Strategien
- SimpleStrategy: für Single-Datacenter, nächste Nodes im Ring
- NetworkTopologyStrategy: für Multi-Datacenter, RF pro Datacenter konfigurierbar
CREATE KEYSPACE sensor_data
WITH replication = {
'class': 'NetworkTopologyStrategy',
'dc1': 3,
'dc2': 2
};
Datenmodell: Tabelle und Partition
- Tabelle: Schema mit festen Spaltendefinitionen
- Partition: Gruppe von Rows mit gleichem Partition Key → physische Speichereinheit
- Partition Key: bestimmt, auf welchem Node Daten liegen
- Clustering Key: Sortierung der Rows innerhalb einer Partition
- Columns: reguläre Datenspalten (typisiert)
Partition Key
- Bestimmt die physische Verteilung im Cluster
- Hash-Funktion (Murmur3) → Token → Node-Zuweisung im Ring
- Alle Rows einer Partition liegen auf denselben Nodes
- Abfragen ohne Partition Key erfordern Cluster-Scan (ineffizient)
Hot Partitions vermeiden
- Schlecht:
date als einziger Partition Key (alle Writes heute → ein Node)
- Besser:
(sensor_id, date) → gleichmäßige Verteilung
- Ziel: Partitionen möglichst gleich groß, max. ~100 MB empfohlen
Clustering Key
- Sekundärer Teil des Primary Key:
PRIMARY KEY (partition_key, clustering_key)
- Definiert die Sortierreihenfolge der Rows innerhalb einer Partition
- Ermöglicht effiziente Bereichsabfragen (BETWEEN, <, >, ORDER BY)
- Mehrere Clustering Keys möglich (Compound Primary Key)
- Sortierrichtung pro Clustering Key konfigurierbar (ASC / DESC)
CREATE TABLE messwerte (
sensor_id TEXT,
zeitpunkt TIMESTAMP,
temperatur DOUBLE,
PRIMARY KEY (sensor_id, zeitpunkt)
) WITH CLUSTERING ORDER BY
(zeitpunkt DESC);
Ergebnis
sensor_id | zeitpunkt | temperatur
-----------+---------------------+-----------
s-001 | 2024-06-01 12:00:00 | 23.4
s-001 | 2024-06-01 11:00:00 | 22.8
s-001 | 2024-06-01 10:00:00 | 22.1
CQL: Tabelle anlegen
Keyspace erstellen
CREATE KEYSPACE mein_ks
WITH replication = {
'class': 'SimpleStrategy',
'replication_factor': 3
};
Tabelle erstellen
CREATE TABLE mein_ks.benutzer (
benutzer_id UUID,
nachname TEXT,
vorname TEXT,
email TEXT,
erstellt TIMESTAMP,
PRIMARY KEY (benutzer_id)
);
Compound Primary Key
CREATE TABLE mein_ks.bestellungen (
kunden_id UUID,
bestell_ts TIMESTAMP,
produkt_id UUID,
menge INT,
preis DECIMAL,
PRIMARY KEY (kunden_id, bestell_ts)
) WITH CLUSTERING ORDER BY
(bestell_ts DESC);
Hinweise
- CQL (Cassandra Query Language) ist SQL-ähnlich
- Keine Fremdschlüssel, keine Joins
CQL: Schreiben und Lesen
Schreiben
INSERT INTO messwerte
(sensor_id, zeitpunkt, temperatur)
VALUES
('s-001', toTimestamp(now()), 23.5);
UPDATE messwerte
SET temperatur = 24.0
WHERE sensor_id = 's-001'
AND zeitpunkt = '2024-06-01 12:00:00';
DELETE FROM messwerte
WHERE sensor_id = 's-001'
AND zeitpunkt = '2024-06-01 10:00:00';
Lesen
-- Effizient: Partition Key angegeben
SELECT * FROM messwerte
WHERE sensor_id = 's-001';
-- Bereichsabfrage auf Clustering Key
SELECT * FROM messwerte
WHERE sensor_id = 's-001'
AND zeitpunkt > '2024-06-01 00:00:00';
-- Achtung: ALLOW FILTERING → Full Scan
SELECT * FROM messwerte
WHERE temperatur > 25.0
ALLOW FILTERING;
- WHERE ohne Partition Key → ineffizient, Warnung
ALLOW FILTERING explizit erforderlich
Datenmodellierung: Query-First
- Kein Join-Support: Daten müssen bereits in der richtigen Form gespeichert sein
- Kein ad-hoc Querying: beliebige WHERE-Bedingungen nicht möglich ohne ALLOW FILTERING
- Modell an Zugriffsmustern ausrichten: zuerst definieren, welche Abfragen gestellt werden, dann Tabellen entwerfen
- Denormalisierung: Daten werden bewusst dupliziert, um Joins zu vermeiden
- Eine Abfrage = eine Tabelle: für jedes Zugriffsmuster ggf. eigene Tabelle
- Cassandra Data Modeling (link)
Vorgehen
1. Anforderungen erfassen
→ Welche Abfragen werden gestellt?
→ Welche Filter? Welche Sortierung?
2. Zugriffsmuster auflisten
→ GET sensor by ID
→ GET latest readings for sensor
→ GET all sensors in region
3. Tabellen entwerfen
→ Partition Key aus Filterbedingung
→ Clustering Key aus Sortierung
Anti-Pattern:
SELECT * FROM sensoren
WHERE region = 'Berlin'
AND typ = 'Temperatur'
ORDER BY wert DESC
→ Mehrere Nicht-PK-Filter: ALLOW FILTERING
Lösung: eigene Tabelle für diesen Query
CREATE TABLE sensoren_by_region_typ (
region TEXT,
typ TEXT,
wert DOUBLE,
id UUID,
PRIMARY KEY ((region, typ), wert)
) WITH CLUSTERING ORDER BY (wert DESC);
Beispiel Zeitreihendaten
Anforderung
- Sensormesswerte speichern: Temperatur, feuchtigkeitigkeit
- Abfrage: alle Messwerte eines Sensors der letzten Stunde
- Viele Sensoren, hohe Schreibrate (1 Wert/s pro Sensor)
Schema-Design
CREATE TABLE messwerte_stündlich (
sensor_id TEXT,
stunde TEXT, -- 'YYYY-MM-DD-HH'
zeitpunkt TIMESTAMP,
temperatur DOUBLE,
feuchtigkeit DOUBLE,
PRIMARY KEY ((sensor_id, stunde), zeitpunkt)
) WITH CLUSTERING ORDER BY
(zeitpunkt DESC)
AND default_time_to_live = 2592000;
-- 30 Tage TTL
Abfragen
-- Letzte Stunde eines Sensors
SELECT * FROM messwerte_stündlich
WHERE sensor_id = 's-001'
AND stunde = '2024-06-01-12';
-- Einfügen (Batch für Atomarität)
BEGIN BATCH
INSERT INTO messwerte_stündlich
(sensor_id, stunde, zeitpunkt,
temperatur, feuchtigkeit)
VALUES
('s-001', '2024-06-01-12', '2024-06-01 12:05:00', 23.5, 61.2);
APPLY BATCH;
Partitionsgröße kontrollieren
- Compound Partition Key
(sensor_id, stunde) begrenzt Partition auf 3.600 Rows/h
- Verhindert unbegrenzt wachsende Partitionen
Cassandra vs. Relationale DB
Stärken Cassandra
- Lineare horizontale Skalierbarkeit (Schreiben und Lesen)
- Keine Single Point of Failure, masterlose Architektur
- Extrem hohe Schreibdurchsatz (append-only, CommitLog)
- Multi-Datacenter-Replikation nativ unterstützt
- Tunable Consistency: Tradeoff zwischen Konsistenz und Verfügbarkeit
- Ideal für Zeitreihendaten, IoT, hohe Schreiblast
Schwächen Cassandra
- Kein Joins, kein flexibles Querying
- Modellierung komplex (Query-First, Denormalisierung)
- Keine starken Transaktionen über Partitionen (nur Lightweight Transactions)
Stärken Relationale DB
- Volle ACID-Transaktionen über mehrere Tabellen
- Flexible ad-hoc-Abfragen mit Joins und komplexem SQL
- Normalisierung verhindert Datenduplikation
- Starke Konsistenz standardmäßig
- Reifes Ökosystem, breite Tool-Unterstützung
Anwendungsfälle
Typische Einsatzgebiete
- IoT / Zeitreihendaten: Sensordaten, Messwerte, Metriken
- Messaging: Inbox-Verwaltung, Chat-Verläufe (WhatsApp nutzte Cassandra)
- Empfehlungssysteme: User-Activity-Logs, Click-Streams
- Content-Delivery: User-Profile, Session-Daten
- Finanzdaten: Transaktionshistorie, Audit-Logs
Cassandra in der Praxis
- Netflix: Viewing-History, User-Profile (Milliarden von Rows)
- Apple: iCloud-Dienste, über 75.000 Cassandra-Nodes
- Instagram: Direct Messages, Aktivitäts-Feed
- Uber: Fahrthistorie, Geo-Daten
- Discord: Message-Speicherung (Milliarden von Nachrichten)
Managed Angebote
- DataStax Astra DB (DBaaS auf Basis von Cassandra)
- Amazon Keyspaces (Cassandra-kompatibel, serverless)
- Azure Managed Instance for Apache Cassandra