feat(PROJ-58): Indexierung + OCR optional als Cron-Batch statt Dauerbetrieb

Neues config.yml-Feld batch_mode (index/ocr, Default false = unverändertes
Verhalten). Bei batch_mode:true verarbeiten neue Cron-Jobs (index-pending,
ocr-reprocess) die Backlogs in größeren Abständen statt sofort bei jedem
Mail-Import, um Schreiblast auf der Festplatte zu glätten. Zeiten in
/etc/cron.d/archivmail frei anpassbar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sysops
2026-06-24 23:07:17 +02:00
co-authored by Claude Sonnet 4.6
parent 76655f78a2
commit fae274f930
10 changed files with 407 additions and 20 deletions
+64
View File
@@ -0,0 +1,64 @@
# PROJ-58: Indexierung + OCR als Cron-Batch-Jobs (statt Dauerbetrieb)
## Status: Deployed
**Created:** 2026-06-24
**Last Updated:** 2026-06-24
## Dependencies
- PROJ-30 (Manticore-Indexierung)
- PROJ-35 (OCR & Anhang-Volltext-Indexierung)
- PROJ-56 (Last-Entzerrung für Hintergrundjobs — verwandtes Cron-Muster)
## Hintergrund (Nutzerwunsch)
Aktuell laufen Indexierung (`internal/index/tenant_worker.go`) und OCR (`internal/ocr/worker.go`) sofort und nebenläufig bei jedem Mail-Import als Dauerbetrieb-Goroutinen. Das erzeugt viele kleine Schreibzugriffe auf die Festplatte (Manticore-Writes, OCR-Tesseract-Output, DB-Updates) statt geblockter Batches. Nutzerwunsch: Beide Prozesse sollen optional in größeren, **per `cron.d` konfigurierbaren Zeitabständen** laufen, damit die Zeiten später selbst angepasst werden können (analog zum bestehenden Purge-Cron, PROJ-56c).
## Bestehende Bausteine (bereits vorhanden, lt. Code-Analyse)
- `indexed_at TIMESTAMPTZ` (storage.go) markiert bereits indexierte Mails — Query auf `indexed_at IS NULL` liefert die Pending-Liste ohne neue Spalte.
- `ocr_status` (pending/done/failed/skipped/disabled) ist bereits vollständig vorhanden.
- `cmd_ocr_reprocess.go` ist bereits "lade Batch → verarbeite → beenden" und damit direkt cron-fähig.
- Es fehlt ein äquivalenter Batch-Befehl für die Indexierung (aktuell nur `cmd_reindex.go`, das *immer alle* Mails neu indexiert statt nur die ungeindexten — ungeeignet für einen häufigen Cron-Lauf).
## Entscheidung (Nutzer, 2026-06-24)
- Modus per Config umschaltbar (`index.batch_mode`, `ocr.batch_mode`), Default `false` = aktuelles Verhalten unverändert (non-breaking, analog PROJ-56).
- Bei `batch_mode: true` wird der jeweilige Dauerbetrieb-Worker im Daemon **nicht gestartet**; neue Mails bleiben bis zum nächsten Cron-Lauf mit `indexed_at IS NULL` / `ocr_status='pending'` in der DB stehen (kein Datenverlust, nur verzögerte Sichtbarkeit in Suche/OCR).
- Zeiten stehen in `/etc/cron.d/archivmail`, frei editierbar, mit demselben Kommentarstil wie der bestehende OCR-Pausen- und Purge-Cron.
## Acceptance Criteria
- [x] Neuer CLI-Befehl `archivmail index-pending --config ... --limit N` lädt Mails mit `indexed_at IS NULL` (Query-Pattern analog `cmd_ocr_reprocess.go`), indexiert sie über den `TenantIndexWorker`, wartet auf vollständiges Drain, beendet sich danach.
- [x] `config.yml`: neue Felder `index.batch_mode` (bool, default false) und `ocr.batch_mode` (bool, default false).
- [x] Bei `batch_mode: true` wird der jeweilige Worker beim Daemon-Start nicht gestartet und der Upload-Pfad submitted nicht mehr in den In-Memory-Channel (kein sinnloses Queue-Volllaufen/Log-Spam).
- [x] Bei `batch_mode: false` (Default) bleibt das bisherige Verhalten 1:1 erhalten — keine Regression für bestehende Installationen.
- [x] `deploy/cron.d/archivmail` bekommt zwei neue, kommentierte Cron-Zeilen für `index-pending` und `ocr-reprocess --status pending`, mit Beispiel-Intervall (z.B. alle 15 Minuten), klar als "Zeiten hier anpassen" markiert — analog zum bestehenden OCR-Pausenfenster-Kommentarstil.
- [x] Boot-Resume-Goroutinen (OCR-Backfill in main.go, Index-Backfill `runBackfill`) laufen nur, wenn der jeweilige `batch_mode` **nicht** aktiv ist (sonst übernimmt der Cron-Job diese Aufgabe).
- [x] Dokumentation im Cron-File erklärt, dass bei `batch_mode: true` neue Mails erst nach dem nächsten Cron-Lauf durchsuchbar/OCR-bearbeitet sind.
## Tech Design
Übersprungen (klar umrissene, additive Konfigurationsoption mit bestehenden Bausteinen — kein architektonischer Schnitt, analog PROJ-56).
## Implementation Notes (2026-06-24)
### Geänderte/neue Dateien
- `config/config.go`: neues Feld `BatchMode bool` (`yaml:"batch_mode"`, default false) in `IndexConfig` und `OCRConfig`. Additiv/non-breaking, kein Pointer nötig da `false` der gewünschte Default ist.
- `internal/storage/storage.go`: neue Funktion `GetUnindexedMails(ctx, limit)` + Typ `UnindexedMail{ID, TenantID}` — Query `WHERE indexed_at IS NULL ORDER BY received_at DESC` (analog zu `GetMailsByOCRStatus`).
- `cmd/archivmail/cmd_index_pending.go` (neu): CLI-Befehl `index-pending` (Flags `--config`, `--limit`), Vorbild `cmd_ocr_reprocess.go`. Lädt ungeindexte Mails, parst sie, baut `index.MailDocument`, queued sie auf einen frisch erstellten `TenantIndexWorker` (Queue = batch+16, kein Drop), setzt `indexed_at`, wartet via `worker.Stop()` auf vollständiges Drain, beendet sich.
- `cmd/archivmail/main.go`: Befehl im Dispatcher registriert. Daemon-Start gated: bei `cfg.Index.BatchMode` kein `tenantWorker.Start()` und kein `runBackfill`; bei `cfg.OCR.BatchMode` kein `ocrWorker.Start()`, keine OCR-Boot-Resume-Goroutine, keine IMAP/POP3-`SetOCRSubmit`-Hooks. `submitToWorker()` um zwei Flags (`indexBatchMode`, `ocrBatchMode`) erweitert → überspringt die jeweiligen In-Memory-Submits (kein Queue-Volllaufen / Log-Spam). Mails behalten dabei `indexed_at IS NULL` bzw. `ocr_status='pending'` und werden vom Cron-Job nachgezogen.
- `deploy/cron.d/archivmail`: zwei neue, kommentierte Zeilen (`index-pending` `*/15`, `ocr-reprocess --status pending` `5,20,35,50`), klar als nur-bei-`batch_mode:true`-relevant und frei editierbar markiert.
- `config/config.docker.yml.example`: auskommentierte Beispiele für `index.batch_mode` und `ocr.batch_mode`.
### Design-Entscheidungen / Abweichungen
- `BatchMode` ist ein einfacher `bool` (kein Pointer wie bei PROJ-56 `JitterSeconds`), da hier `false` = Default = gewünschtes Alt-Verhalten; eine Unterscheidung unset/explizit-false ist nicht nötig.
- IMAP/POP3-Importer indexieren synchron direkt über `idxMgr` (nicht über den `TenantIndexWorker`) — dieser Pfad bleibt unverändert; `index.batch_mode` betrifft bewusst nur den Worker-/SMTP-Upload-Pfad (Schreiblast-Glättung des Async-Workers). OCR-Hooks der Importer werden hingegen bei `ocr.batch_mode` deaktiviert, da OCR ausschließlich über den Worker läuft.
- Lokal kein `go build` möglich (kein Toolchain) — nur statische Konsistenzprüfung; Build-Verifikation auf 192.168.1.131/132.
## QA Test Results (192.168.1.132, 2026-06-24)
- Build: `CGO_ENABLED=0 go build -buildvcs=false -o archivmail ./cmd/archivmail/` → Exit 0.
- `go vet ./...`: nur vorbestehende, PROJ-58-unabhängige Befunde (xapian_wrapper.cpp/cgo, storage.go self-assignment, storage_test.go-Signatur). Keine neuen Befunde durch PROJ-58.
- Default-Verhalten (`batch_mode` unset/false): Live-Service unverändert weitergelaufen, kontinuierliche Indexierung/OCR + Boot-Backfill bestätigt aktiv, `/api/health` ok.
- `batch_mode: true` (isolierte Test-Config): Daemon startet, loggt "batch mode enabled — continuous worker not started" für beide Worker, kein Backfill/Boot-Resume, keine kontinuierliche Verarbeitung — wie spezifiziert.
- `index-pending --limit 5` gegen Test-DB: 5 ungeindexte Mails geladen, Worker drained, Exit 0.
- Minor-Finding behoben: `index-pending` fehlte in `printHelp()` (cmd_import.go) — ergänzt.
- Keine Critical/High-Findings. Server 192.168.1.131 nicht angefasst während der QA.
## Out of Scope
- Kein Wechsel der bestehenden Mechanismen für Installationen, die `batch_mode` nicht setzen.
- Kein UI/Admin-Schalter im Frontend — Konfiguration ausschließlich über `config.yml` + `cron.d`.