docs: Systemanforderungen und journald-Abhaengigkeit dokumentieren (v0.6.3)

Aus den Testlaeufen auf Debian 12 (CT 200) und Debian 13 (CT 201):

- Systemanforderungen: mindestens 2 GB RAM. Eine einzelne A4-Seite in
  300 dpi mit deskew=true hat auf einem 512-MB-Container den OOM-Killer
  ausgeloest (anon-rss:489676kB). Dazu der Zusammenhang RAM <->
  max_workers x jobs x Aufloesung, wie sich ein OOM-Kill aeussert und wie
  man ihn nachweist (/sys/fs/cgroup/memory.events, dmesg auf dem Host).
- Troubleshooting: kaputtes systemd-journald ist ein blinder Fleck, weil
  der Dienst seit v0.4.1 nur dorthin loggt. Symptom, erste Pruefung und
  ein Vordergrund-Notbehelf. Auf einem von zwei Testcontainern aufgetreten
  — kein generelles LXC-Muster.

Nebenbei korrigiert: toter Anker in docs/INSTALLATION.md (#3-ocr-sprachen
-> #4-ocr-sprachen) und eine veraltete Stelle im Briefing, die
requirements.txt noch als "ocrmypdf 16.x" beschrieb.

Reine Doku-Version, 152 Tests unveraendert gruen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-23 00:18:45 +02:00
parent 04dc3c7b72
commit 305454eeb5
6 changed files with 201 additions and 7 deletions
+137 -1
View File
@@ -12,6 +12,7 @@ Verwandte Dokumente: [README](../README.md) · [Update](UPDATE.md) · [Debian-Ma
|-------|-------------|
| Betriebssystem | Debian 12 (bookworm) oder Debian 13 — systemd wird vorausgesetzt |
| Python | 3.11+ (wegen `tomllib` aus der stdlib); kommt aus der Distribution |
| Arbeitsspeicher | **mindestens 2 GB** für den produktiven Betrieb — siehe [Systemanforderungen](#systemanforderungen) |
| Rechte | `root` (`sudo ./install.sh`) |
| Netz | apt-Zugriff für die System-Pakete, PyPI-Zugriff für die venv |
| Repo | muss dauerhaft liegen bleiben — `update.sh` kopiert daraus (s. [UPDATE.md](UPDATE.md)) |
@@ -29,7 +30,7 @@ ca-certificates curl
```
Weitere Tesseract-Sprachpakete installiert der Installer bei Bedarf pro Instanz
nach (siehe [OCR-Sprachen](#3-ocr-sprachen)).
nach (siehe [OCR-Sprachen](#4-ocr-sprachen)).
Die Python-Abhängigkeiten stehen **fest gepinnt** in `requirements.txt`
(ocrmypdf, watchdog, requests, paramiko). Warum das so ist und wie man die Pins
@@ -37,6 +38,92 @@ anhebt: [OS-UPGRADE.md](OS-UPGRADE.md#pins-in-requirementstxt).
---
## Systemanforderungen
CPU und Platte sind unkritisch — **der Arbeitsspeicher ist es nicht.** OCR
rastert jede Seite in voller Auflösung ins RAM; der Spitzenbedarf hängt an der
Seitengröße, nicht an der Dateigröße der PDF.
**Empfehlung: mindestens 2 GB RAM.** Für Mehr-Instanz-Betrieb oder
`max_workers > 2` entsprechend mehr.
### Warum 512 MB nachweislich nicht reichen
Gemessen auf einem LXC-Container mit **512 MB RAM + 512 MB Swap**, Debian 13,
Ghostscript 10.05.1:
| Vorgang | Ergebnis |
|---------|----------|
| eine einzelne **A4-Seite in 300 dpi**, `deskew = true` | cgroup-Limit gerissen, Dienst vom **OOM-Killer** beendet |
| dieselbe Verarbeitung mit einer kleineren Seite (850 × 1100 px) | läuft sauber durch, ca. 19 s |
Beleg aus dem `dmesg` des LXC-Hosts:
```
oom-kill:constraint=CONSTRAINT_MEMCG, oom_memcg=/lxc/201, task=python
total-vm:1168764kB, anon-rss:489676kB
```
Eine Seite, ein Worker — und schon knapp 500 MB anonymer Speicher. 512 MB sind
damit für 300-dpi-Scans keine knappe, sondern eine unzureichende Dimensionierung.
### RAM ↔ `max_workers` × `jobs` × Auflösung
Der Spitzenbedarf multipliziert sich über drei Config-Werte aus
[`[ocr]`](#ocr):
| Key | Default | Wirkung auf den Speicher |
|-----|---------|--------------------------|
| `max_workers` | `2` | **so viele PDFs gleichzeitig** — jede mit eigenem Seitenpuffer. Der direkte Multiplikator |
| `jobs` | `4` | Threads **innerhalb** einer PDF; mehrere Seiten gleichzeitig im Speicher |
| `oversample` | `300` | Auflösung gerasterter Seiten — der Bedarf wächst quadratisch mit der dpi |
Der Default `max_workers = 2` erlaubt also, dass **zwei** solcher Seiten
parallel verarbeitet werden. Wer knapp dimensioniert, zieht zuerst
`max_workers` auf `1` herunter, danach `jobs`. `oversample` unter 300 zu
drücken, spart zwar Speicher, kostet aber Erkennungsqualität — das ist der
letzte Hebel, nicht der erste.
### Wie sich ein OOM-Kill äußert
Von außen sieht ein OOM-Kill wie ein Anwendungsfehler aus — er ist keiner:
- Der Dienst ist **weg** bzw. wurde von systemd neu gestartet
(`Restart=on-failure`); `systemctl show -p NRestarts` steigt.
- Im journal bricht die Verarbeitung **mitten in der Datei** ab, ohne
Python-Traceback und ohne `ERROR`-Zeile aus dem Tool.
- Die betroffene PDF bleibt in `working/` liegen.
### Wie man ihn nachweist
**Im Container:**
```bash
cat /sys/fs/cgroup/memory.events
# oom_kill 1 <- alles über 0 ist ein Treffer
```
**Auf dem LXC-Host** (im Container zeigt `dmesg` diese Zeilen nicht):
```bash
dmesg -T | grep -i oom-kill
```
Diese Prüfung gehört an den **Anfang** der Fehlersuche, wenn Dateien
unerklärlich in `working/` liegen bleiben: ohne sie sucht man den Fehler in
ocrmypdf, Tesseract oder der Config, wo keiner ist.
### Datenverlust ist abgefangen, der OOM bleibt
Seit **v0.6.0** greift der Dienst beim nächsten Start auf, was in `working/`
liegen geblieben ist (siehe [UPDATE.md](UPDATE.md#wiederaufnahme-aus-working)).
Eine vom OOM-Killer unterbrochene Datei geht also nicht verloren. Behoben ist
damit aber nur die Folge: bei unveränderter Dimensionierung läuft dieselbe Datei
nach dem Neustart erneut in denselben OOM — bis `max_workers` sinkt oder das
System mehr RAM bekommt.
---
## Installation
```bash
@@ -480,6 +567,55 @@ cd /opt/pdf-ocr-hotfolder && sudo ./venv/bin/python -m pdf_ocr_hotfolder \
Der Interpreter der venv ist weg — fast immer nach einem Distributions-Upgrade.
Siehe [OS-UPGRADE.md](OS-UPGRADE.md).
### Keine Logs: "No journal files were found"
**Symptom:** `journalctl -u pdf-ocr-hotfolder@<instanz>` bleibt leer oder meldet
`No journal files were found.` — auch dann, wenn der Dienst nachweislich läuft
und Dateien verarbeitet.
**Erste Prüfung:**
```bash
systemctl status systemd-journald
```
Ist `systemd-journald.service` selbst `failed` (beobachtet mit
`status=243/CREDENTIALS`), gibt es schlicht kein journal, in das geschrieben
werden könnte.
**Einordnung.** Der Dienst loggt seit v0.4.1 **ausschließlich** nach journald —
es gibt bewusst kein eigenes Logfile und kein Logverzeichnis. Ein kaputtes
journald ist damit ein blinder Fleck: jede Fehlersuche läuft ins Leere, und der
Ausfall sieht aus wie ein stummer Dienst. **Deshalb vor jeder Fehlersuche
zuerst journald prüfen**, nicht erst, wenn nichts anderes mehr passt.
Das ist **kein** generelles LXC-Muster. Von zwei Testcontainern war genau einer
betroffen; auf dem zweiten lief journald einwandfrei. Es handelt sich um einen
Schaden auf dieser einen Maschine, nicht um eine Eigenschaft von Containern.
**Notbehelf, solange journald nicht zu retten ist:** die Instanz einmal im
Vordergrund laufen lassen — dann geht die Ausgabe direkt ins Terminal, am
journal vorbei.
```bash
sudo systemctl stop pdf-ocr-hotfolder@<instanz>
cd /opt/pdf-ocr-hotfolder && sudo -u pdfocr ./venv/bin/python -m pdf_ocr_hotfolder \
--config /etc/pdf-ocr-hotfolder/<instanz>.toml
```
Das `cd` ist zwingend: das Paket wird nicht pip-installiert, sondern nach
`/opt/pdf-ocr-hotfolder` kopiert und nur über das Arbeitsverzeichnis gefunden
(ohne `cd` gibt es `No module named pdf_ocr_hotfolder`, v0.6.2). Beenden mit
`Strg+C`, danach `sudo systemctl start pdf-ocr-hotfolder@<instanz>`. Wer nur den
Bestand abarbeiten und dann aussteigen will, hängt `--once` an (siehe
[Manueller Lauf](#manueller-lauf-one-shot)).
### Dienst bricht mitten in der Verarbeitung weg
Datei bleibt in `working/`, kein Traceback, `NRestarts` steigt: das ist fast
immer der OOM-Killer, kein Anwendungsfehler. Nachweis und Dimensionierung unter
[Systemanforderungen](#wie-sich-ein-oom-kill-äußert).
---
## Manueller Lauf (One-Shot)