Update README.md

This commit is contained in:
2026-06-12 22:51:32 +02:00
parent dc2ac38058
commit ec5f2fe92b
+137 -65
View File
@@ -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<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.
- **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<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
```
┌──────────────────────────────────────────────────────┐
│ 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** E0E8 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)*
---