7.2 KiB
Codex-Brief — Korrekturen aus dem zstd-Messlauf
Vier Befunde aus meinem Messlauf (14.07.). Reihenfolge = Priorität.
1 und 2 sind Pflicht vor der ersten echten @dr-gold.de-Migration.
1. 🔴 REGRESSION: Alte plain-Archive sind im Viewer unsichtbar
Befund: Die Nachrichtenliste kommt jetzt aus mbox_index. Archive, die
vor dem zstd-Deploy geschrieben wurden, haben keine Index-Zeilen:
mbox_index: codex-abnahme.../INBOX -> 1 Zeile (Datei enthaelt 207 Mails)
Viewer zeigt: "INBOX (1)"
Das Perfide: Es zeigt „(1)" statt eines Fehlers. Ein halb-wahres Ergebnis ist schlimmer als gar keins — es sieht aus, als funktioniere es. Der im zstd-Brief geforderte Fallback auf den alten Parse-Pfad fehlt.
Fix — die mbox ist append-only, also reicht ein Fortschritts-Marker pro Datei:
CREATE TABLE IF NOT EXISTS mbox_index_state(
account_id INTEGER NOT NULL,
folder TEXT NOT NULL,
indexed_bytes INTEGER NOT NULL DEFAULT 0, -- bis hierhin ist die Datei indiziert
UNIQUE(account_id, folder)
);
- Nachindizieren: Wenn
dateigroesse > indexed_bytes→ abindexed_bytesbis EOF scannen, Index-Zeilen anhaengen,indexed_bytesfortschreiben. Deckt leeren und teilweisen Index gleichermassen ab. - Der Migrationspfad schreibt
indexed_bytesbeim Append mit fort — dann muss im Normalbetrieb nie nachgescannt werden. - CLI
--reindex <konto|all>fuer den Bestand. - Der Scanner muss beide Formate koennen: plain (
From-Trennzeilen) und.zst(Frame fuer Frame entpacken, dann innerhalb des Frames an denFrom-Trennzeilen splitten). Das ist genau die Logik, die Punkt 4 spaeter auch fuer Batch-Frames braucht — einmal richtig bauen. - Nie wieder still halbe Wahrheiten: Wenn
dateigroesse != indexed_bytesund nicht nachindiziert werden kann → Fehler zeigen, keine gekuerzte Liste.
2. 🔴 Ziel-Dedup-Werkzeug (die Feuerwehr)
Befund: In vdevop-03/INBOX existieren 199 Message-IDs doppelt — die
INBOX wurde irgendwann komplett ein zweites Mal migriert. Niemand hat es gemerkt,
weil im Log nur copied=199 stand und wie normaler Fortschritt aussah.
Die Ursachen sind inzwischen dicht (Message-ID-Normalisierung, DB-Guard, Watch-Fail-Fast). Aber es gibt kein Werkzeug, das Dubletten im Ziel findet oder repariert. Wenn das bei einem echten dr-gold-Postfach passiert, gibt es keinen Ausweg — und das merkt man erst, wenn es zu spaet ist.
Fix: --dedup-target <konto> [--folders ...] [--apply] (+ Admin-Route).
- Ordner scannen, header-only (
Headers()-Pfad, keine Bodies!), nach normalisierter Message-ID gruppieren. - Pro Gruppe mit >1 Kopie: die niedrigste UID behalten (die aelteste, zuerst
eingespielte), den Rest
\Deleted+EXPUNGE.
Sicherheitsregeln — das Ding loescht fremde Mail, ein Bug hier ist schlimmer als die Dubletten:
- Dry-Run ist Default. Ohne
--applywird nur berichtet: pro Ordner, wie viele Gruppen, wie viele ueberzaehlige Kopien, welche UIDs. - Nie die letzte Kopie loeschen. Pro Message-ID bleibt immer genau eine.
- Mails ohne Message-ID nie anfassen — nicht sicher dedupliizierbar. Separat auszaehlen und berichten.
- Jede Loeschung protokollieren (Konto, Ordner, UID, Message-ID) — es braucht eine Beweiskette.
- Nur
requireAdmin.
3. 🟡 folder_map ist toter Code
Die Tabelle wird angelegt, aber nirgends gelesen:
07-migrate.go:122 uebergibt den Override hart als "":
dstFolder := MapSourceToTarget(folder.Name, folder.Attrs, folder.Delim, dst.Delim(), targets, "")
// ^^ immer leer
MapSourceToTarget hat den Parameter, 11-folders.go dokumentiert „manuelles
folder_map hat Vorrang" — es tut es nur nicht. Ein dokumentiertes Feature, das
stillschweigend nichts tut. (Genau daran ist mein Messlauf haengengeblieben: die
Umleitung wurde ignoriert und 207 Mails landeten im echten Ziel-Ordner.)
Fix: folder_map pro Konto laden und als override durchreichen. Plus
Pflege in der UI (Konten-Formular: Quell-Ordner → Ziel-Ordner).
4. 🟢 Kompressionsrate — gemessen, und die Entscheidung dazu
| Groesse | Faktor | |
|---|---|---|
| plain mbox (207 Mails) | 4.372.002 | — |
| zstd, Frame pro Mail (heute) | 1.470.412 | 2,97× |
| zstd, ganze Datei am Stueck | 113.564 | 38,50× |
Der Abstand ist die Cross-Message-Redundanz, die Frame-pro-Mail wegwirft. (Mein Korpus uebertreibt ihn — 199 fast identische Benachrichtigungsmails. Auf echter Mail waere die Luecke kleiner, aber real.)
Was wir NICHT machen: zstd-Dictionary. Das waere technisch der eleganteste
Weg (genau dafuer sind Dictionaries da), aber es macht das Archiv abhaengig von
einer externen Datei: Dictionary weg → Archiv unlesbar. Fuer ein Beweis-Archiv,
das 6–10 Jahre halten und im Streitfall herausgegeben werden muss, ist
„zstd -d funktioniert einfach, ohne Beiwerk" mehr wert als jeder
Kompressionsfaktor. Selbstgenuegsamkeit schlaegt Rate.
Was wir machen koennen: Batch-Frames mit kleinem Deckel (2 MB).
Der Index (inner_offset/inner_len) ist bereits dafuer gebaut.
Aber die Durability-Regel ist dabei nicht verhandelbar:
Bei Batching ist eine Mail nach mbox.Append noch nicht auf der Platte — sie
liegt im Frame-Puffer. Deshalb:
Frame voll (oder Ordner zu Ende)
-> Frame komprimieren
-> KOMPLETTEN Frame an die Datei anhaengen + fsync
-> DANN erst: Index-Zeilen schreiben
-> DANN erst: MarkCopied fuer alle Mails des Batches (eine Transaktion)
MarkCopied darf niemals vor dem fsync passieren — sonst gilt eine Mail als
gesichert, die nie im Archiv ankam. Das waere der schlimmstmoegliche Bug fuer ein
Beweis-Archiv.
Nebenwirkung, die man kennen muss: Ein Absturz mitten im Batch laesst bis zu 2 MB Mails unmarkiert → sie werden beim naechsten Lauf erneut ins IMAP-Ziel kopiert → Dubletten dort. Dasselbe Fenster wie heute, nur groesser (Batch statt Einzelmail). Deshalb der kleine Deckel (2 MB, nicht 8) — und deshalb ist Punkt 2 (Dedup-Werkzeug) die Voraussetzung dafuer.
Priorität: niedriger als 1–3. 2,97× ist bereits ein Gewinn, und die Vorschau wurde durch den Index sogar 6× schneller (1,8 ms statt 10,8 ms). Korrektheit zuerst.
Abnahme
go test ./...gruen.- Reindex:
--reindex all→ der Viewer zeigt fuer das bestehende plain-Archiv wieder alle 207 Mails (statt „(1)"). Liste und Oeffnen funktionieren fuer plain und.zst. - Dedup, Dry-Run:
--dedup-target codex-abnahme-vdevop02-to-vdevop03meldet fuerINBOX199 Gruppen mit je einer ueberzaehligen Kopie (das ist der bekannte, echte Bestand — ein guter Selbsttest). Mit--applybleiben danach genau 365 eindeutige Mails uebrig. Ohne--applydarf sich nichts aendern. - folder_map: Ein Eintrag
INBOX → TEST-ZIELfuehrt im Log zuINBOX -> TEST-ZIEL(heute:INBOX -> INBOX). - Round-trip bleibt byte-identisch — auch nach jeder Aenderung an der
Frame-Struktur:
zstd -d x.mbox.zst== plain-mbox,cmp-sauber. Das ist die Linie, die nie fallen darf.
Die Messungen (Rate, Vorschau-Latenz, Round-trip) fahre ich danach erneut.