Table of Contents
- paulDB 🦀🏳️🌈
- Was ist paulDB?
- Der Nordstern: HTAP
- Das HANA-Vorbild: Delta & Main
- Architektur (in-memory, HTAP-zentriert)
- Kernideen im Detail
- Delta (Row) vs. Main (Column)
- Kompression im Column-Store – der Deep-Dive 🔬
- Query-Router = der HTAP-Beweis
- Roadmap
- Design-Entscheidungen (Mini-ADR)
- Repo-Features, die paulDB nutzt
- Lernressourcen
- Status
paulDB 🦀🏳️🌈
Eine eigene HTAP-Datenbank in Rust. Row- und Column-Storage in einer In-Memory-Engine, verbunden durch ein Delta-Merge-Konzept nach dem Vorbild von SAP HANA. Gebaut, um zu verstehen – nicht nur zu benutzen.
Status: 🌱 Vision-Phase · Sprache: Rust · Modell: In-Memory HTAP · Lizenz: MIT
Was ist paulDB?
paulDB ist mein Versuch, eine Datenbank von Grund auf selbst zu schreiben – in Rust. Nicht, weil die Welt noch eine Datenbank braucht, sondern weil ich wissen will, warum Datenbanken so funktionieren, wie sie funktionieren.
Der rote Faden ist eine konkrete, ehrgeizige Frage:
Wie hält man OLTP und OLAP in einem System gleichzeitig schnell – also HTAP – und wie macht SAP HANA das mit Delta und Main?
paulDB ist die Antwort, die ich mir selbst mit Code gebe. Mein „Crafting Interpreters", nur für Datenbanken.
Der Nordstern: HTAP
Klassische Systeme zwingen zur Wahl zwischen zwei Welten:
| Workload | Optimiert für | Speicherform | Beispiel-Query |
|---|---|---|---|
| OLTP | viele kleine Schreib-/Lesezugriffe | Row-Store (Zeilen) | INSERT … · SELECT … WHERE id = 42 |
| OLAP | wenige große Aggregationen | Column-Store (Spalten) | SELECT kat, SUM(wert) … GROUP BY kat |
HTAP (Hybrid Transactional/Analytical Processing) will beides in einem System. Genau das ist der einzige Maßstab, an dem paulDB jede Entscheidung misst: Was den HTAP-Beweis schärft, kommt zuerst. Alles andere wartet.
Das HANA-Vorbild: Delta & Main
SAP HANA löst HTAP elegant über zwei Bereiche pro Tabelle:
flowchart LR
W[Schreibzugriffe] --> DELTA["DELTA<br/>Row-Store · schreib-optimiert"]
R[Leseanalysen] --> MAIN["MAIN<br/>Column-Store · lese-optimiert, komprimiert"]
DELTA -- "Delta-Merge (periodisch)" --> MAIN
- Delta – schreib-optimiert, nimmt neue Daten schnell auf (OLTP-Seite).
- Main – lese-optimiert, stark komprimiert, ideal für Analytik (OLAP-Seite).
- Delta-Merge – schiebt periodisch Delta → Main und hält Lesezugriffe schnell.
Genau dieses Prinzip ist das Herzstück von paulDB.
Architektur (in-memory, HTAP-zentriert)
In-Memory ist hier kein Kompromiss – es ist authentisch: HANA ist primär in-memory, Delta und Main leben im RAM. Persistenz auf Platte ist deshalb eine optionale spätere Etappe, nicht das Fundament.
flowchart TD
SQL["SQL-Frontend<br/>Tokenizer → AST → Planner"]
ROUTER{"Query-Router<br/>OLTP oder OLAP?"}
DELTA["DELTA · Row-Store<br/>(in-memory, schreib-optimiert)"]
MAIN["MAIN · Column-Store<br/>(in-memory, komprimiert)"]
MERGE["Delta-Merge-Worker<br/>(periodisch)"]
SQL --> ROUTER
ROUTER -->|"INSERT / Punkt-SELECT"| DELTA
ROUTER -->|"GROUP BY / Aggregation"| MAIN
DELTA -. "Lese-Queries sehen Delta ∪ Main" .-> MAIN
DELTA ==>|merge| MERGE ==> MAIN
Kernideen im Detail
Delta (Row) vs. Main (Column)
Eine Zeile in den Delta-Store zu schreiben ist billig – man hängt sie hinten an. Über eine Spalte im Main-Store zu aggregieren ist billig – alle Werte einer Spalte liegen zusammenhängend und cache-freundlich im Speicher. paulDB nutzt beide Stärken und überbrückt sie mit dem Merge.
Kompression im Column-Store – der Deep-Dive 🔬
Hier gehen wir so realistisch und tief wie möglich und bauen HANAs zweistufiges Modell nach.
Stufe 1 – Dictionary-Encoding (immer): Jede Spalte bekommt ein sortiertes Wörterbuch ihrer distinct-Werte. Die eigentliche Spalte wird zu einem Vektor aus Integer-Value-IDs (Index ins Wörterbuch). Das allein macht die Spalte klein und macht Scans/Aggregationen schnell, weil man über kompakte Integer statt über Strings läuft.
Stufe 2 – Advanced Compression auf den Value-ID-Vektor. Pro Spalte wird das günstigste Verfahren gewählt:
| Verfahren | Idee | Stark bei |
|---|---|---|
| Prefix | häufigen Anfangswert einmal speichern | Spalten mit dominantem Startwert |
| Run-Length (RLE) | „Wert × Anzahl" statt Wiederholungen | langen Wiederholungsläufen |
| Cluster | Blöcke aus wiederkehrenden Mustern | lokal geclusterten Werten |
| Sparse | häufigsten Wert weglassen, nur Ausnahmen speichern | dünn besetzten Spalten |
| Indirect | gemeinsame Werte über Blöcke indirekt referenzieren | mittlerer Kardinalität |
Das ist exakt die Familie, die HANA auf seinen Column-Store legt – und genau das Rabbit-Hole, in dem das „Warum" wohnt.
Query-Router = der HTAP-Beweis
Der Router ist der Moment, in dem paulDB sichtbar HTAP wird: dieselbe Engine bedient
ein SELECT … WHERE id = … über den Row-Pfad (Delta + Main) und ein
… GROUP BY … über den Column-Pfad (Main). Ein Lese-Query sieht dabei immer
Delta ∪ Main, damit frisch geschriebene Daten sofort in der Analytik auftauchen.
Roadmap
Etappenweise – jede Stufe ist für sich ein vollständiges Lernziel. (Reihenfolge ist Plan, kein Versprechen an einen Zeitpunkt. Erst wenn das SAP-Fundament steht.)
- E0 – Rust-Fundament + Repo-Setup ·
cargo, Projektstruktur, Tests, CI - E1 – In-Memory Row-Store (Delta) · Tabellen, Schema,
INSERT, Full-Scan - E2 – Mini-SQL-Parser · Tokenizer → AST → Executor:
INSERT+SELECT … WHERE - E3 – In-Memory Column-Store (Main) · spaltige Daten + tiefe Kompression (Dictionary → Prefix/RLE/Cluster/Sparse/Indirect)
- E4 – Delta-Merge ❤️ · periodischer Merge Delta(row) → Main(column) — der Nordstern
- E5 – Query-Router · Punktabfrage → Row-Pfad, Aggregation → Column-Pfad — der HTAP-Beweis
- E6 – SQL-Ausbau ·
GROUP BY,SUM/COUNT/AVG, dannJOIN - E7 – MVCC / Transaktionen (leicht) · konsistente Lesersicht während des Merge
- E8 – (optional) Persistenz · WAL + Snapshot: aus „Beweis" wird „echte DB"
Design-Entscheidungen (Mini-ADR)
Drei bewusste Weichen, an denen sich alles ausrichtet:
- Nordstern = HTAP-Beweis. Jede Etappe dient dem Ziel, Delta-Merge + Row/Column in einer Engine zu zeigen. Deshalb steht Delta-Merge bei E4, nicht am Ende.
- In-Memory zuerst. Authentisch zu HANA, schneller Erfolg. Platte ist optional (E8).
- Volles SQL als Fernziel. Start mit Mini-SQL (E2), bewusst auf
GROUP BY/JOINhingebaut (E6) – denn ohne Aggregationen kann man die OLAP-Seite nicht vorführen.
Komplexitäts-Notiz fürs Gefühl: ein Full-Scan ist O(n), ein späterer Index-Lookup
über einen B-Tree wäre O(\log n) – der Unterschied, der OLTP erst schnell macht.
Repo-Features, die paulDB nutzt
Mein Gitea ist überraschend mächtig für dieses Projekt:
- Projects (Kanban-Board) – die Roadmap-Etappen als sichtbare Karten (To Do / In Progress / Done).
- Milestones – E0–E8 als Meilensteine, gefüllt mit Issues.
- Packages → Cargo-Registry 🦀 – paulDB-Crates landen in meiner eigenen Rust-Registry statt auf crates.io.
- Actions (CI/CD) – bei jedem Push automatisch
cargo build+cargo test. - Wiki – Platz für tiefe Design-Docs jenseits dieses README.
Lernressourcen
Die Schultern, auf denen paulDB steht:
- 📘 Database Internals – Alex Petrov (Storage Engines, B-Trees, MVCC, WAL)
- 📗 Crafting Interpreters – Robert Nystrom (Tokenizer, AST – fürs SQL-Frontend)
- 💻 cstack/db_tutorial (eine DB Schritt für Schritt)
- 🗃️ SQLite Source Code (das Vorbild für „klein, robust, vollständig")
- 🦀 The Rust Programming Language („the Book") + Rust by Example
- 🟧 SAP HANA Administration Guide (Delta-Merge & Column-Store-Kompression im Original)
Status
🌱 Vision-Phase. paulDB ist aktuell ein Zuhause für einen Traum, der bewusst wartet, bis mein SAP-Fundament steht. Das ist kein Rückstand – das ist Reihenfolge.
Ein leeres Repo mit klarem Plan ist kein Hochstapeln. Es ist ein Versprechen an mich selbst.
von Paul Horn · gebaut, um zu verstehen · auf dem Weg nach BC 🏔️