FDN-28: pruefprotokoll
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web (dms-browser) (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
patrickandClaude Sonnet 5 authored and root committed 2026-10-13 10:54:59 +00:00
1 parent 3d00314123
commit 3425495217
1 file changed
+132
+132
View File
@@ -0,0 +1,132 @@
# FDN-28 – Prüfprotokoll: Rückstau/Drosselung vor dem DB-Verbindungspool
Nachtrag zu QA-10. QA-10 (`feature/qa-10-last-leistungstest`, Commit
`da34550`, noch nicht auf `main` gemerged) hatte bei 150 gleichzeitigen
Anfragen einen echten Engpass gefunden: Postgres meldete `too many clients
already` (SQLSTATE 53300), sowohl für HTTP-Anfragen als auch für
Hintergrundjobs (Malware-Scan, Such-Sync) — als rohe Verbindungsfehler statt
einer definierten `503`-Antwort. Dieses Ticket fügt die fehlende
Zulassungskontrolle (Admission Control) zwischen Anfragevolumen und
DB-Zugriff ein.
**Hinweis zur Ausgangslage:** Der Ticket-Text nennt `dms/docs/QA-10-PRUEFPROTOKOLL.md`
als "jetzt auf main gemerged". Das stimmt zum Zeitpunkt dieser Arbeit nicht
— QA-10 liegt noch unverändert auf seinem eigenen Feature-Branch
(`feature/qa-10-last-leistungstest`), nicht auf `main`. Das Dokument wurde
trotzdem direkt aus diesem Branch gelesen (Abschnitt 3 dort beschreibt genau
den hier behobenen Engpass); die Implementierung dieses Tickets ist davon
unabhängig und baut auf dem aktuellen `main` auf.
## 1. Lösung
Neues Paket `dms/internal/dbadmission`:
- `Gate`: Zulassungssemaphor (gepuffertes Kanal-Semaphor) mit konfigurierbarer
maximaler Nebenläufigkeit und maximaler Wartezeit auf einen freien
Durchlass.
- `Middleware(g, retryAfter)`: HTTP-Middleware — wird das Gate innerhalb der
Wartezeit nicht frei, antwortet sie mit `503` (Core-Fehlerformat,
`apierror.Write`, Code `unavailable`) und `Retry-After`-Header statt den
Request an den DB-zugreifenden Handler durchzulassen.
- `JobMiddleware(g)`: Job-Queue-Middleware — bei erschöpftem Gate wird ein
gewöhnlicher (transienter) Fehler zurückgegeben, kein
`jobqueue.LimitExceededError`. Das sorgt dafür, dass `Queue.Fail` den Job
mit exponentiellem Backoff erneut einplant (verzögerte Verarbeitung),
statt ihn in die Dead-Letter-Queue zu verschieben oder Daten zu verlieren.
- Ein `nil`-Gate lässt jede Anfrage/jeden Job unverändert durch (Admission
deaktiviert, z. B. für Bestandsinstallationen ohne explizite
Konfiguration).
Verdrahtung:
- `dms/internal/apiwiring.ChainConfig` bekommt `Admission *dbadmission.Gate`
und `AdmissionRetryAfter time.Duration`; `Chain()` schaltet die Middleware
vor Authentifizierung/Rechteentscheidung (frühestmöglicher Ausstieg bei
Überlast, bevor unnötige Arbeit anfällt).
- `dms/cmd/app/api.go`: `identityChain` baut das Gate aus der Umgebung
(`NEXARCH_DMS_APP_ADMISSION_MAX_CONCURRENT`, Default 40;
`NEXARCH_DMS_APP_ADMISSION_MAX_WAIT_SECONDS`, Default 2;
`NEXARCH_DMS_APP_ADMISSION_RETRY_AFTER_SECONDS`, Default 5;
`...MAX_CONCURRENT=0` schaltet ab).
- `dms/cmd/worker/main.go`: `rt.Middleware` wird um `dbadmission.JobMiddleware`
erweitert (eigene Env-Variablen `NEXARCH_DMS_WORKER_ADMISSION_MAX_CONCURRENT`/
`..._MAX_WAIT_SECONDS`, gleiche Defaults/Abschaltlogik). Das Gate sitzt
außerhalb der bestehenden Tracker-Middleware, sodass Gate-Erschöpfung
selbst nicht als Fehler im Error-Tracking auftaucht (erwartetes
Rückstau-Verhalten, kein Bug).
## 2. Automatisierte Tests
```
cd dms && go build ./... # OK
cd dms && go vet ./... # OK
cd dms && gofmt -l internal/dbadmission internal/apiwiring cmd/app cmd/worker # keine neuen Abweichungen
cd dms && TEST_TENANT_DSN=... TEST_MANTICORE_ADDR=127.0.0.1:9306 \
go test ./internal/apiwiring/... ./internal/dbadmission/... \
./internal/jobqueue/... ./internal/workerruntime/... -p 1 -v
```
Ergebnis: alle Pakete **PASS**, keine Regression in bestehenden Tests.
Neue Tests je Akzeptanzkriterium:
| Test | Akzeptanzkriterium | Ergebnis |
|---|---|---|
| `dbadmission.TestMiddleware_UeberGrenze_503MitRetryAfter` | 1 | **bestanden** — 503 + `Retry-After` statt rohem Fehler |
| `apiwiring.TestChain_Admission_UeberGrenze_503MitRetryAfter` | 1 (reale Verdrahtung über `Chain()`, nicht nur isoliertes Paket) | **bestanden** |
| `dbadmission.TestJobMiddleware_ErschoepftesGate_VerzoegertStattDatenverlust` | 2 | **bestanden** — transienter Fehler statt `LimitExceededError`; Job a (vorher gestartet) läuft unverändert zu Ende durch |
| `dbadmission.TestMiddleware_UnterGrenze_Unveraendert` | 3 | **bestanden** — 5 Anfragen unterhalb der Grenze, alle 200 |
| `apiwiring.TestChain_OhneAdmission_Unveraendert` | 3 | **bestanden** — ohne konfiguriertes Gate (`ChainConfig{}`) unverändertes Verhalten |
| `dbadmission.TestGate_NilGate_LaesstAllesDurch` | 3 (Regression bei deaktivierter Kontrolle) | **bestanden** |
## 3. Prüfungen vor der Abnahme
**1. Simulierte Überlast:** `TestChain_Admission_UeberGrenze_503MitRetryAfter`
fährt die tatsächliche Produktions-`Chain()`-Funktion (nicht nur das
isolierte `dbadmission`-Paket) mit einem Gate der Kapazität 1: eine
blockierende Anfrage hält den einzigen Durchlass, eine zweite, gleichzeitige
Anfrage erhält `503` mit `Retry-After: 9` statt eines rohen
Verbindungsfehlers. **Bestanden.**
Bewusst NICHT real gegen den geteilten Postgres-Dienst von `dms_dev`
reproduziert: Das würde (wie in QA-10 dokumentiert) echte
`max_connections`-Erschöpfung auf einem von mehreren Sitzungen gemeinsam
genutzten Testhost erfordern und andere parallele Arbeit auf 204 gefährden.
Die Zulassungskontrolle selbst ist DB-agnostisch (sie begrenzt Nebenläufigkeit
vor dem Handler-Aufruf, unabhängig davon, was der Handler tut) — der
HTTP-Ebenen-Test deckt den Mechanismus vollständig ab, ohne dieses Risiko
einzugehen.
**2. Hintergrundjobs unter Überlast:**
`TestJobMiddleware_ErschoepftesGate_VerzoegertStattDatenverlust` zeigt: bei
erschöpftem Gate kommt ein transienter Fehler zurück (kein
`LimitExceededError`), der in der echten Job-Verarbeitung
(`jobqueue.Worker.processOne`) zu `Queue.Fail` (exponentielles Backoff, bis
zu `MaxAttempts` Versuche) statt zu einem sofortigen Fehlschlag/Datenverlust
führt. Der zuerst gestartete Job läuft unverändert bis zum Erfolg durch —
kein Job geht verloren. **Bestanden.**
**3. Bestehende QA-10-Prüfungen laufen unverändert grün:** QA-10 ist nicht
auf `main` gemerged (siehe Hinweis oben) und daher auf diesem Branch nicht
vorhanden/ausführbar. Stattdessen wurde die volle bestehende Testsuite der
betroffenen Pakete (`apiwiring`, `jobqueue`, `workerruntime`) gegen die
reale `dms_dev`-Datenbank auf Host 204 gefahren — alle **PASS**, keine
Regression durch die neue Zulassungskontrolle.
## 4. Gesamtergebnis
| Akzeptanzkriterium | Status |
|---|---|
| 1. 503 + Retry-After statt rohem Verbindungsfehler bei Überlast | **erfüllt** |
| 2. Hintergrundjobs verzögert statt fehlzuschlagen | **erfüllt** |
| 3. Verhalten unterhalb der Grenze unverändert | **erfüllt** |
| Prüfung vor der Abnahme | Status |
|---|---|
| 1. Simulierte Überlast: definierte 503-Antworten | **erfüllt** (HTTP-Ebene real gegen `Chain()`; keine reale Postgres-Verbindungserschöpfung auf geteiltem Testhost provoziert, siehe Begründung oben) |
| 2. Hintergrundjobs unter Überlast: verzögert, kein Datenverlust | **erfüllt** |
| 3. Bestehende QA-10-Prüfungen laufen unverändert grün | **nicht anwendbar** (QA-10 nicht auf `main`), stattdessen volle betroffene Testsuite real gegen `dms_dev` gefahren, grün |
Alle drei Akzeptanzkriterien sind erfüllt. Prüfung 3 der Vorgabe war wegen
des nicht gemergten QA-10-Standes nicht wörtlich ausführbar; als Ersatz
wurde die reale, betroffene Testsuite gegen die Live-Datenbank gefahren.