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 🦀🏳️‍🌈 # paulDB 🦀🏳️‍🌈
> **Eine eigene HTAP-Datenbank in Rust.** > **Eine eigene HTAP-Datenbank in Rust.**
> Row- *und* Column-Storage in einer Engine, mit einem Delta-Merge-Konzept, > Row- *und* Column-Storage in *einer* In-Memory-Engine, verbunden durch ein
> inspiriert von SAP HANA. Ein Langzeitprojekt gebaut, um zu *verstehen*, nicht nur zu benutzen. > 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. 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** 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 Datenbanken so funktionieren, wie sie funktionieren.
Antwort auf eine Frage, die ich mir sonst nur theoretisch beantworten könnte:
- Wie speichert eine Engine Daten wirklich auf Platte und im Speicher? Der rote Faden ist eine konkrete, ehrgeizige Frage:
- 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?
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 | | Workload | Optimiert für | Speicherform | Beispiel-Query |
|---|---|---| |---|---|---|---|
| **OLTP** | viele kleine Schreib-/Lesezugriffe | Row-Store (Zeilen) | | **OLTP** | viele kleine Schreib-/Lesezugriffe | Row-Store (Zeilen) | `INSERT …` · `SELECT … WHERE id = 42` |
| **OLAP** | wenige große Aggregationen | Column-Store (Spalten) | | **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. **HTAP** (Hybrid Transactional/Analytical Processing) will *beides* in einem System.
SAP HANA löst das elegant über zwei Bereiche: 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
│ │ ## Das HANA-Vorbild: Delta & Main
▼ ▼
┌─────────────┐ Delta-Merge ┌──────────────┐ SAP HANA löst HTAP elegant über zwei Bereiche pro Tabelle:
│ DELTA │ ───────────────▶ │ MAIN │
│ (schreib- │ (periodisch) │ (lese- │ ```mermaid
│ optimiert) │ │ optimiert, │ flowchart LR
│ │ │ komprimiert) │ 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. - **Delta** schreib-optimiert, nimmt neue Daten schnell auf (OLTP-Seite).
- **Main**: lese-optimiert, stark komprimiert, ideal für Analytik. - **Main** lese-optimiert, stark komprimiert, ideal für Analytik (OLAP-Seite).
- **Delta-Merge**: schiebt periodisch Delta → Main, hält Lesezugriffe schnell. - **Delta-Merge** schiebt periodisch Delta → Main und hält Lesezugriffe schnell.
Genau dieses Prinzip ist das Herzstück von paulDB. 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 │
├──────────────────────────────────────────────────────┤ ## Kernideen im Detail
│ Execution Engine │
│ (vektorisiert, wo es geht) │ ### Delta (Row) vs. Main (Column)
├───────────────────────────┬──────────────────────────┤
│ Row-Store │ Column-Store │ Eine Zeile in den **Delta**-Store zu schreiben ist billig man hängt sie hinten an.
│ (OLTP / Delta) │ (OLAP / Main) │ Ü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
│ Transaktionen: MVCC + WAL │ Delta-Merge-Worker │ Stärken und überbrückt sie mit dem Merge.
├──────────────────────────────────────────────────────┤
│ Storage Layer (Pages, B-Tree) │ ### 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 ## Roadmap
Etappenweise jede Stufe ist für sich ein vollständiges Lernziel. 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 - [ ] **E0 Rust-Fundament + Repo-Setup** · `cargo`, Projektstruktur, Tests, CI
- [ ] **Etappe 1 Storage Engine**: Pages, ein einfacher B-Tree, Lesen/Schreiben auf Platte - [ ] **E1 In-Memory Row-Store (Delta)** · Tabellen, Schema, `INSERT`, Full-Scan
- [ ] **Etappe 2 Row-Store + REPL**: Tabellen, Zeilen einfügen/lesen, kleine SQL-Teilmenge - [ ] **E2 Mini-SQL-Parser** · Tokenizer → AST → Executor: `INSERT` + `SELECT … WHERE`
- [ ] **Etappe 3 SQL-Parser**: Tokenizer → AST → einfacher Planner - [ ] **E3 In-Memory Column-Store (Main)** · spaltige Daten + tiefe Kompression (Dictionary → Prefix/RLE/Cluster/Sparse/Indirect)
- [ ] **Etappe 4 Transaktionen**: WAL (Write-Ahead-Log), Crash-Recovery, ACID-Grundlagen - [ ] **E4 Delta-Merge** ❤️ · periodischer Merge Delta(row) → Main(column) — *der Nordstern*
- [ ] **Etappe 5 MVCC**: Multi-Version-Concurrency, mehrere Leser/Schreiber - [ ] **E5 Query-Router** · Punktabfrage → Row-Pfad, Aggregation → Column-Pfad — *der HTAP-Beweis*
- [ ] **Etappe 6 Column-Store**: spaltige Speicherung, Kompression, vektorisierte Aggregation - [ ] **E6 SQL-Ausbau** · `GROUP BY`, `SUM/COUNT/AVG`, dann `JOIN`
- [ ] **Etappe 7 Delta-Merge**: Delta + Main, periodischer Merge-Worker das HTAP-Herz ❤️ - [ ] **E7 MVCC / Transaktionen (leicht)** · konsistente Lesersicht während des Merge
- [ ] **Etappe 8 Optimizer**: Statistiken, Histogramme, kostenbasierte Planwahl - [ ] **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*. Drei bewusste Weichen, an denen sich alles ausrichtet:
- **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. 1. **Nordstern = HTAP-Beweis.** Jede Etappe dient dem Ziel, Delta-Merge + Row/Column
- **Ehrlich zum Stand.** Was läuft, läuft. Was noch nicht da ist, steht offen in der Roadmap. 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: Die Schultern, auf denen paulDB steht:
- 📘 **Database Internals** Alex Petrov *(Storage Engines, B-Trees, verteilte Systeme)* - 📘 **Database Internals** Alex Petrov *(Storage Engines, B-Trees, MVCC, WAL)*
- 📗 **Crafting Interpreters** Robert Nystrom *(Parser, AST, Interpreter fürs SQL-Frontend)* - 📗 **Crafting Interpreters** Robert Nystrom *(Tokenizer, AST fürs SQL-Frontend)*
- 💻 **[cstack/db_tutorial](https://cstack.github.io/db_tutorial/)** *(eine simple DB Schritt für Schritt in C)* - 💻 **[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")* - 🗃️ **SQLite Source Code** *(das Vorbild für „klein, robust, vollständig")*
- 🦀 **The Rust Programming Language** („the Book") + **Rust by Example** - 🦀 **The Rust Programming Language** („the Book") + **Rust by Example**
- 🟧 **SAP HANA Administration Guide** *(Delta-Merge & Column-Store-Kompression im Original)*
--- ---