# Codex-Brief — mbox-Kompression mit zstd-3 Ziel: Das Datengrab schrumpfen, **ohne** den verifizierten Append-only-Pfad und die Vorschau kaputtzumachen. Danach vergleichen wir gemessen: Größe und Vorschau-Latenz, komprimiert vs. plain. ## Gemessene Ausgangslage (echte mbox auf GoldPi) | mbox | roh | zstd-3 | zstd-19 | |---|---|---|---| | INBOX.mbox | 4.372.002 | 113.564 (**38,5×**) | 111.017 (39,4×) | | Test-Haupt-Ordner | 51.699 | 11.316 (**4,6×**) | 10.677 (4,8×) | **zstd-19 bringt nur ~2 % mehr als zstd-3, kostet aber ein Vielfaches an Zeit → Level 3.** (Die 38× sind ein Artefakt: die INBOX besteht großteils aus fast identischen Benachrichtigungsmails. Realistisch bei echten Postfächern: 2–4×.) ## Format `.mbox.zst` = **Folge von zstd-Frames**. Konkatenierte Frames sind eine gültige `.zst` — `zstd -d datei.mbox.zst` liefert **exakt** die mbox, die wir sonst plain geschrieben hätten. Damit bleibt der Append-only-Charakter erhalten: neue Mail = neuer Frame hinten dran. **Start mit: ein Frame pro Mail.** Das macht die Vorschau trivial schnell (genau ein Frame entpacken). Nachteil: keine Redundanz *zwischen* Mails → schlechtere Rate. Falls die gemessene Rate enttäuscht, schalten wir auf **Batch-Frames mit 8-MB-Deckel** um (bessere Rate, Vorschau entpackt dann ≤ 8 MB ≈ 30 ms). **Der Index unten ist so gebaut, dass dieser Wechsel nichts kostet** — bitte die Felder auch dann schon so anlegen. Bibliothek: **`github.com/klauspost/compress/zstd`** — pure Go, kein cgo, bleibt single-binary. ## Offset-Index (der Kern) Neue Tabelle, geschrieben im selben Schritt wie der mbox-Append: ```sql CREATE TABLE IF NOT EXISTS mbox_index( account_id INTEGER NOT NULL, folder TEXT NOT NULL, seq INTEGER NOT NULL, -- laufende Nummer im Ordner message_id TEXT NOT NULL, subject TEXT NOT NULL DEFAULT '', from_addr TEXT NOT NULL DEFAULT '', date TEXT NOT NULL DEFAULT '', file_offset INTEGER NOT NULL, -- Byte-Offset des FRAMES in der Datei frame_len INTEGER NOT NULL, -- Laenge des Frames (komprimiert) inner_offset INTEGER NOT NULL DEFAULT 0, -- Offset der Mail IM entpackten Frame inner_len INTEGER NOT NULL, -- Laenge der Mail entpackt UNIQUE(account_id, folder, message_id) ); ``` (`inner_offset` ist bei „ein Frame pro Mail" immer 0 — er ist die Vorbereitung für Batch-Frames.) **Das schenkt uns nebenbei einen Bug-Fix:** Die Liste im Viewer parst heute die *ganze* mbox-Datei (`ReadMboxList`). Künftig kommt sie aus dem Index → **kein Dateizugriff mehr, schneller als heute**, auch bei einem 10-GB-Archiv. ## Schreibpfad — die Reihenfolge bleibt heilig In `07-migrate.go` **nicht umsortieren**. Neu ist nur, dass der mbox-Append Offsets zurückgibt und danach der Index geschrieben wird: ``` AlreadyCopied → dst.Append → mbox.Append (liefert offset/len) → Index-Zeile → MarkCopied ``` `MarkCopied` bleibt **das Letzte**. Ein Absturz davor kopiert die Mail beim nächsten Lauf erneut — dieselbe bewusste Entscheidung wie bisher. ## Lesepfad - **Liste** → nur SQLite (`mbox_index`), keine Datei anfassen. - **Eine Mail öffnen** → `file_offset` seeken, **genau diesen Frame** entpacken, `inner_offset`/`inner_len` herausschneiden. - **Abwärtskompatibel:** Bestehende **plain `.mbox`** müssen weiter funktionieren. Erkennung über Endung bzw. zstd-Magic (`0xFD2FB528`). Wenn für eine Datei keine Index-Zeilen existieren → alter Parse-Pfad. ## Umschaltbar (wichtig für den Vergleich) `config.json`: `"mbox_compression": "none" | "zstd"` (Default zunächst `none`, damit nichts überrascht). So können wir **dasselbe Postfach zweimal sichern** — einmal plain, einmal zstd — und sauber vergleichen. ## Export (die harte Anforderung bleibt) Eine Funktion/Route **„Archiv als plain mbox exportieren"**: entpackt die `.mbox.zst` zu einer normalen `.mbox`. Damit bleibt die Zusage erhalten, die der Grund für mbox war: **man kann sie in eM Client ziehen** bzw. einem Anwalt oder Prüfer in die Hand geben. Ein Handgriff statt null — akzeptabel. ## Abnahme 1. `go test ./...` grün. 2. **Round-trip byte-identisch** — das ist die wichtigste Prüfung für ein Beweis-Archiv: Denselben Quell-Ordner einmal mit `none` und einmal mit `zstd` sichern. Dann muss gelten: `zstd -d ordner.mbox.zst` == `ordner.mbox` **Byte für Byte** (`cmp`). Wenn nicht: **Stopp.** Ein Archiv, das nicht exakt zurückkommt, ist wertlos. 3. **Rate messen** an einer echten mbox (nicht an meinem Testkorpus — der ist durch die vielen identischen Benachrichtigungsmails unrealistisch gut). 4. Idempotenz unverändert: zweiter Lauf `copied=0`. 5. Anhänge (PDF/PNG/ZIP) kommen aus dem komprimierten Archiv byte-identisch wieder heraus. 6. **Vorschau-Latenz**: Mail öffnen aus plain vs. aus zstd — Zeit messen. (Diesen Vergleich fahre ich, Claude.) ## NICHT in diesem Schritt - Bestehende plain-mbox nachträglich komprimieren (Migration alter Archive) — eigener, späterer Schritt. - Batch-Frames mit 8-MB-Deckel — nur falls die gemessene Rate enttäuscht.