Add optional zstd mbox archives
This commit is contained in:
parent
21844688ac
commit
e612896e06
14 changed files with 620 additions and 39 deletions
114
zstd-brief.md
Normal file
114
zstd-brief.md
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
# 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
|
||||
|
||||
`<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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue