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
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:
1 parent
3d00314123
commit
3425495217
1 file changed
+132
@@ -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.
|
||||
Reference in new issue
Block a user