Case-Study · Express 4 REST-API, 850 RPS Peak, 3 Node-Replicas
Case-Study: 12k 500-Errors/Tag durch Schema-Validation halbiert
Express-API mit 12k täglichen 500-Errors, alle wegen ungültiger Payloads. Schema-Validation an der Route-Grenze beseitigte sie komplett.
Metriken vor / nach
Größe / Volumen
12k 500/Tag
↓
0 500/Tag
Parse / Laufzeit
reaktiv im Code
↓
0,03 ms pro Req
Status
PagerDuty täglich
↓
PagerDuty stumm
Maßnahmen
- ✓ JSON Schema Draft 2020-12 für 14 Endpoints geschrieben
- ✓ Ajv 8.x mit allErrors + strict-Mode
- ✓ Pre-compiled Validators im App-Init
- ✓ Middleware ajv-validate-request für Express
- ✓ 400-Response mit RFC 7807 Problem-Details statt 500
Die API: ein E-Commerce-Backend für einen mittelständischen Händler. 850 Requests pro Sekunde im Peak, drei Node-Instanzen hinter Nginx. Symptom: ~12.000 500-Errors pro Tag in Sentry, alle mit Stack-Traces tief im Business-Code. Pager-Duty war ein laufender Witz.
Wurzelanalyse
Sentry-Cluster-View zeigte: 94 % der 500er waren TypeError oder Cannot-read-property-of-undefined. Die API hatte zwar einige Validierungen, aber inkonsistent - manche Routen prüften manuell auf req.body.email, andere setzten den Wert direkt durch.
Maßnahme: Schema-Validation an der Route-Grenze
Für jeden der 14 POST/PUT-Endpoints wurde ein JSON Schema verfasst. Beispiel für POST /orders:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["customerId", "items", "shippingAddress"],
"properties": {
"customerId": { "type": "string", "format": "uuid" },
"items": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["sku", "qty"],
"properties": {
"sku": { "type": "string", "pattern": "^[A-Z0-9-]+$" },
"qty": { "type": "integer", "minimum": 1, "maximum": 100 }
}
}
},
"shippingAddress": { "$ref": "#/definitions/address" }
},
"additionalProperties": false
}
Ajv-Setup
Ajv 8 mit strict: true, allErrors: true ist die produktionsreife Default-Konfiguration. Pre-Compilation der Schemas beim App-Init: jeder Validator wird einmal zu hochoptimiertem JavaScript kompiliert und dann millionenfach aufgerufen. Benchmark in der Test-Suite: 31 Mikrosekunden pro Validierung - vernachlässigbar gegenüber jedem DB-Roundtrip.
Express-Integration
app.post('/orders', validateBody(orderSchema), createOrder);
Bei Schema-Failure: 400 mit RFC-7807-konformem Problem-Details-Body. Im Sentry wird ein Warn (nicht Error) gelogged, sodass valide Failures keinen Alert auslösen.
Ergebnis
| Metrik | Vorher | Nachher |
|---|---|---|
| 500-Errors/Tag | 12.000 | 0 |
| 400-Errors/Tag | ~600 | ~3.400 |
| P95-Latenz | 42 ms | 42 ms |
| Pager-Calls/Woche | 2-4 | 0 |
Die zusätzlichen 400er waren das eigentliche Problem die ganze Zeit - Schrott-Inputs aus einem dritten Marktplatz-Integrator, der ungültige Strings als customerId schickte. Mit der validen 400-Response konnte der Integrator es direkt korrigieren.