-
Notifications
You must be signed in to change notification settings - Fork 1
Compendium chapter 02 architecture
Navigation: Home > Pages
"Eine gute Architektur ist wie ein gutes Fundament - unsichtbar, aber entscheidend fΓΌr alles, was darauf aufbaut."
In diesem Kapitel tauchen wir tief in die Architektur von ThemisDB ein. Sie lernen, wie die verschiedenen Schichten zusammenarbeiten, wie Transaktionen garantiert werden, und wie MVCC (Multi-Version Concurrency Control) ParallelitΓ€t ohne Locks ermΓΆglicht.
Was Sie in diesem Kapitel lernen werden:
- Die Schichten-Architektur von ThemisDB
- Wie MVCC funktioniert und warum es wichtig ist
- Transaction Management und ACID-Garantien
- Storage Layer mit RocksDB
- Indexstrukturen fΓΌr Performance
- Praktische Demonstration mit einer Todo-App
Voraussetzungen: Kapitel 1 gelesen, GrundverstΓ€ndnis von Datenbanken.
ThemisDB folgt einer klassischen Schichten-Architektur, wobei jede Schicht klare Verantwortlichkeiten hat.
ββββββββββββββββββββββββββββββββββββββββββββββββ
β 1. Query Layer (AQL) β
β β’ Query Parsing & Optimization β
β β’ Execution Planning β
β β’ Result Formatting β
ββββββββββββββββ¬ββββββββββββββββββββββββββββββββ
β
ββββββββββββββββΌββββββββββββββββββββββββββββββββ
β 2. Transaction Manager (MVCC) β
β β’ Snapshot Isolation β
β β’ Conflict Detection β
β β’ Commit/Rollback Coordination β
ββββββββββββββββ¬ββββββββββββββββββββββββββββββββ
β
ββββββββββββββββΌββββββββββββββββββββββββββββββββ
β 3. Model Engines β
β ββββββββββββ¬βββββββββββ¬βββββββββββββββββββ β
β β Graph β Document β Vector β β
β β Engine β Engine β Engine β β
β ββββββββββββ΄βββββββββββ΄βββββββββββββββββββ β
β β’ Model-spezifische Logik β
β β’ Secondary Indexes β
β β’ Constraints β
ββββββββββββββββ¬ββββββββββββββββββββββββββββββββ
β
ββββββββββββββββΌββββββββββββββββββββββββββββββββ
β 4. Index Manager β
β β’ B-Tree Indexes β
β β’ Hash Indexes β
β β’ Geo Indexes (Hilbert Curves) β
β β’ Vector Indexes (HNSW) β
β β’ Fulltext Indexes β
ββββββββββββββββ¬ββββββββββββββββββββββββββββββββ
β
ββββββββββββββββΌββββββββββββββββββββββββββββββββ
β 5. Storage Layer (RocksDB) β
β β’ LSM-Trees fΓΌr SSD-Optimierung β
β β’ Write-Ahead Log (WAL) β
β β’ Compression (LZ4, Zstandard) β
β β’ Snapshots & Backups β
ββββββββββββββββββββββββββββββββββββββββββββββββ
Separation of Concerns: Jede Schicht hat eine klar definierte Aufgabe und kann unabhΓ€ngig optimiert oder ersetzt werden.
Beispiel: Wenn wir in Zukunft eine neue Storage-Engine einfΓΌhren wollen, mΓΌssen wir nur Schicht 5 Γ€ndern - alle anderen Schichten bleiben unverΓ€ndert.
Traditionelle Datenbanken verwenden Locks:
# Traditionell: Pessimistic Locking
transaction1.begin()
transaction1.lock(row_id) # Row wird gesperrt
transaction1.update(row_id, new_value)
transaction1.unlock(row_id)
transaction1.commit()
# Problem: Transaction 2 muss warten
transaction2.begin()
transaction2.lock(row_id) # BLOCKIERT bis transaction1 fertig ist!Problem: Bei hoher ParallelitΓ€t fΓΌhrt dies zu:
- Warteschlangen und Bottlenecks
- Deadlocks zwischen Transaktionen
- Timeouts und Failed Transactions
MVCC (Multi-Version Concurrency Control) erstellt stattdessen Versionen:
# MVCC: Optimistic Concurrency
transaction1.begin(snapshot_id=100)
# Liest Version 100 der Daten
transaction1.read(row_id) # Version 100
# Gleichzeitig kann transaction2 lesen!
transaction2.begin(snapshot_id=100)
transaction2.read(row_id) # Auch Version 100, keine Wartezeit!
# Transaction 1 schreibt
transaction1.update(row_id, value_a) # Erstellt Version 101
transaction1.commit() # Version 101 wird permanent
# Transaction 2 schreibt auch
transaction2.update(row_id, value_b) # Will auch Version 101 erstellen
transaction2.commit() # FEHLER: Conflict Detection!
# "Row was modified by another transaction"Vorteil: Reads blockieren nie Writes, Writes blockieren nie Reads.
sequenceDiagram
participant T1 as Transaction 1
participant MVCC as MVCC Manager
participant T2 as Transaction 2
T1->>MVCC: BEGIN (snapshot_id=100)
Note over T1,MVCC: T1 sieht Version 100
T2->>MVCC: BEGIN (snapshot_id=100)
Note over T2,MVCC: T2 sieht auch Version 100
T1->>MVCC: READ(row_id)
MVCC-->>T1: Version 100 (age=28)
T2->>MVCC: READ(row_id)
Note over T2,MVCC: Kein Warten!
MVCC-->>T2: Version 100 (age=28)
T1->>MVCC: UPDATE(row_id, age=29)
Note over MVCC: Erstellt Version 101
T1->>MVCC: COMMIT
Note over MVCC: Version 101 wird permanent
T2->>MVCC: UPDATE(row_id, age=30)
T2->>MVCC: COMMIT
Note over MVCC: [ERROR] Conflict Detection!
MVCC--xT2: ERROR: Write-Write Conflict
Abb. 02.1: Systemarchitektur-Γbersicht
Jede Datenzeile hat nicht nur einen Wert, sondern eine Historie:
Row ID: users/alice
Version 100 (committed):
name: "Alice Smith"
age: 28
Version 101 (committed):
name: "Alice Smith"
age: 29
Version 102 (in_progress, transaction_id=555):
name: "Alice Johnson"
age: 29
Snapshot-Reads: Eine Transaktion mit snapshot_id=100 sieht nur Versionen β€ 100, die committed sind.
Write-Write Conflicts: Wenn zwei Transaktionen die gleiche Row Γ€ndern wollen, gewinnt die erste. Die zweite bekommt einen Conflict Error beim Commit.
graph LR
subgraph "MVCC Version History"
V100[Version 100<br/>age: 28<br/>status: committed]
V101[Version 101<br/>age: 29<br/>status: committed]
V102[Version 102<br/>age: 30<br/>status: in_progress<br/>tx_id: 555]
V100 --> V101
V101 --> V102
end
T1[Transaction 1<br/>snapshot_id=100] -.reads.-> V100
T2[Transaction 2<br/>snapshot_id=101] -.reads.-> V101
T3[Transaction 3<br/>snapshot_id=102] -.reads.-> V102
style V100 fill:#95e1d3
style V101 fill:#78e08f
style V102 fill:#ffd32a
Abb. 02.2: Query-Engine-Komponenten
ThemisDB garantiert volle ACID-Properties:
A - Atomicity (AtomaritΓ€t):
Alle Operationen in einer Transaktion passieren komplett oder gar nicht.
# Beispiel: Geldtransfer
transaction.begin()
transaction.update("accounts/alice", {"balance": 900}) # -100
transaction.update("accounts/bob", {"balance": 1100}) # +100
transaction.commit() # Beide Updates oder keines!Wenn der Server zwischen den beiden update() Calls abstΓΌrzt:
- Ohne AtomaritΓ€t: Alice verliert 100β¬, Bob bekommt nichts (Katastrophe!)
- Mit AtomaritΓ€t: Beide Updates werden zurΓΌckgerollt (Konsistent!)
C - Consistency (Konsistenz):
Constraints werden immer eingehalten.
# Foreign Key Constraint
transaction.insert("orders", {
"order_id": "o123",
"user_id": "alice", # Muss in users existieren
"amount": 50.0
})
# Wenn user "alice" nicht existiert -> FEHLER!I - Isolation (Isolierung):
Transaktionen sehen sich nicht gegenseitig.
# Transaction 1 liest
t1.read("products/laptop") # price: 999
# Transaction 2 Γ€ndert (aber committed noch nicht)
t2.update("products/laptop", {"price": 899})
# Transaction 1 liest nochmal
t1.read("products/laptop") # Immer noch 999!
# Sieht die Γnderung von T2 NICHT, bis T2 committetD - Durability (Dauerhaftigkeit):
Einmal committed, sind Daten permanent - auch bei Serverausfall.
transaction.commit()
# Ab hier garantiert: Daten sind auf Disk!
# Selbst wenn Server JETZT abstΓΌrzt, sind Daten da.stateDiagram-v2
[*] --> Begin: transaction.begin()
Begin --> Active: Snapshot erstellt
Active --> Reading: read()
Active --> Writing: write()
Active --> Validating: commit() aufgerufen
Reading --> Active
Writing --> Active
Validating --> CheckConflicts: PrΓΌfe Write-Write Conflicts
CheckConflicts --> WriteWAL: Keine Konflikte
CheckConflicts --> Rollback: Konflikt erkannt
WriteWAL --> ApplyChanges: WAL persistent
ApplyChanges --> Committed: Γnderungen sichtbar
Committed --> [*]
Active --> Rollback: rollback() oder Fehler
Rollback --> [*]
note right of WriteWAL
Write-Ahead Log (WAL)
garantiert Durability
end note
note right of CheckConflicts
MVCC Conflict Detection:
Hat andere Transaktion
bereits committed?
end note
Abb. 02.3: Storage-Layer-Struktur
ThemisDB implementiert Snapshot Isolation, ein Mittelweg zwischen Serializable und Read Committed:
| Isolation Level | Dirty Reads | Non-Repeatable Reads | Phantom Reads |
|---|---|---|---|
| Read Uncommitted | β MΓΆglich | β MΓΆglich | β MΓΆglich |
| Read Committed | β Verhindert | β MΓΆglich | β MΓΆglich |
| Snapshot Isolation | β Verhindert | β Verhindert | β Verhindert |
| Serializable | β Verhindert | β Verhindert | β Verhindert |
Snapshot Isolation ist performanter als Serializable, verhindert aber trotzdem alle Anomalien, die in der Praxis relevant sind.
ThemisDB verwendet RocksDB als Storage-Engine. Diese Entscheidung basiert auf mehreren Faktoren:
1. LSM-Trees fΓΌr SSDs optimiert
Traditionelle B-Trees schreiben oft:
Write 1 byte β Read 4KB page β Modify 1 byte β Write 4KB page
LSM-Trees (Log-Structured Merge Trees) schreiben sequentiell:
Write 1 byte β Append to log β Done
Later: Merge logs in background
Resultat: 10x schneller auf SSDs!
2. Battle-Tested bei Facebook
RocksDB verarbeitet bei Facebook:
- Billions of operations per day
- Petabytes of data
- 10+ years Production Experience
3. Flexible Key-Value API
RocksDB ist ein Key-Value Store - perfekt als Foundation fΓΌr hΓΆhere Modelle:
// Relational: key = "users/alice"
db->Put("users/alice", json_data);
// Graph: key = "edges/from_alice_to_bob"
db->Put("edges/from_alice_to_bob", edge_data);
// Vector: key = "vectors/product_123"
db->Put("vectors/product_123", embedding_data);RocksDB unterstΓΌtzt "Column Families" - separate Namespaces innerhalb einer DB:
// Trennung nach Datenmodell
db->Put(cf_relational, "users/alice", data);
db->Put(cf_graph_edges, "alice->bob", edge);
db->Put(cf_vectors, "prod_123", embedding);Vorteil: Jede Column Family kann eigene Optimierungen haben:
- Relational: Starke Compression
- Graph: Weniger Compression, mehr Cache
- Vectors: Spezielle Bloom Filters
ThemisDB nutzt ein kanonisches Speicherformat, das als "Base Entity" bezeichnet wird [3], [4]. Jede logische EntitΓ€t β sei es eine relationale Zeile, ein Graph-Knoten, ein Vektor-Objekt oder ein Dokument β wird als ein einziges binΓ€r-serialisiertes Dokument (als "Blob" bezeichnet) gespeichert [11].
Diese Architekturentscheidung ist fundamental fΓΌr die Multi-Modell-FΓ€higkeit von ThemisDB:
Multi-Modell-Datenabbildung auf physischer Ebene:
| Logisches Modell | Physischer Speicher | Key-Format | Value-Format |
|---|---|---|---|
| Relational | (PK, Blob) | "table_name:pk_value" |
VelocyPack/Bincode |
| Dokument | (PK, Blob) | "collection:pk_value" |
VelocyPack/Bincode |
| Graph (Knoten) | (PK, Blob) | "node:pk_value" |
VelocyPack/Bincode |
| Graph (Kante) | (PK, Blob) | "edge:pk_value" |
VelocyPack (inkl. _from/_to) |
| Vektor | (PK, Blob) | "object:pk_value" |
VelocyPack (inkl. Vektor-Array) |
Wie funktioniert das Base Entity Pattern?
Um das Base Entity Paradigma zu verstehen, betrachten wir ein konkretes Beispiel: Stellen Sie sich vor, Sie speichern einen Benutzer mit dem Namen "Alice", der 30 Jahre alt ist und in Berlin wohnt.
Im traditionellen Ansatz (z.B. PostgreSQL):
-- Relationale Tabelle mit festen Spalten
CREATE TABLE users (
id UUID PRIMARY KEY,
name TEXT,
age INTEGER,
city TEXT
);
INSERT INTO users VALUES ('uuid-123', 'Alice', 30, 'Berlin');Im ThemisDB Base Entity Ansatz:
ZunΓ€chst wird das logische Objekt in ein einheitliches BinΓ€rformat serialisiert:
// 1. Logisches Objekt (Application Layer)
User alice = {
id: "uuid-123",
name: "Alice",
age: 30,
city: "Berlin"
};
// 2. Serialisierung zu VelocyPack (binΓ€res JSON-Γ€hnliches Format)
// VelocyPack ist optimiert fΓΌr schnelles Parsing und kompakte Speicherung
std::vector<uint8_t> blob = VelocyPack::serialize(alice);
// Resultat: ~40 Bytes binΓ€re Daten statt ~80 Bytes ASCII-JSON
// 3. Speicherung in RocksDB
std::string key = "users:uuid-123"; // Namespace + Primary Key
db->Put(key, blob);Dieser Blob ist das "Base Entity" β die kanonische Speicherform fΓΌr alle Datenmodelle. Entscheidend: Egal ob Sie einen relationalen Datensatz, einen Graph-Knoten, ein Dokument oder einen Vektor speichern β physisch ist es immer ein Key-Value-Paar mit binΓ€rem Blob als Value.
Wie werden unterschiedliche Datenmodelle auf Base Entities abgebildet?
-
Relational (Tabellenzeile):
// Key: "table_name:primary_key" // Value: BinΓ€r-serialisierte Zeile mit allen Spalten Key: "users:uuid-123" Value: VelocyPack({id, name, age, city})
-
Graph-Knoten:
// Key: "node:node_id" // Value: Knoten-Eigenschaften + Metadaten Key: "nodes:alice" Value: VelocyPack({_id, label: "Person", properties: {name, age}})
-
Graph-Kante:
// Key: "edge:edge_id" // Value: Kante mit _from, _to, Gewicht, Eigenschaften Key: "edges:knows-123" Value: VelocyPack({_from: "alice", _to: "bob", _weight: 1.0, since: "2020"})
-
Vektor-Objekt:
// Key: "vector:object_id" // Value: Objekt-Metadaten + Embedding-Array Key: "vectors:doc-456" Value: VelocyPack({id, metadata: {...}, embedding: [0.1, 0.2, ..., 0.9]})
Warum ist das ein Durchbruch?
Problem bei Polyglot Persistence: In traditionellen Multi-Modell-AnsΓ€tzen (z.B. PostgreSQL + Neo4j + ChromaDB) mΓΌssen Sie denselben Datenpunkt in mehreren Datenbanken speichern:
- PostgreSQL: Metadaten (Name, Alter)
- Neo4j: Graph-Beziehungen
- ChromaDB: Vektor-Embedding
Resultat: Datenkonsistenz ist unmΓΆglich zu garantieren, weil atomare Transaktionen ΓΌber drei separate Systeme nicht mΓΆglich sind.
LΓΆsung mit Base Entity: Alle Informationen (Metadaten, Graph-Kontext, Vektor-Embedding) sind im selben Blob. Eine RocksDB-Transaktion kann atomar:
- Den Base Entity-Blob aktualisieren
- SekundΓ€rindizes aktualisieren (relationaler Index, Graph-Adjazenz, HNSW-Vector-Index)
- Alles oder nichts (ACID)
Vorteile dieses Ansatzes:
- Einheitliche Speicherschicht: Alle Datenmodelle teilen sich denselben physischen Speicher [11]
- ACID ΓΌber alle Modelle: Transaktionen kΓΆnnen atomar ΓΌber Graph, Vector und Relational operieren [20]
- Effiziente Serialisierung: BinΓ€rformate wie VelocyPack [41] sind 4x schneller als Standard-JSON-Parser [40]
ThemisDB nutzt nicht Standard-RocksDB, sondern die RocksDB TransactionDB-Variante [3], [46]. Diese bietet:
- Snapshot Isolation: Jede Transaktion operiert auf einem konsistenten Snapshot der Datenbank [15], [20]
- Conflict Detection: Parallele Transaktionen, die dieselben SchlΓΌssel bearbeiten, werden erkannt [46]
- Atomare Rollbacks: Fehlschlagende Transaktionen werden vollstΓ€ndig zurΓΌckgerollt [16]
Dies ist entscheidend: Die Aktualisierung einer einzelnen logischen EntitΓ€t (z.B. UPDATE users SET age = 31) erfordert die atomare Γnderung mehrerer physischer Key-Value-Paare:
- Der "Base Entity"-Blob muss aktualisiert werden
- SekundΓ€rindex-EintrΓ€ge mΓΌssen geΓ€ndert werden (z.B. LΓΆschen von
idx:age:30, EinfΓΌgen vonidx:age:31)
Ohne TransactionDB wΓ€re diese Konsistenz zwischen Base Entity-Blobs und Index-Projektionen nicht garantiert.
Jede Write-Operation wird erst in ein Log geschrieben:
1. Client sendet: UPDATE users/alice SET age=29
2. WAL Write: "UPDATE users/alice age=29" β Disk (sync!)
3. MemTable Update: In-Memory Update
4. Return OK to Client
...spΓ€ter...
5. Background Compaction: MemTable β SSTable auf Disk
Crash Recovery: Wenn Server abstΓΌrzt:
- MemTable (RAM) ist verloren
- WAL (Disk) ist da
- Beim Restart: WAL replay β MemTable rebuild β Normal operations
Wie funktioniert der WAL-Mechanismus im Detail?
Der Write-Ahead Log ist das HerzstΓΌck der Dauerhaftigkeit (Durability) in ThemisDB. Lassen Sie uns den Ablauf Schritt fΓΌr Schritt nachvollziehen:
Schritt 1 - Client sendet Write-Operation: Ein Client mΓΆchte den Benutzer "Alice" aktualisieren. Die Operation wird an den ThemisDB-Server gesendet:
client.update("users", "uuid-123", {age: 31});Schritt 2 - WAL Write (kritisch fΓΌr Durability): Bevor irgendetwas im Hauptspeicher geΓ€ndert wird, schreibt ThemisDB die Operation in das Write-Ahead Log auf der Festplatte. Dieser Schritt ist synchron β der Server wartet, bis die Daten physisch auf die Disk geschrieben sind (fsync):
// Pseudo-Code der WAL-Implementation
WALEntry entry = {
sequence_number: next_seq++,
timestamp: now(),
operation: "UPDATE",
key: "users:uuid-123",
value: serialize({age: 31})
};
// Kritisch: sync=true erzwingt fsync() - Daten MΓSSEN auf Disk sein
wal_file.append(entry, sync=true);
// Erst wenn fsync() zurΓΌckkehrt, sind Daten dauerhaft gespeichertWarum ist sync=true wichtig?
Ohne fsync() wΓΌrden Daten nur im Betriebssystem-Cache liegen. Bei einem Stromausfall wΓ€ren sie verloren. Mit fsync() garantiert das OS, dass Daten auf physischen Plattern (oder SSD) sind.
Schritt 3 - MemTable Update (In-Memory): Erst nachdem der WAL-Eintrag sicher auf Disk ist, wird die Γnderung im MemTable (RAM-Struktur) vorgenommen:
// MemTable ist eine sortierte In-Memory-Map (z.B. Skip List)
memtable.put("users:uuid-123", {age: 31});Das MemTable ist schnell (O(log n) fΓΌr Inserts), aber flΓΌchtig. Bei einem Crash ist es weg.
Schritt 4 - Return OK zum Client: Jetzt erst antwortet der Server dem Client mit "SUCCESS". Der Client hat die Garantie:
- Daten sind dauerhaft (WAL auf Disk)
- Selbst bei sofortigem Crash sind Daten nicht verloren
Schritt 5 - Background Compaction (asynchron): Im Hintergrund, ohne den Client zu blockieren, werden MemTables periodisch auf Disk geschrieben als SSTable-Dateien:
// Wenn MemTable voll ist (z.B. 256 MB)
if (memtable.size() > 256MB) {
SSTable* sstable = memtable.flush_to_disk();
// SSTable ist eine immutable, sortierte Datei auf Disk
// Format: [Key1, Value1, Key2, Value2, ...]
}Crash Recovery - Warum WAL lebensrettend ist:
Szenario: Server crashed nach Schritt 4, aber vor Schritt 5. Das MemTable (RAM) ist verloren, aber der WAL (Disk) ist da.
Beim Server-Restart:
// 1. WAL lesen und replay
for (entry in wal_file) {
memtable.put(entry.key, entry.value);
}
// β MemTable ist rekonstruiert!
// 2. Normale Operationen fortsetzen
server.start();Resultat: Kein Datenverlust. Alle committed Transaktionen sind wiederhergestellt.
Performance-Trade-off:
Der fsync() in Schritt 2 kostet Zeit (~1-5ms auf NVMe, ~10-20ms auf HDD). Deshalb ist die WAL-Platzierung auf schnellem NVMe-Speicher kritisch fΓΌr Write-Performance.
Die Entscheidung fΓΌr eine LSM-Tree-Architektur [12] hat spezifische Performance-Implikationen:
Schreiboptimierung (Create/Update/Delete):
- LSM-Trees sind inhΓ€rent schreiboptimiert [12], [14]
- Jede C/U/D-Operation ist ein extrem schneller, sequentieller "Append-Only"-Vorgang in eine In-Memory-Struktur (das Memtable)
- Benchmarks zeigen ca. 45.000 Writes pro Sekunde Durchsatz [3], [5]
- Ideal fΓΌr die Ingestion-Pipeline (Covina) mit hohem Schreibdurchsatz
Lese-Performance-Optimierung:
- Ein Punktabruf ΓΌber den PrimΓ€rschlΓΌssel (Get(PK)) ist schnell
- Attribut-basierte Abfragen (z.B.
SELECT * WHERE age > 30) wΓΌrden ohne Indizes einen Full-Scan aller Blobs erfordern [4] - Dies erzwingt architektonisch die Notwendigkeit der "Layer" (SekundΓ€rindizes) fΓΌr optimale Leseleistung [3]
Speicherhierarchie: ThemisDB implementiert eine ausgefeilte Speicherhierarchie [5]:
- HeiΓe Daten: Residieren im Block Cache (RAM, standardmΓ€Γig 1 GB) und in den oberen Levels des LSM-Trees (L0-L5)
- Kompression: Obere Levels mit schnellem LZ4-Algorithmus [37] komprimiert (33,8 MB/s Throughput)
- Kalte Daten: Wandern in das unterste Level (L6) mit ZSTD-Kompression [38] fΓΌr maximale Speicherdichte (2,8x Ratio)
- Automatische Optimierung: Background Compaction verschiebt Daten zwischen Levels [12]
Die Performance von ThemisDB hΓ€ngt entscheidend davon ab, wo Daten gespeichert werden. Moderne Computer haben eine ausgefeilte Speicherhierarchie mit dramatischen Geschwindigkeitsunterschieden:
Die Speicherpyramide (von schnell nach langsam):
βββββββββββββββββββββ
β CPU L1 Cache β < 1 ns (4-64 KB)
β ββββββββββββββββββββ£
β CPU L2 Cache β ~3 ns (256 KB - 1 MB)
β ββββββββββββββββββββ£
β CPU L3 Cache β ~10 ns (8-64 MB, shared)
β ββββββββββββββββββββ£
β RAM (DDR4/DDR5) β ~100 ns (16-512 GB)
β ββββββββββββββββββββ£
ββββββββββ¨ββββββββββββββββββββ¨βββββββββ
β NUMA Boundary (~200-300ns) β
ββββββββββ¬ββββββββββββββββββββ¬βββββββββ
β ββββββββββββββββββββ£
β NVMe SSD (PCIe4) β ~10 ΞΌs (1-8 TB)
β ββββββββββββββββββββ£
β SATA SSD β ~50 ΞΌs (256 GB - 4 TB)
β ββββββββββββββββββββ£
β HDD (7200 RPM) β ~5 ms (4-20 TB)
βββββββββββββββββββββ
Faktor zwischen schnellstem und langsamstem: ~5.000.000x (!)
Wie nutzt ThemisDB diese Hierarchie optimal?
1. HeiΓe Daten im RAM (Block Cache):
ThemisDB konfiguriert RocksDB mit einem groΓen Block Cache (standardmΓ€Γig 1-4 GB, konfigurierbar bis 128 GB+):
// Aus docs/de/performance/performance_memory.md
RocksDBWrapper::Config config;
config.block_cache_size_mb = 4096; // 4 GB Block Cache
config.cache_index_and_filter_blocks = true;
config.pin_l0_filter_and_index_blocks_in_cache = true;
config.high_pri_pool_ratio = 0.5; // 50% fΓΌr Index/FilterWas wird gecacht?
- Data Blocks: Die eigentlichen Key-Value-Paare (50% des Cache)
- Index Blocks: Index-Strukturen fΓΌr schnelles Suchen (25% des Cache, High-Priority)
- Filter Blocks: Bloom-Filter zur Vermeidung unnΓΆtiger Disk-Reads (25% des Cache, High-Priority)
Wirkung: Ein Cache-Hit (Daten sind im RAM) hat ~100ns Latenz. Ein Cache-Miss (Disk-Read nΓΆtig) hat ~10ΞΌs Latenz auf NVMe. Faktor 100x schneller!
2. Write-Ahead Log auf schnellstem Medium (NVMe):
Das WAL ist write-kritisch. Jede Schreiboperation wartet auf einen fsync(). Deshalb sollte das WAL auf dem schnellsten verfΓΌgbaren Medium liegen:
// Separates WAL-Verzeichnis auf dedizierter NVMe
config.db_path = "/data/rocksdb"; // Haupt-DB (kann langsamer sein)
config.wal_dir = "/nvme/fast/wal"; // WAL auf schnellster NVMePerformance-Gewinn:
- NVMe (PCIe4): ~10ΞΌs fsync β 45.000 Writes/Sekunde [3], [5]
- SATA SSD: ~50ΞΌs fsync β 20.000 Writes/Sekunde
- HDD: ~5ms fsync β 200 Writes/Sekunde
Faktor 225x zwischen NVMe und HDD!
3. LSM-Tree Levels mit gestufter Kompression:
ThemisDB nutzt unterschiedliche Kompression fΓΌr verschiedene LSM-Tree Levels:
config.compression_default = "lz4"; // Level 0-5 (heiΓ)
config.compression_bottommost = "zstd"; // Level 6+ (kalt)Warum?
- L0-L5 (heiΓe Daten): Werden hΓ€ufig gelesen β LZ4 (33.8 MB/s Throughput [5], 2-3x Kompression)
- L6 (kalte Daten): Werden selten gelesen β ZSTD (2.8x Kompression [5], aber langsamer)
Speicher-Trade-off visualisiert:
Level 0 (RAM): MemTable β 256 MB, unkomprimiert
β flush
Level 1 (NVMe): Junge SSTables β ~512 MB, LZ4-komprimiert
β compaction
Level 2 (NVMe): Γltere SSTables β ~2 GB, LZ4-komprimiert
β compaction
Level 3-5 (NVMe/SATA): Alte Daten β ~20 GB, LZ4-komprimiert
β compaction
Level 6 (SATA/HDD): Kalte Daten β ~200 GB, ZSTD-komprimiert (max. Dichte)
Automatische Datenmigration:
ThemisDB verschiebt Daten automatisch "nach unten":
- Neue Writes β MemTable (RAM, ultra-schnell)
- MemTable voll β Flush zu L1 (NVMe, schnell)
- Compaction β Daten wandern zu L2, L3, ... (progressiv langsamer)
- Kalte Daten β L6 (maximal komprimiert, platzsparend)
Resultat: HΓ€ufig zugegriffene Daten bleiben "oben" (schnell), selten genutzte Daten wandern "nach unten" (langsam aber platzsparend).
4. GPU-VRAM fΓΌr HNSW-Index (optional, fΓΌr Vektor-Suche):
FΓΌr sehr groΓe Vektor-Datenbanken (>100M Embeddings) kann der HNSW-Index auf GPU-VRAM gemappt werden:
// GPU-Beschleunigung fΓΌr Vektor-Γhnlichkeitssuche
HNSWConfig hnsw_config;
hnsw_config.use_gpu = true;
hnsw_config.gpu_device = 0; // CUDA device 0
// VRAM: ~1-5 ns Latenz, aber begrenzte GrΓΆΓe (8-80 GB)Trade-off: VRAM ist noch schneller als RAM (~1-5ns vs ~100ns), aber extrem begrenzt (8-24 GB typisch).
Zusammenfassung: Speicherhierarchie-Strategie:
| Datentyp | Optimal Platziert | Latenz | BegrΓΌndung |
|---|---|---|---|
| MemTable | RAM | ~100 ns | Aktive Writes, Γ€ndert sich stΓ€ndig |
| WAL | NVMe (PCIe4) | ~10 ΞΌs | Write-kritisch, sync-Operationen |
| Block Cache | RAM | ~100 ns | HΓ€ufig gelesene BlΓΆcke |
| L0-L5 SSTables | NVMe | ~10 ΞΌs | HeiΓe Daten, hΓ€ufig gelesen |
| L6 SSTables | SATA SSD/HDD | ~50 ΞΌs - 5 ms | Kalte Daten, selten gelesen |
| HNSW-Index | RAM (oder GPU-VRAM) | ~100 ns (~5ns GPU) | Vektor-Suche, latenz-kritisch |
| Backups | HDD/Tape/S3 | Sekunden | Langzeit-Archivierung |
Best Practice fΓΌr Production:
# NVMe PCIe4 fΓΌr WAL und L0-L3
/nvme/fast/ β 2 TB NVMe PCIe4 fΓΌr WAL + Hot SSTables
# SATA SSD fΓΌr L4-L6
/ssd/bulk/ β 8 TB SATA SSD fΓΌr Cold SSTables
# HDD fΓΌr Backups
/backup/ β 40 TB HDD Array fΓΌr Point-in-Time SnapshotsJetzt bauen wir eine vollstΓ€ndige Todo-App, um die Architektur in Aktion zu sehen. Basis ist examples/02_todo_app.
Die Todo-App folgt dem MVC-Pattern und demonstriert alle ACID-Eigenschaften:
βββββββββββββββββββββββββββββββββββββββββββ
β UI (Tkinter) β
β β’ Task List (Treeview) β
β β’ Detail Panel (Form) β
β β’ Toolbar (Buttons) β
βββββββββββββββ¬ββββββββββββββββββββββββββββ
β
βββββββββββββββΌββββββββββββββββββββββββββββ
β Controller (main.py) β
β β’ Event Handlers β
β β’ Business Logic β
β β’ Validation β
βββββββββββββββ¬ββββββββββββββββββββββββββββ
β
βββββββββββββββΌββββββββββββββββββββββββββββ
β TodoClient (themis_client.py) β
β β’ CRUD Operations β
β β’ Transaction Management β
β β’ Error Handling β
βββββββββββββββ¬ββββββββββββββββββββββββββββ
β
βββββββββββββββΌββββββββββββββββββββββββββββ
β ThemisDB Server β
β β’ MVCC Transactions β
β β’ Secondary Indexes β
β β’ ACID Guarantees β
βββββββββββββββββββββββββββββββββββββββββββ
FΓΌr unsere Todo-App definieren wir zuerst das Datenmodell mit Python Dataclasses. Wir nutzen Enumerations fΓΌr Status und PrioritΓ€t, um Type-Safety zu gewΓ€hrleisten und Tippfehler zu vermeiden. Die Klasse Task kapselt alle Informationen eines Tasks und bietet Methoden zur Serialisierung fΓΌr ThemisDB.
π VollstΓ€ndiger Code:
examples/02_todo_app/models.py
# Kernkomponenten des Task-Modells (Auszug)
from dataclasses import dataclass
from datetime import datetime
from enum import Enum
class TaskStatus(Enum):
OPEN = "open"
IN_PROGRESS = "in_progress"
DONE = "done"
class TaskPriority(Enum):
LOW = "low"
NORMAL = "normal"
HIGH = "high"
@dataclass
class Task:
id: str
title: str
status: TaskStatus
priority: TaskPriority
created_at: datetime
updated_at: datetime
# ... weitere Felder siehe vollstΓ€ndige Datei
def to_dict(self):
"""Serialisierung fΓΌr ThemisDB - konvertiert Task zu Dictionary"""
return {
"_key": self.id,
"title": self.title,
"status": self.status.value,
"priority": self.priority.value,
"created_at": self.created_at.isoformat(),
# ... vollstΓ€ndige Implementierung in models.py
}Wichtige Design-Entscheidungen:
- Enums fΓΌr Status/Priority: Verhindert ungΓΌltige Werte und bietet Type-Safety
-
Dataclass-Decorator: Generiert automatisch
__init__,__repr__,__eq__Methoden - to_dict/from_dict Methoden: Klare Trennung von Serialisierungslogik
- Type Hints: ErmΓΆglicht IDE-UnterstΓΌtzung und frΓΌhzeitige Fehlererkennung
# ThemisDB starten (Docker)
docker run -d -p 8765:8765 themisdb/themisdb:1.3.4
# Todo-App installieren
cd examples/02_todo_app
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txtDer TodoClient kapselt alle Datenbankoperationen und bietet eine saubere API fΓΌr unsere Anwendung. Die Klasse initialisiert beim Start automatisch die benΓΆtigte Collection und erstellt Performance-Indizes auf hΓ€ufig gefilterten Feldern wie Status und PrioritΓ€t. Dies demonstriert Best Practices fΓΌr die Arbeit mit ThemisDB.
π VollstΓ€ndiger Code:
examples/02_todo_app/themis_client.py
KernfunktionalitΓ€t - Database Setup:
class TodoClient:
def __init__(self, host="localhost", port=8765):
self.client = Client(host, port)
self._setup_database()
def _setup_database(self):
"""Erstellt Collection und Indizes fΓΌr optimale Performance"""
self.client.create_collection("tasks", type="document")
# Performance-Indizes auf oft gefilterten Feldern
self.client.create_index("tasks", "status_idx", ["status"])
self.client.create_index("tasks", "priority_idx", ["priority"])
self.client.create_index("tasks", "due_date_idx", ["due_date"])CRUD-Operationen (Auszug):
def create_task(self, task: Task) -> bool:
"""Erstellt einen neuen Task in der Datenbank"""
self.client.insert("tasks", task.to_dict())
return True
def update_task(self, task: Task) -> bool:
"""Aktualisiert Task - MVCC erkennt Konflikte automatisch!"""
try:
self.client.update("tasks", task.id, task.to_dict())
return True
except ConflictError:
print("Task was modified by another user!")
return FalseQuery-Beispiele mit AQL:
def list_tasks(self, status: Optional[TaskStatus] = None) -> List[Task]:
"""Demonstriert AQL-Queries mit dynamischen Filtern"""
query = "FOR task IN tasks"
if status:
query += f' FILTER task.status == "{status.value}"'
query += " SORT task.created_at DESC RETURN task"
results = self.client.query(query)
return [Task.from_dict(data) for data in results]Die vollstΓ€ndige Implementierung enthΓ€lt zusΓ€tzlich get_task(), delete_task() und search_tasks() Methoden. Siehe vollstΓ€ndige Datei fΓΌr alle Details.
Die Todo-App zeigt MVCC in Aktion:
Szenario: Zwei Benutzer Γ€ndern gleichzeitig den gleichen Task
# User 1: Startet Transaction
user1 = TodoClient()
task = user1.get_task("task_123") # Snapshot Version 100
task.status = TaskStatus.IN_PROGRESS
task.updated_at = datetime.now()
# User 2: Auch Transaction, gleicher Task
user2 = TodoClient()
task2 = user2.get_task("task_123") # Auch Snapshot Version 100
task2.priority = TaskPriority.HIGH
task2.updated_at = datetime.now()
# User 1 committed zuerst
user1.update_task(task) # β SUCCESS, Version 101
# User 2 versucht zu committen
user2.update_task(task2) # β CONFLICT!
# Error: "Task was modified by another transaction"Was passiert intern?
- Beide lesen Version 100 (Snapshot Isolation)
- User 1 schreibt Version 101 und committed
- User 2 versucht auch Version 101 zu schreiben
- ThemisDB erkennt: "Version 101 existiert bereits!"
- Conflict Error wird geworfen
LΓΆsung im Code:
def update_task_with_retry(self, task: Task, max_retries=3):
"""Update mit automatischem Retry bei Conflicts"""
for attempt in range(max_retries):
try:
self.client.update("tasks", task.id, task.to_dict())
return True
except ConflictError:
# Re-read aktuellste Version
latest = self.get_task(task.id)
if not latest:
return False
# Merge changes (Application Logic)
# z.B.: Keep newer updated_at
if latest.updated_at > task.updated_at:
task = latest
# Retry mit gemergten Daten
continue
except Exception as e:
return False
return False # Max retries exceededOhne Index: Full Table Scan
-- Ohne Index: O(n)
SELECT * FROM tasks WHERE status = 'open'
-- Muss ALLE Tasks durchgehen: 10.000 Tasks = 10.000 Reads
Mit Index: Direkt zum Ergebnis
-- Mit Index auf status: O(log n + k), k = Anzahl Results
SELECT * FROM tasks WHERE status = 'open'
-- B-Tree Lookup: log(10.000) β 13 Reads + nur relevante Tasks
1. B-Tree Index (Default)
client.create_index("tasks", "status_idx", ["status"])- Sortierte Daten
- Range Queries:
WHERE age > 18 AND age < 65 - Equality:
WHERE status = 'open'
2. Hash Index
client.create_index("tasks", "id_hash_idx", ["id"], type="hash")- Nur Equality:
WHERE id = 'task_123' - Schneller als B-Tree fΓΌr Equality
- Keine Range Queries
3. Geo Index (Hilbert Curves)
client.create_index("locations", "geo_idx", ["coordinates"], type="geo")- RΓ€umliche Queries
- Nearest Neighbor
- Bounding Box Searches
4. Fulltext Index
client.create_index("tasks", "fulltext_idx", ["title", "description"], type="fulltext")- Tokenization
- Stemming
- Relevance Scoring
5. Vector Index (HNSW)
client.create_index("products", "embedding_idx", ["embedding"], type="vector", dimensions=768)- Approximate Nearest Neighbor
- Cosine Similarity
- Euclidean Distance
| Operation | Ohne Index | Mit B-Tree | Mit Hash |
|---|---|---|---|
Equality (=) |
O(n) | O(log n) | O(1) |
Range (>, <) |
O(n) | O(log n + k) | β |
| Sort | O(n log n) | O(k) | β |
k = Anzahl Ergebnisse
In diesem Kapitel haben Sie gelernt:
β
Schichten-Architektur: 5 Schichten mit klaren Verantwortlichkeiten
β
MVCC: ParallelitΓ€t ohne Locks durch Versionierung
β
Transaktionen: ACID-Garantien und Snapshot Isolation
β
RocksDB: LSM-Trees, WAL, Column Families
β
Indizes: B-Tree, Hash, Geo, Fulltext, Vector
β
Todo-App: VollstΓ€ndige CRUD-Anwendung mit MVCC
- Multi-Model MVCC: Transaktionen ΓΌber alle Modelle hinweg
- RocksDB Foundation: Battle-tested, SSD-optimiert
- Flexible Indizes: FΓΌr jeden Use Case der richtige Index
- Snapshot Isolation: Performance + Konsistenz
Im nΓ€chsten Kapitel lernen Sie, wie die verschiedenen Datenmodelle kombiniert werden kΓΆnnen und wann welches Modell am besten passt.
Kapitel 3: Multi-Model verstehen β
- Complete Example: examples/02_todo_app
- MVCC Deep-Dive: ../de/architecture/architecture_mvcc.md
- RocksDB Storage: ../de/storage/storage_rocksdb.md
- Index Design: Kapitel 15 - Storage Internals
ThemisDB speichert alle Daten - unabhΓ€ngig vom Modell (relational, document, graph, vector) - als BaseEntity. Dies ist die fundamentale Speichereinheit.
class BaseEntity {
public:
using Blob = std::vector<uint8_t>;
using FieldMap = std::map<std::string, Value>;
enum class Format { BINARY, JSON };
// PrimΓ€rschlΓΌssel
const std::string& getPrimaryKey() const;
void setPrimaryKey(std::string_view pk);
// Feld-Zugriff (lazy parsing)
std::optional<Value> getField(std::string_view field_name) const;
void setField(std::string_view field_name, const Value& value);
// Serialisierung
Blob serialize() const;
std::string toJson() const;
// Factory-Methoden
static BaseEntity fromJson(std::string_view pk, std::string_view json_str);
static BaseEntity fromFields(std::string_view pk, const FieldMap& fields);
static BaseEntity deserialize(std::string_view pk, const Blob& blob);
private:
std::string primary_key_;
Blob blob_;
Format format_ = Format::BINARY;
mutable std::shared_ptr<FieldMap> field_cache_;
};Kernkonzept: Jede EntitΓ€t ist ein einzelnes binΓ€res Blob mit effizienter Serialisierung und Lazy-Parsing fΓΌr schnellen Feldzugriff.
ThemisDB verwendet ein typsicheres std::variant-System:
| Typ | C++ Typ | Verwendung | Beispiel |
|---|---|---|---|
| null | std::monostate |
Fehlende/leere Werte | NULL |
| bool | bool |
Wahrheitswerte |
true, false
|
| int | int64_t |
Ganzzahlen |
42, -1000
|
| double | double |
Gleitkommazahlen |
3.14, 1e10
|
| string | std::string |
Texte | "Hello World" |
| vector | std::vector<float> |
Embeddings fΓΌr ANN | [0.1, 0.2, 0.3] |
| binary | std::vector<uint8_t> |
BinΓ€rdaten | Bilder, PDFs |
Wichtig: Nested Objects/Arrays sind aktuell nicht unterstΓΌtzt (geplant fΓΌr v1.4).
flowchart TB
A[JSON Input] --> B[fromJson]
B --> C[BaseEntity in Memory]
C --> D[serialize]
D --> E[Binary Blob]
E --> F[RocksDB Storage]
F --> G[Read from RocksDB]
G --> H[deserialize]
H --> C
C --> I[getField lazy parsing]
I --> J[field_cache_]
C --> K[toJson]
K --> L[JSON Output]
style C fill:#e1f5ff
style J fill:#fff4e1
style F fill:#ffe1e1
Abb. 02.4: Transaction-Management-Flow
Lazy Parsing: Felder werden erst geparst, wenn sie angefordert werden - spart CPU-Zeit bei groΓen Objekten.
ThemisDB vereint alle Datenmodelle in einer einzigen RocksDB-Instanz durch ein hierarchisches SchlΓΌsselsystem mit PrΓ€fixen.
| Datenmodell | Format | Beispiel | Beschreibung |
|---|---|---|---|
| Relational | entity:table:pk |
entity:users:alice |
Tabellenzeilen |
| Document | entity:collection:pk |
entity:orders:order_123 |
Dokumente |
| Graph Node | entity:node:pk |
entity:node:user_456 |
Graph-Knoten |
| Graph Edge | entity:edge:pk |
entity:edge:follows_789 |
Graph-Kanten |
| Vector | entity:vectors:pk |
entity:vectors:doc_abc |
Vektor-Embeddings |
| Secondary Index | idx:table:column:value:pk |
idx:users:age:30:alice |
Equality Indexes |
| Range Index | ridx:table:column:value:pk |
ridx:products:price:99.99:prod_1 |
Range Queries |
| Spatial Index | sidx:table:geohash:pk |
sidx:locations:u33dc1:berlin |
Geo-Queries |
| TTL Index | ttlidx:table:column:ts:pk |
ttlidx:sessions:created:1730000000:s1 |
Time-based Expiry |
| Fulltext | ftidx:table:column:token:pk |
ftidx:articles:body:search:art_42 |
Volltextsuche |
| Graph Outdex | graph:out:pk_start:pk_edge |
graph:out:alice:follows_789 |
Ausgehende Kanten |
| Graph Indeg | graph:in:pk_target:pk_edge |
graph:in:bob:follows_789 |
Eingehende Kanten |
| Changefeed | changefeed:pk:seqno |
changefeed:alice:0000000001 |
CDC Stream |
| Time-Series | ts:metric:entity:ts |
ts:cpu:server1:1730000000000 |
Metriken |
Separator: Alle SchlΓΌssel verwenden : als Trennzeichen fΓΌr schnelles Prefix-Scanning.
flowchart LR
A[RocksDB] --> B[entity: Prefix]
A --> C[idx: Prefix]
A --> D[graph: Prefix]
A --> E[ts: Prefix]
B --> B1[entity:users:alice]
B --> B2[entity:orders:order_123]
B --> B3[entity:node:user_456]
C --> C1[idx:users:age:30:alice]
C --> C2[ridx:products:price:99.99:prod_1]
C --> C3[ftidx:articles:body:search:art_42]
D --> D1[graph:out:alice:follows_789]
D --> D2[graph:in:bob:follows_789]
E --> E1[ts:cpu:server1:1730000000000]
E --> E2[ts:memory:server1:1730000000000]
style A fill:#e1f5ff
style B fill:#fff4e1
style C fill:#e1ffe1
style D fill:#ffe1f5
style E fill:#f5e1ff
Abb. 02.5: MVCC-Versionskontrolle
// KeySchema API
std::string pk = KeySchema::extractPrimaryKey("idx:users:age:30:alice");
// Ergebnis: "alice"
KeyType type = KeySchema::parseKeyType("entity:users:alice");
// Ergebnis: KeyType::RELATIONAL
std::string key = KeySchema::makeRelationalKey("users", "alice");
// Ergebnis: "entity:users:alice"Vorteil: Durch PrΓ€fixe kΓΆnnen alle Indizes einer Tabelle oder alle Kanten eines Knotens mit einem einzigen RocksDB-Scan gelesen werden.
Nachdem wir in Abschnitt 2.2 die Grundlagen von MVCC kennengelernt haben, tauchen wir nun in die Implementierungsdetails ein.
flowchart TB
A[Application] --> B[TransactionManager]
B --> C[TransactionWrapper]
C --> D[RocksDB TransactionDB]
D --> E[Snapshot Manager]
D --> F[Lock Manager]
D --> G[WAL]
E --> H[Versioned Data]
F --> I[Write-Write Conflict Detection]
G --> J[Durability]
subgraph "MVCC Engine"
D
E
F
G
end
style B fill:#e1f5ff
style D fill:#fff4e1
style E fill:#e1ffe1
style F fill:#ffe1e1
Abb. 02.6: Index-Strukturen
// Transaction A liest Snapshot zum Zeitpunkt t=100
auto txn_a = db.beginTransaction();
auto snapshot = txn_a->getSnapshot(); // t=100
// Transaction B schreibt zur gleichen Zeit
auto txn_b = db.beginTransaction();
txn_b->put("entity:users:alice", "{age: 31}");
txn_b->commit(); // Commit zur Zeit t=101
// Transaction A sieht alte Version (t=100)
auto value = txn_a->get("entity:users:alice");
// value = "{age: 30}" (alte Version)
txn_a->commit();sequenceDiagram
participant T1 as Transaction 1
participant T2 as Transaction 2
participant DB as RocksDB
T1->>DB: get(users:alice)
DB-->>T1: {age: 30}
T2->>DB: get(users:alice)
DB-->>T2: {age: 30}
T1->>DB: put(users:alice, {age: 31})
Note over T1,DB: Acquires Lock
T2->>DB: put(users:alice, {age: 32})
Note over T2,DB: Lock Conflict Detected!
T1->>DB: commit()
DB-->>T1: OK (age=31 persisted)
T2->>DB: commit()
DB-->>T2: CONFLICT (rollback)
Abb. 02.7: Replication-Topologie
KonfliktauflΓΆsung: Transaction 2 wird automatisch zurΓΌckgerollt - die Anwendung muss die Transaktion wiederholen.
Benchmark-Ergebnisse (v1.5.0-dev):
| Operation | MVCC (ops/s) | WriteBatch (ops/s) | Overhead |
|---|---|---|---|
| Single Entity Insert | 3,400 | 3,100 | +9.7% |
| Batch Insert (100) | 27,800 | 27,800 | 0% |
| Snapshot Read | 44,000 | - | Baseline |
| Rollback | 35,300 | - | Baseline |
Fazit: MVCC-Overhead ist minimal bei Batch-Operationen - Snapshot-Isolation kostet fast nichts!
Problem: Wenn eine Transaktion zurΓΌckgerollt wird, mΓΌssen alle Indizes konsistent bleiben.
LΓΆsung: Alle Index-Operationen sind Teil der Transaktion:
// Atomare Transaktion: Entity + Secondary Index + Graph Outdex
auto txn = db.beginTransaction();
// 1. Insert Entity
txn->put(KeySchema::makeRelationalKey("users", "alice"),
entity.serialize());
// 2. Update Secondary Index
txn->put(KeySchema::makeSecondaryIndexKey("users", "age", "30", "alice"),
empty_value);
// 3. Update Graph Outdex
txn->put(KeySchema::makeGraphOutdexKey("alice", "follows_789"),
edge_data);
// Alles oder nichts
if (!txn->commit()) {
// Conflict detected - alle 3 Operationen werden zurΓΌckgerollt
}Garantie: Entweder werden alle Indizes aktualisiert oder keiner - keine Inkonsistenzen mΓΆglich!
ThemisDB verwendet differenzierte Kompression je nach Datentyp fΓΌr optimale Balance zwischen Speicherersparnis und Performance.
| Datentyp | Compression | Ratio | CPU-Overhead | Use Case |
|---|---|---|---|---|
| Time-Series | Gorilla | 10-20x | +15% | Metriken, Sensordaten |
| Vektoren | SQ8 Quantization | 4x | +20% | Embeddings ab 1M Vektoren |
| Content Blobs | ZSTD Level 19 | 1.5-2x | +30% | Dokumente, Bilder |
| JSON Metadata | LZ4 (RocksDB) | 2-3x | +5% | Structured Data |
| Graph Edges | LZ4 (RocksDB) | 2x | +5% | Adjacency Lists |
Gorilla ist ein hocheffizienter Codec fΓΌr Time-Series-Daten von Facebook:
// Gorilla Encoding
std::vector<uint8_t> GorillaCodec::encode(
const std::vector<int64_t>& timestamps,
const std::vector<double>& values
) {
// Delta-of-Delta Encoding fΓΌr Timestamps
// XOR-Encoding fΓΌr Float-Werte
// Typical Ratio: 12-20x
}Beispiel:
Raw Data (100k Punkte):
- Timestamps: 800 KB (8 bytes Γ 100k)
- Values: 800 KB (8 bytes Γ 100k)
- Total: 1.6 MB
Gorilla Compressed:
- Total: 80-130 KB
- Ratio: 12-20x
Scalar Quantization reduziert float32 (4 bytes) auf int8 (1 byte):
flowchart LR
A[Float32 Vector] --> B[Compute Min/Max]
B --> C[Scale to 0-255]
C --> D[int8 Vector]
D --> E[Store with Scale Params]
E --> F[Dequantize on Search]
F --> G[HNSW Search]
style A fill:#e1f5ff
style D fill:#e1ffe1
style G fill:#fff4e1
Abb. 02.8: Failover-Mechanismus
Trade-off:
- β 4x Speicherersparnis
- β Bessere Cache-Nutzung β schnellere Suche
β οΈ 95-98% Recall@10 (minimal schlechter als float32)
ThemisDB konfiguriert RocksDB mit Level-based Compression:
// Level 0-5: LZ4 (schnell fΓΌr Hot Data)
options.compression_per_level[0] = rocksdb::kLZ4Compression;
options.compression_per_level[1] = rocksdb::kLZ4Compression;
// ...
// Level 6+: Zstandard (hΓΆhere Ratio fΓΌr Cold Data)
options.compression_per_level[6] = rocksdb::kZSTD;
options.compression_per_level[7] = rocksdb::kZSTD;Rationale: Hot Data (Level 0-1) benΓΆtigt schnellen Zugriff β LZ4. Cold Data (Level 6+) profitiert von hΓΆherer Kompression β Zstandard.
In diesem erweiterten Kapitel haben Sie die Tiefe der ThemisDB-Architektur kennengelernt:
- BaseEntity: Unified Storage Layer fΓΌr alle Datenmodelle
- Key Schema: Hierarchisches Namespacing mit PrΓ€fixen
- MVCC: Snapshot Isolation ohne Locks
- Compression: Differenzierte Strategien (Gorilla, SQ8, ZSTD)
- Index-Konsistenz: Atomare Transaktionen ΓΌber alle Indizes
- Separation of Concerns: Klar getrennte Schichten
- Performance First: Lazy Parsing, Lazy Indexing
- Multi-Model Unity: Ein Speicher, viele Modelle
- Concurrency Without Locks: MVCC statt Pessimistic Locking
- Adaptive Compression: Datentyp-spezifische Algorithmen
- Complete Example: examples/02_todo_app
- MVCC Deep-Dive: ../de/architecture/architecture_mvcc.md
- RocksDB Storage: ../de/storage/storage_rocksdb.md
- Index Design: Kapitel 15 - Storage Internals
Detaillierte Implementierungsdokumentation zu den behandelten Architektur-Themen:
| Thema | Referenz (docs/de/) |
|---|---|
| Architektur-Γberblick | docs/de/architecture/ARCHITECTURE_OVERVIEW.md |
| DIP-Architektur (VollstΓ€ndig) | docs/de/architecture/COMPLETE_DIP_ARCHITECTURE.md |
| DMS Moderne Architektur | docs/de/architecture/DMS_MODERN_ARCHITECTURE.md |
| Modularisierungs-Entscheidung | docs/de/architecture/MODULARIZATION_DECISION.de.md |
| Modularisierungsplan | docs/de/architecture/MODULARIZATION_PLAN.md |
| Binary Protocol Buffers | docs/de/architecture/BINARY_PROTOCOL_BUFFERS.md |
| DLL Signaturverifizierung | docs/de/architecture/DLL_SIGNATURE_VERIFICATION.md |
| Linux Module Signing | docs/de/architecture/LINUX_MODULE_SIGNING.md |
β ZurΓΌck: Kapitel 1: EinfΓΌhrung
β Weiter: Kapitel 3: Installation
Kapitel 2 von 30 | Teil I: Grundlagen | ~14.000 WΓΆrter | Phase-3-Sync: β
ThemisDB 1.9.0-beta Β· Home Β· Module-Index Β· GitHub Β· Issues
ThemisDB 1.9.0-beta Β· Home Β· Wiki-Index Β· Module-Index Β· FAQ Β· Quick-Reference Β· GitHub Β· Issues Β· Discussions Β· License
- Home
- Hero Articles
- All Wiki Pages
- FAQ
- Edition Comparison
- Repository README
- Changelog
- Roadmap
- Versioning
- Integration Mapping
- Overview
- Readme
- Appendix D Feature Status
- Appendix E Incident Runbooks
- Appendix F AQL Cheatsheet
- Appendix G Configuration
- Appendix H Glossary
- Appendix I Troubleshooting
- Appendix Literatur
- Chapter 00 Genesis
- Chapter 01 Introduction
- Chapter 02 Architecture
- Chapter 03 Multimodel
- Chapter 04 Installation
- Chapter 05 Relational
- Chapter 06 Graph
- Chapter 07 Document
- Chapter 08 Storage Layer
- Chapter 08 Vector
- Chapter 09 Timeseries
- Chapter 10 Enterprise
- Chapter 11 Realtime
- Chapter 12 Computervision
- Chapter 13 Fulltext
- Chapter 14 Geospatial
- Chapter 15 Analytics
- Chapter 16 Ml
- Chapter 16 Sharding
- Chapter 17 LLM Integration
- Chapter 17 Scaling
- Chapter 18 HA
- Chapter 18 Ml
- Chapter 19 Monitoring
- Chapter 19 Monitoring Observability
- Chapter 20 Backup
- Chapter 20 Performance
- Chapter 21 Auth
- Chapter 21 Performance
- Chapter 22 Clients
- Chapter 22 Encryption
- Chapter 23 Testing Qa
- Chapter 24 Ai Ethics
- Chapter 25 Devops Infrastructure
- Chapter 26 Migration Legacy
- Chapter 27 Troubleshooting
- Chapter 28 AQL Reference
- Chapter 29 Analytics Process Mining
- Chapter 30 Deployment Operations
- Chapter 31 API Protocols
- Chapter 32 API Design Rest Principles
- Chapter 32 AQL Oop Implementation
- Chapter 33 Best Practices
- Chapter 34 Query Optimization
- Chapter 35 Data Modeling Patterns
- Chapter 36 Security Hardening
- Chapter 37 Ecosystem Integration
- Chapter 38 Observability Sre
- Chapter 39 Performance Tuning Cookbook
- Chapter 40 Data Governance Compliance
- Chapter 41 Hands On Labs
- Chapter 42 Docs Assistant Usage
- Chapter MVCC Hlc
- Cover
- Cover Book
- Index
- Preface
- Test Links Example
- Batch Operations
- Best Practices
- CRUD Tutorial
- Custom Document Ingestion
- Getting Started Tutorial
- Interactive Examples
- Schema Design
- Video Tutorials
- AQL Reference
- AQL Examples
- AQL Overview
- AQL Feature Roadmap
- AQL Geospatial Guide
- AQL LLM Migration Guide
- AQL API
- AQL Grammar (EBNF)
- AQL Root Overview
- AQL Examples (root)
- API Reference
- API Module README
- OpenAPI Overview
- Client SDK Overview
- SDK Overview
- Operations
- Operations Overview
- Operations Runbook
- Operations Handbook
- ThemisCtl Admin Guide
- Pipeline E2E SOPs
- Docker Overview
- Docker Hub README
- Helm Overview
- Packaging Overview
- Operator Overview
- Security Policy
- Production Hardening Checklist
- Security Hardening Guide
- Encryption Key Management
- Access Control Framework
- Zero Trust Policy
- API Authentication & Authorization
- HSM Production Setup
- PKCS11 Integration
- DSGVO / SOC2 Checklist
- Access Model Runbooks
- Access Model Dashboard
- Maturity Automation Runbook
- Access Review Automation
- Access Model Dashboard
- Access Model Runbooks
- Rights Revocation
- Dr Checklists
- Dr Testing
- Incident Response Playbook
- Incident Response Testing
- GPU Oom Recovery
- Grammar Debugging
- Metrics Scrape Troubleshooting
- Model Swap Procedure
- Quota Tuning
- Subagent Deployment
- Logging Configuration
- Content Model
- Crypto & Keys
- Feature Flags Reference
- Modular Architecture Roadmap
- Modularization Guide
- Module Architecture Index
- PostgreSQL Wire Protocol
- Query Scheduling
- Raft Consensus Design
- Resource Pooling
- Source Directory Guide
- Unified Access Model
- E1 001 Layered Retrieval Design
- E1 002 Ann Abstraction Strategy
- E1 003 Tensor Summary Types
- E1 004 Lora Package Distinction
- E1 005 Model Switch Compatibility
- E1 006 Federated Tensor Summaries
- E2 001 Evaluation Framework Design
- E2 002 Hardware Profile Strategy
- E2 003 Query Planner Routing Model
- E2 004 Approximation Governance Rules
- E2 005 Cross Layer Fallback Confidence Policy
- E3 001 Distributed Tensor Design
- E3 002 Manifest Coordination Strategy
- E3 003 Recovery And Erasure Choice
- E3 004 Tensor Fabric Infrastructure
- Contributing
- Contributing (root)
- Code of Conduct
- Support
- Maintainers
- CTest Guide
- Build Quick Reference
- Developer Wiki Index
- Build / Test / CI
- Module Index
- Branching Strategy
- Release Strategy
- CI Policy Gates Wave C
- Disabled Stub Policy
- Docs PR Policy
- GA Promotion Sign Off
- Github Milestones Setup
- Governance Policies Phase1
- Maturity Claim Verification Checklist
- Maturity Evidence Registry
- Merge Gate Bot Config
- Merge Gate Status Live
- Phase 1 Closure Report
- Phase Closure Policy
- Phase Dependency Graph
- Phase3 Enforcement Runbook
- Plugin Submodule Rollback
- PR Version Targeting
- PR Version Targeting Backfill
- Production Ready 2026 Delivery Plan
- Publish Workflow Audit 2026 09 23
- Query Module Status
- Readme
- Release Governance
- Release Promotion Gate Policy
- Release Validation Checklist
- Root Hygiene Policy
- SBOM Approved Versions
- Security Compliance Audit Report 2026 08 10
- Security Module 5671 Evidence Summary
- Sharding P6 Residual Risk Acceptance
- Sourcecode Compliance Governance
- Src Module Documentation Compliance 2026 09 20
- Updates Development Status Sign Off
- Wave C Implementation Complete
- Wave C Implementation Plan
- Wave C Ml Exit Gate Sign Off
- Wave C Policy Gate Evidence
- Blob Storage
- Cuda
- Ethics Ai
- Exporters
- Huggingface
- Image Analysis
- Importers
- RPC
- Scraper
- Themisdb Ai Watermark Detector
- User Storage Encrypted
- Chimera Architecture
- Chimera Future
- Chimera Readme
- Chimera Roadmap
- Covina Fastapi Ingestion Architecture
- Covina Fastapi Ingestion Future
- Covina Fastapi Ingestion Roadmap
- Vcc Base Architecture
- Vcc Base Future
- Vcc Base Roadmap
- Vcc Clara Ingestion Architecture
- Vcc Clara Ingestion Future
- Vcc Clara Ingestion Roadmap
- Vcc Veritas Architecture
- Vcc Veritas Future
- Vcc Veritas Roadmap
- 01 Hello World
- 02 Todo App
- 03 Contact Manager
- 04 Inventory System
- 05 Time Series Monitor
- 06 Graph Social Network
- 07 Vector Search Documents
- 08 Dms Erp System
- 09 Iot Sensor Network
- 10 Drone Image Analysis
- 11 Blog Wiki
- 12 Expense Tracker
- 13 Recipe Manager
- 14 Ecommerce Catalog
- 15 Event Management
- 16 Kanban Board
- 17 Crm
- 18 Realtime Chat
- 19 Recommendation Engine
- 20 Smart Home
- 21 Coding Platform
- 22 AQL Diagram Tool
- 23 Traveling Salesman
- 24 Moral Philosophy Debates
- API Versioning
- Distributed Sharding
- Feedback Plugins
- Geo
- Gnn
- Image Analysis
- Legal Lora Training
- LLM
- Lora Sync
- Migration
- Nlp
- Performance
- Railway
- Replication
- Rope Visualization
- Sample Product Config
- Security
- Client SDK Overview
- Quickstart
- Sdk Enhancements
- Sdk Implementation Summary
- Test Suite Readme
- Go
- Java
- Javascript
- Php
- Python
- Ruby
- Rust
- Typescript
- 01 Grundlegende Operationen
- 02 AQL Queries
- 03 Graph Daten
- 04 Multimodell Anwendung
- 01 Quickstart Guide
- 02 AQL Referenz Kurzuebersicht
- 03 Datenmodellierung Guide
- 04 Uebungsaufgaben
- 05 Best Practices Guide
- Training Documents
- Training Overview
- 01 Einfuehrung Und Uebersicht
- 02 Datenmodelle Und Architektur
- 03 AQL Abfragesprache
- 04 Installation Und Setup
- 05 Anwendungsbeispiele
- Training Presentations
- Dependencies Readme
- Processmonitor Readme
- Themis.admintools.shared Readme
- Themis.aqlquerybuilder Readme
- Themis.aqlquerybuilder Roadmap
- Themis.auditlogviewer Readme
- Themis.auditlogviewer Roadmap
- Themis.classificationdashboard Readme
- Themis.classificationdashboard Roadmap
- Themis.compliancereports Readme
- Themis.compliancereports Roadmap
- Themis.gisviewer.controlpanel Readme
- Themis.gisviewer.controlpanel Roadmap
- Themis.impactanalysisviewer Readme
- Themis.impactanalysisviewer Roadmap
- Themis.ingestiontool Readme
- Themis.ingestiontool Roadmap
- Themis.keyrotationdashboard Readme
- Themis.keyrotationdashboard Roadmap
- Themis.piimanager Readme
- Themis.piimanager Roadmap
- Themis.retentionmanager Readme
- Themis.retentionmanager Roadmap
- Themis.sagaverifier Readme
- Themis.sagaverifier Roadmap
- Themis.usbadmintool Readme
- Themis.usbadmintool Roadmap
- Architecture Generator Readme
- CI Readme
- CI Roadmap
- Compiler Diagnostics Readme
- Compiler Diagnostics Roadmap
- Completion Readme
- Copilot Ollama Router Readme
- Copilot Ollama Router Roadmap
- Gnn Readme
- Gnn Roadmap
- Rope Visualizer Readme
- Rope Visualizer Roadmap
- Tco Calculator Readme
- Tco Calculator Roadmap
- Tests Readme
- Tests Roadmap
- Themis Config Wx Readme
- Themis Docs Builder Readme
- Wikipedia Ingestion Readme
- Ai Metadata And Provenance
- Build / Test / CI
- Governance And Roadmap
- Developer Wiki Index
- Module Direct Doxygen Check
- Module Doxygen Baseline Summary
- Module Doxygen Batch
- Module Doxygen Coverage Summary
- Module Doxygen Smoke Summary
- Modules And Apis
- Retrieval Direct Doxygen Check
- Soll Ist Gap Summary
- Wiki Delta Report