From ec5f2fe92bc5a46ea2398bf1c56f8deeabeae570 Mon Sep 17 00:00:00 2001 From: Paul Horn Date: Fri, 12 Jun 2026 22:51:32 +0200 Subject: [PATCH] Update README.md --- README.md | 202 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 137 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index 4e9dd46..74a8b6f 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,15 @@ +--- +include_toc: true +--- + # paulDB 🦀🏳️‍🌈 > **Eine eigene HTAP-Datenbank in Rust.** -> Row- *und* Column-Storage in einer Engine, mit einem Delta-Merge-Konzept, -> inspiriert von SAP HANA. Ein Langzeitprojekt – gebaut, um zu *verstehen*, nicht nur zu benutzen. +> 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` --- @@ -10,95 +17,159 @@ 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. Jede Zeile Code hier ist eine -Antwort auf eine Frage, die ich mir sonst nur theoretisch beantworten könnte: +Datenbanken so funktionieren, wie sie funktionieren. -- Wie speichert eine Engine Daten wirklich auf Platte und im Speicher? -- Wie wird aus `SELECT … WHERE …` ein ausgeführter Plan? -- Wie hält man OLTP (schnelle Einzelzugriffe) und OLAP (große Aggregationen) - in *einem* System gleichzeitig schnell – also **HTAP**? -- Wie macht HANA das mit **Delta** und **Main** – und kann ich das nachbauen? +Der rote Faden ist eine konkrete, ehrgeizige Frage: -paulDB ist mein „Crafting Interpreters", nur für Datenbanken. +> **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. --- -## Warum HTAP + Delta-Merge? +## Der Nordstern: HTAP -Klassische Systeme zwingen zur Wahl: +Klassische Systeme zwingen zur Wahl zwischen zwei Welten: -| | Optimiert für | Speicherform | -|---|---|---| -| **OLTP** | viele kleine Schreib-/Lesezugriffe | Row-Store (Zeilen) | -| **OLAP** | wenige große Aggregationen | Column-Store (Spalten) | +| 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. -SAP HANA löst das elegant über zwei Bereiche: +**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. -``` - Schreibzugriffe Leseanalysen - │ │ - ▼ ▼ - ┌─────────────┐ Delta-Merge ┌──────────────┐ - │ DELTA │ ───────────────▶ │ MAIN │ - │ (schreib- │ (periodisch) │ (lese- │ - │ optimiert) │ │ optimiert, │ - │ │ │ komprimiert) │ - └─────────────┘ └──────────────┘ +--- + +## Das HANA-Vorbild: Delta & Main + +SAP HANA löst HTAP elegant über zwei Bereiche pro Tabelle: + +```mermaid +flowchart LR + W[Schreibzugriffe] --> DELTA["DELTA
Row-Store · schreib-optimiert"] + R[Leseanalysen] --> MAIN["MAIN
Column-Store · lese-optimiert, komprimiert"] + DELTA -- "Delta-Merge (periodisch)" --> MAIN ``` -- **Delta**: schreib-optimiert, nimmt neue Daten schnell auf. -- **Main**: lese-optimiert, stark komprimiert, ideal für Analytik. -- **Delta-Merge**: schiebt periodisch Delta → Main, hält Lesezugriffe schnell. +- **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-Vision +## 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. + +```mermaid +flowchart TD + SQL["SQL-Frontend
Tokenizer → AST → Planner"] + ROUTER{"Query-Router
OLTP oder OLAP?"} + DELTA["DELTA · Row-Store
(in-memory, schreib-optimiert)"] + MAIN["MAIN · Column-Store
(in-memory, komprimiert)"] + MERGE["Delta-Merge-Worker
(periodisch)"] + SQL --> ROUTER + ROUTER -->|"INSERT / Punkt-SELECT"| DELTA + ROUTER -->|"GROUP BY / Aggregation"| MAIN + DELTA -. "Lese-Queries sehen Delta ∪ Main" .-> MAIN + DELTA ==>|merge| MERGE ==> MAIN ``` -┌──────────────────────────────────────────────────────┐ -│ SQL-Frontend │ -│ Parser → AST → Planner → Optimizer │ -├──────────────────────────────────────────────────────┤ -│ Execution Engine │ -│ (vektorisiert, wo es geht) │ -├───────────────────────────┬──────────────────────────┤ -│ Row-Store │ Column-Store │ -│ (OLTP / Delta) │ (OLAP / Main) │ -├───────────────────────────┴──────────────────────────┤ -│ Transaktionen: MVCC + WAL │ Delta-Merge-Worker │ -├──────────────────────────────────────────────────────┤ -│ Storage Layer (Pages, B-Tree) │ -└──────────────────────────────────────────────────────┘ -``` + +--- + +## 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.)* +*(Reihenfolge ist Plan, kein Versprechen an einen Zeitpunkt. Erst wenn das +SAP-Fundament steht.)* -- [ ] **Etappe 0 – Fundament**: Rust lernen, Projektstruktur, `cargo`, Tests -- [ ] **Etappe 1 – Storage Engine**: Pages, ein einfacher B-Tree, Lesen/Schreiben auf Platte -- [ ] **Etappe 2 – Row-Store + REPL**: Tabellen, Zeilen einfügen/lesen, kleine SQL-Teilmenge -- [ ] **Etappe 3 – SQL-Parser**: Tokenizer → AST → einfacher Planner -- [ ] **Etappe 4 – Transaktionen**: WAL (Write-Ahead-Log), Crash-Recovery, ACID-Grundlagen -- [ ] **Etappe 5 – MVCC**: Multi-Version-Concurrency, mehrere Leser/Schreiber -- [ ] **Etappe 6 – Column-Store**: spaltige Speicherung, Kompression, vektorisierte Aggregation -- [ ] **Etappe 7 – Delta-Merge**: Delta + Main, periodischer Merge-Worker – das HTAP-Herz ❤️ -- [ ] **Etappe 8 – Optimizer**: Statistiken, Histogramme, kostenbasierte Planwahl +- [ ] **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`, dann `JOIN` +- [ ] **E7 – MVCC / Transaktionen (leicht)** · konsistente Lesersicht während des Merge +- [ ] **E8 – *(optional)* Persistenz** · WAL + Snapshot: aus „Beweis" wird „echte DB" --- -## Prinzipien +## Design-Entscheidungen (Mini-ADR) -- **Verstehen vor Benutzen.** Jedes Modul kommentiert das *Warum*, nicht nur das *Was*. -- **Klein, aber echt.** Lieber eine simple Engine, die wirklich läuft, als ein leeres Framework. -- **Analogien nutzen.** paulDB ↔ Postgres ↔ HANA ↔ DuckDB – Vergleiche machen Konzepte greifbar. -- **Ehrlich zum Stand.** Was läuft, läuft. Was noch nicht da ist, steht offen in der Roadmap. +Drei bewusste Weichen, an denen sich alles ausrichtet: + +1. **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. +2. **In-Memory zuerst.** Authentisch zu HANA, schneller Erfolg. Platte ist optional (E8). +3. **Volles SQL als Fernziel.** Start mit Mini-SQL (E2), bewusst auf `GROUP BY`/`JOIN` + hingebaut (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. --- @@ -106,11 +177,12 @@ Etappenweise – jede Stufe ist für sich ein vollständiges Lernziel. Die Schultern, auf denen paulDB steht: -- 📘 **Database Internals** – Alex Petrov *(Storage Engines, B-Trees, verteilte Systeme)* -- 📗 **Crafting Interpreters** – Robert Nystrom *(Parser, AST, Interpreter – fürs SQL-Frontend)* -- 💻 **[cstack/db_tutorial](https://cstack.github.io/db_tutorial/)** *(eine simple DB Schritt für Schritt in C)* +- 📘 **Database Internals** – Alex Petrov *(Storage Engines, B-Trees, MVCC, WAL)* +- 📗 **Crafting Interpreters** – Robert Nystrom *(Tokenizer, AST – fürs SQL-Frontend)* +- 💻 **[cstack/db_tutorial](https://cstack.github.io/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)* ---