Mail-Graveyard/zstd-brief.md
2026-07-14 20:20:30 +02:00

114 lines
5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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: 24×.)
## Format
`<ordner>.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.