Postgres-Jobqueue (processing_jobs, FOR UPDATE SKIP LOCKED), In-Prozess- Worker-Goroutinen, kein Redis/AMQP. Enqueue mit Idempotency-Key-Dedup, Dequeue mit Stale-Lock-Wiedervorlage (Absturzsicherheit), Fail mit arithmetischem Backoff und Dead-Letter-Queue nach erschoepften Versuchen, RequeueDeadLetter fuer manuelle Wiederholung. Auf 192.168.1.131 verifiziert: Absturz-Wiedervorlage (Job von einem "abgestuerzten" Worker nie completed/failed, zweiter Worker holt ihn nach Ablauf der Sperre erneut), Idempotenz bei Doppelzustellung (gleicher idempotency_key erzeugt nur 1 Zeile), DLQ-Eintrag manuell wiederholbar. Vier reale Fehler beim Testen gefunden und behoben: zwei pgx-Typinferenz- Bugs im SQL-Parameterhandling (toter workerID-Parameter ohne Referenz in der Query; untypisiertes any statt []string fuer den ::text[]-Cast), sowie zwei Testinfrastruktur-Bugs (dms_tenant_test sammelte schema_migrations- Zustand ueber Sitzungen hinweg an, jobqueue-Testfixture raeumte processing_jobs nicht auf) - neues scripts/reset-test-env.sh + make check (-p 1) analog Core behoben. Siehe dms/docs/FDN-04-PRUEFPROTOKOLL.md fuer alle Pruefungsergebnisse. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
78 lines
4.4 KiB
Markdown
78 lines
4.4 KiB
Markdown
# FDN-04 – Prüfprotokoll: Job-Queue & Worker-Runtime
|
||
|
||
Welle 3. Voraussetzung: FDN-02 (Status "Fertig").
|
||
|
||
## Umsetzung
|
||
|
||
`internal/jobqueue`:
|
||
|
||
- `migrations/tenant/0002_processing_jobs.up.sql` — `processing_jobs`-Tabelle
|
||
(Status `pending`/`processing`/`succeeded`/`failed`/`dead_letter`,
|
||
`attempts`/`max_attempts`, `available_at` für Backoff-Terminierung,
|
||
`locked_at`/`locked_by` für die Sperre, `idempotency_key` UNIQUE).
|
||
- `Queue.Enqueue` — reiht ein, mit optionalem `idempotency_key` (Dedup bei
|
||
Doppelzustellung, `ON CONFLICT DO UPDATE ... RETURNING id`).
|
||
- `Queue.Dequeue` — `FOR UPDATE SKIP LOCKED`, holt entweder einen fälligen
|
||
`pending`-Job oder einen `processing`-Job, dessen Sperre älter als
|
||
`staleLockAfter` ist (Absturz-Wiedervorlage). Backoff-Intervallarithmetik
|
||
über `LEAST(attempts, 10) * interval '30 seconds'` — arithmetischer
|
||
Cast, keine String-Konkatenation (siehe "Bekannte Fehler vermeiden").
|
||
- `Queue.Complete`/`Queue.Fail` — bei erschöpften Versuchen wandert der Job
|
||
in `dead_letter`.
|
||
- `Queue.RequeueDeadLetter` — manuelle Wiederholung eines DLQ-Eintrags.
|
||
- `Queue.Status` — Job-Status abfragbar.
|
||
- `Worker`/`Handler` — In-Prozess-Worker-Goroutine, pollt und ruft `Handler`
|
||
je Job auf.
|
||
|
||
## Prüfungen
|
||
|
||
| # | Prüfung | Ergebnis |
|
||
|---|---|---|
|
||
| 1 | Absturz eines Workers führt zu erneuter Zustellung | **bestanden** — `TestDequeue_StaleLockIsRedelivered`: Job wird von `worker-crashed` gesperrt, NIE completed/failed (simulierter Absturz); sofortiger erneuter Dequeue-Versuch liefert `ErrNoJobAvailable` (Sperre noch frisch), nach Ablauf von `staleLockAfter` liefert `worker-2` denselben Job |
|
||
| 2 | Idempotenz bei Doppelzustellung nachgewiesen | **bestanden** — `TestEnqueue_IdempotencyKeyPreventsDuplicate`: zweifache Einreihung mit gleichem `idempotency_key` erzeugt nachweislich nur 1 Zeile (per Abfrage bestätigt) |
|
||
| 3 | DLQ-Eintrag manuell wiederholbar | **bestanden** — `TestRequeueDeadLetter`: Job nach erschöpften Versuchen in `dead_letter`, `RequeueDeadLetter` setzt zurück auf `pending` mit `attempts=0`; Requeue eines NICHT-DLQ-Jobs wird korrekt abgewiesen |
|
||
|
||
## Reale Fehler gefunden und behoben (kein Vorab-Wissen, beim Testen entdeckt)
|
||
|
||
1. **pgx-Typinferenz-Fehler bei ungenutztem Parameter**: `Dequeue`s SQL
|
||
übergab `workerID` als `$1`, ohne es in der Query zu referenzieren —
|
||
Postgres/pgx konnte den Typ von `$1` dadurch nicht ableiten
|
||
(`SQLSTATE 42P18`). Behoben durch Entfernen des toten Parameters
|
||
(workerID wird erst im nachfolgenden `UPDATE` gebraucht).
|
||
2. **`$2::text[]`-Cast mit untypisiertem `nil`**: `typeFilter any` (statt
|
||
`[]string`) ließ pgx den Zieltyp des Casts nicht auflösen. Behoben durch
|
||
`[]string`-Typisierung der Variable.
|
||
3. **Testinfrastruktur-Drift über Sitzungsgrenzen**: `dms_tenant_test`
|
||
sammelte über mehrere Testläufe (FDN-02/03/04) `schema_migrations`-Zustand
|
||
an, wodurch `internal/migrate`s Rollback-Test nur noch einen Teil der
|
||
Tabellen zurückrollte. Neues `scripts/reset-test-env.sh` (Datenbank
|
||
droppen+neu anlegen, analog Core `scripts/reset-test-env.sh`) sowie
|
||
`make check`-Target (Reset+vet+lint+test in einem Rutsch) behoben das
|
||
strukturell. Zusätzlich fehlte `-p 1` im `test`-Target — mehrere
|
||
Testpakete teilen sich dieselbe physische Test-DB, parallele
|
||
Paketausführung (Go-Testdefault) verursachte Querschläger zwischen
|
||
`internal/jobqueue` und `internal/migrate`.
|
||
4. **`internal/jobqueue`s Test-Fixture räumte nicht auf**: `TRUNCATE` statt
|
||
`DROP TABLE` ließ die Tabelle `processing_jobs` stehen, wodurch
|
||
`internal/migrate`s eigene, versionierte Migration mit
|
||
`relation already exists` scheiterte. Behoben durch `DROP TABLE IF EXISTS`
|
||
im Test-Cleanup.
|
||
|
||
## Build/Test-Ergebnis (192.168.1.131, `make check`)
|
||
|
||
```
|
||
go build ./... -> clean
|
||
scripts/reset-test-env.sh -> dms_tenant_test leer neu angelegt
|
||
go vet ./... -> clean
|
||
golangci-lint run ./... -> 0 issues
|
||
go test ./... -p 1 -count=1 -> 4/4 Pakete ok, 0 Fehlschläge (inkl. 8 jobqueue-Tests, 3 migrate-Tests, 6 storage-Tests real gegen MinIO)
|
||
```
|
||
|
||
## Gesamtergebnis
|
||
|
||
**Bestanden.** Alle drei Akzeptanzkriterien und alle drei Pflichtprüfungen
|
||
erfüllt. Vier reale Fehler beim Testen gefunden und behoben (zwei
|
||
Produktionscode-Bugs im SQL-Parameterhandling, zwei
|
||
Testinfrastruktur-Bugs) — bestätigt erneut den Wert, jede Prüfung
|
||
tatsächlich auf einem echten Testhost auszuführen statt nur zu behaupten.
|