Home Up PDF Prof. Dr. Ingo Claßen
Cassandra - ADBKT

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
  • Schema ist schemagebunden (Spalten definiert), aber Sparse-Speicherung

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

Ring-Architektur

  • Consistent Hashing: Token-Ring verteilt Daten auf Nodes
  • Jeder Node übernimmt einen Token-Bereich im Ring
  • Peer-to-Peer: kein Master, jeder Node gleichwertig
  • Client kann jeden Node als Koordinator kontaktieren
  • Koordinator leitet Anfragen an die zuständigen Nodes weiter

Virtuelle Nodes (Vnodes)

  • Jeder physische Node besitzt mehrere Token-Bereiche (Vnodes)
  • Gleichmäßigere Lastverteilung
  • Einfacheres Hinzufügen / Entfernen von Nodes

Gossip-Protokoll

  • Dezentrales Kommunikationsprotokoll zur Cluster-Koordination
  • Jeder Node tauscht periodisch (1 s) Zustandsinformationen mit 1–3 Peers aus
  • Informationen verbreiten sich exponentiell schnell im Cluster

Inhalte

  • Node-Discovery: neue Nodes werden automatisch bekannt
  • Failure Detection: Phi-Accrual-Algorithmus erkennt ausgefallene Nodes
  • Cluster State: Token-Bereiche, Rack/Datacenter-Zugehörigkeit, Schema-Version

Replikation

  • Replikationsfaktor (RF): Anzahl Kopien jedes Datensatzes
  • Koordinator schreibt auf RF Nodes (Replicas)
  • Replicas werden durch Token-Ring bestimmt (nächste Nodes im Ring)

Strategien

  • SimpleStrategy: nächste RF Nodes im Ring, für Single-DC
  • NetworkTopologyStrategy: RF pro Datacenter, Rack-Awareness

Hinted Handoff

  • Koordinator speichert Write temporär, wenn Replica nicht erreichbar
  • Übertragung erfolgt, sobald der Replica-Node wieder online ist

Konsistenzlevel

Konfigurierbar pro Anfrage (Tunable Consistency)

  • ONE: Antwort von 1 Replica genügt (niedrige Latenz, schwache Konsistenz)
  • QUORUM: Mehrheit (RF/2 + 1) muss antworten
  • ALL: alle Replicas müssen antworten (starke Konsistenz, hohe Latenz)
  • LOCAL_QUORUM: Quorum nur im lokalen Datacenter

Starke Konsistenz durch Tuning

  • Formel: W + R > RF → starke Konsistenz garantiert
  • Beispiel RF = 3: QUORUM Write + QUORUM Read → 2 + 2 > 3 ✓
  • Tradeoff: höhere Konsistenz = höhere Latenz = niedrigere Verfügbarkeit
-- Konsistenzlevel in cqlsh setzen
CONSISTENCY QUORUM;

SELECT * FROM messwerte
WHERE sensor_id = 's-001';

Write Path

  • 1. CommitLog: Write wird sequenziell auf Disk geschrieben (Crash-Recovery)
  • 2. Memtable: Write wird in speicherresidenter Struktur gespeichert
  • 3. SSTable: Memtable wird bei Überschreitung eines Schwellwerts auf Disk gespült (Flush)

Eigenschaften

  • Alle Schreiboperationen sind append-only und sequenziell → sehr schnell
  • Kein direktes Überschreiben von Daten auf Disk
  • Updates erzeugen neue Versionen (Timestamp-basiert)
  • Deletes schreiben Tombstones (Löschmarkierungen)

Read Path

  • 1. Memtable: zuerst wird der aktuelle In-Memory-Puffer geprüft
  • 2. Bloom Filter: probabilistische Struktur – prüft, ob Daten in einer SSTable liegen könnten
  • 3. Key Cache: zwischenspeichert Positionen von Partition Keys in SSTables
  • 4. Row Cache: optionaler Cache für vollständige Partitionen
  • 5. SSTable Merge: Daten aus mehreren SSTables werden zusammengeführt

Merge-Strategie

  • Neueste Version einer Cell gewinnt (Timestamp)
  • Tombstones überschreiben ältere Werte

Compaction

  • SSTables akkumulieren sich über Zeit → Compaction führt sie zusammen
  • Entfernt veraltete Versionen und abgelaufene Tombstones
  • Reduziert Anzahl der SSTables → schnellere Reads

Strategien

  • STCS (Size-Tiered): ähnlich große SSTables werden gemergt, Standard, gut für Writes
  • LCS (Leveled): mehrere Level, begrenzte SSTable-Größe, gut für Reads
  • TWCS (Time-Window): für Zeitreihendaten, kompaktiert nach Zeitfenstern

Tombstones

  • Cassandra überschreibt Daten nie direkt – Deletes schreiben einen Tombstone
  • Tombstone = Markierung mit Timestamp, dass Daten gelöscht wurden
  • Replicas, die offline waren, erhalten Tombstone beim nächsten Sync

gc_grace_seconds

  • Tombstones werden erst nach gc_grace_seconds (Standard: 10 Tage) endgültig entfernt
  • Verhindert, dass gelöschte Daten auf Replicas wieder auftauchen

Problem: zu viele Tombstones

  • Reads müssen viele Tombstones überspringen → Latenzprobleme
  • Lösungen: TTL statt manuelles Löschen, TWCS, richtiges Partitionierungsschema
-- TTL beim Schreiben setzen
INSERT INTO messwerte
  (sensor_id, zeitpunkt, temperatur)
VALUES
  ('s-001', toTimestamp(now()), 23.5)
USING TTL 86400;  -- 1 Tag in Sekunden

-- TTL pro Tabelle konfigurieren
CREATE TABLE logs (
  id    UUID,
  ts    TIMESTAMP,
  msg   TEXT,
  PRIMARY KEY (id, ts)
) WITH default_time_to_live = 604800;
  -- 7 Tage

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

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);

Datenmodellierung: Beispiel Zeitreihendaten

Anforderung

  • Sensormesswerte speichern: Temperatur, Luftfeuchtigkeit
  • 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,
  luftfeucht  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, luftfeucht)
  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

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

Schwächen Cassandra

  • Kein Joins, kein flexibles Querying
  • Modellierung komplex (Query-First, Denormalisierung)
  • Keine starken Transaktionen über Partitionen (nur Lightweight Transactions)

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 (Billionen von Rows)
  • Apple: iCloud-Dienste, über 75.000 Cassandra-Nodes
  • Instagram: Direct Messages, Aktivitäts-Feed
  • Uber: Fahrthistorie, Geo-Daten
  • Discord: Message-Speicherung (Billionen von Nachrichten)

Managed Angebote

  • DataStax Astra DB (DBaaS auf Basis von Cassandra)
  • Amazon Keyspaces (Cassandra-kompatibel, serverless)
  • Azure Managed Instance for Apache Cassandra