json-formatieren.de

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

MetrikVorherNachher
500-Errors/Tag12.0000
400-Errors/Tag~600~3.400
P95-Latenz42 ms42 ms
Pager-Calls/Woche2-40

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.