Files
archivmail/features/PROJ-58-cron-batch-index-ocr.md
T
sysopsandClaude Sonnet 4.6 46502abe75 fix(PROJ-58): Lockfile gegen überlappende Cron-Batch-Läufe + update.sh synct Cron-Dateien
Root Cause für ausbleibende Lastsenkung: update.sh hat /etc/cron.d/archivmail
nie auf den Server kopiert, daher fehlten die PROJ-58-Cronzeilen trotz
aktivem batch_mode komplett. Jetzt kopiert update.sh die Cron-Datei und alle
Wrapper-Skripte bei jedem Deploy automatisch ein.

Zusätzlich: Wrapper-Skripte (analog mailpiler indexer.delta.sh) verhindern
per Lockfile, dass sich Cron-Läufe bei großem Backlog überlappen.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 23:54:37 +02:00

9.0 KiB

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

  • 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.
  • config.yml: neue Felder index.batch_mode (bool, default false) und ocr.batch_mode (bool, default false).
  • 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).
  • Bei batch_mode: false (Default) bleibt das bisherige Verhalten 1:1 erhalten — keine Regression für bestehende Installationen.
  • 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.
  • 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).
  • 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.

Nachtrag (2026-06-24): Lockfile-Schutz gegen überlappende Cron-Läufe

Nutzer wies auf mailpiler-Vorbild hin (indexer.delta.sh, PID-Lockfile-Muster), um zu verhindern, dass sich Cron-Läufe bei großem Backlog überlappen (würde die Last-Glättung wieder aufheben). Umgesetzt:

  • Neue Wrapper-Skripte deploy/cron.d/archivmail-index-pending.sh und deploy/cron.d/archivmail-ocr-reprocess.sh — Lockfile unter /var/run/archivmail/*.lock, analog zu Pilers MAINTMPFILE/DELTATMPFILE-Muster (Datei mit Zeitstempel statt PID, da trap ... EXIT zuverlässig aufräumt; bei bereits laufendem Lauf wird einfach übersprungen statt zu warten/abzubrechen mit Fehlercode, damit Cron keine Fehlermail wegen "schon belegt" verschickt).
  • deploy/cron.d/archivmail ruft jetzt die Wrapper-Skripte unter /usr/local/bin/ auf statt das Binary direkt.
  • Root Cause für "Last geht nicht runter" gefunden: update.sh synct /etc/cron.d/archivmail nie auf den Server — die PROJ-58-Cronzeilen fehlten auf 131 UND 132 komplett, obwohl batch_mode im Code bereits aktiv war. Daher lief weiterhin nichts batch-weise, weil der Backlog nie per Cron abgeholt wurde. Fix: update.sh kopiert jetzt deploy/cron.d/archivmail nach /etc/cron.d/archivmail und alle deploy/cron.d/*.sh-Wrapper nach /usr/local/bin/ bei jedem Deploy.

Nachtrag (2026-06-24): batch_mode als Default für Neuinstallationen

Auf Nutzerwunsch ist index.batch_mode: true und ocr.batch_mode: true jetzt aktiv (nicht mehr auskommentiert) in config/config.docker.yml.example gesetzt, damit neue Installationen direkt mit Cron-Batch statt Dauerbetrieb starten. Bestehende Installationen (wie 192.168.1.131/132) sind davon nicht betroffen, da deren /etc/archivmail/config.yml unabhängig vom Repo-Beispiel ist und weiterhin ohne batch_mode-Einträge (= Dauerbetrieb) läuft.

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.