5 KiB
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:
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_offsetseeken, genau diesen Frame entpacken,inner_offset/inner_lenherausschneiden. - Abwärtskompatibel: Bestehende plain
.mboxmü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
go test ./...grün.- Round-trip byte-identisch — das ist die wichtigste Prüfung für ein
Beweis-Archiv: Denselben Quell-Ordner einmal mit
noneund einmal mitzstdsichern. Dann muss gelten:zstd -d ordner.mbox.zst==ordner.mboxByte für Byte (cmp). Wenn nicht: Stopp. Ein Archiv, das nicht exakt zurückkommt, ist wertlos. - Rate messen an einer echten mbox (nicht an meinem Testkorpus — der ist durch die vielen identischen Benachrichtigungsmails unrealistisch gut).
- Idempotenz unverändert: zweiter Lauf
copied=0. - Anhänge (PDF/PNG/ZIP) kommen aus dem komprimierten Archiv byte-identisch wieder heraus.
- 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.