Wie langlaufende Operationen REST-konform modelliert werden, ohne den Client ewig warten zu lassen
Ein Videotranscoding, ein großer Datenexport oder eine komplexe Report-Generierung passt selten in die Antwortzeit eines einzelnen synchronen HTTP-Requests. Das Long-Running-Operations-Pattern löst dieses Problem, indem die Operation sofort mit HTTP 202 Accepted bestätigt wird, während die eigentliche Arbeit asynchron im Hintergrund läuft und der Client den Fortschritt über einen separaten Status-Endpoint abfragt.
Inhaltsverzeichnis
- 1. Warum synchrone Requests bei langen Operationen scheitern
- 2. HTTP 202 Accepted als sofortige Bestätigung
- 3. Der Status-Endpoint für Polling
- 4. Sinnvolle Polling-Intervalle statt aggressivem Dauerabfragen
- 5. Webhooks als Alternative zu reinem Polling
- 6. Fehlerbehandlung bei fehlgeschlagenen Long-Running-Operations
- 7. Wie lange Job-Status-Informationen aufbewahrt werden sollten
- 8. Laufende Operationen gezielt abbrechbar machen
- 9. Long-Running Operations im Überblick
- 10. Zusammenfassung
- 11. FAQ
1. Warum synchrone Requests bei langen Operationen scheitern
HTTP-Requests haben in der Praxis harte Zeitlimits, gesetzt durch Load Balancer, Reverse Proxies, Browser und den PHP-FPM-Prozess selbst, typischerweise zwischen 30 und 120 Sekunden. Eine Operation, die mehrere Minuten oder gar Stunden benötigt (ein umfangreicher Datenexport, eine Batch-Bildverarbeitung, eine komplexe Report-Berechnung über Millionen Datensätze), überschreitet diese Limits zuverlässig, unabhängig davon, wie gut der Server-Code optimiert ist.
Ein naiver Versuch, das Timeout einfach zu erhöhen, verschiebt das Problem nur, statt es zu lösen, und macht die Anwendung anfällig für aufgehängte Worker-Prozesse, die während der gesamten Operationsdauer blockiert bleiben. Das Long-Running-Operations-Pattern löst das Problem strukturell, indem es die HTTP-Antwortzeit von der tatsächlichen Verarbeitungszeit entkoppelt.
Ein zusätzlicher, oft unterschätzter Vorteil dieser Entkopplung ist, dass der HTTP-Server-Prozess selbst nach Bestätigung der Anfrage sofort wieder für andere Requests frei wird, statt für die gesamte Dauer der Operation blockiert zu bleiben, was die Gesamtkapazität der Anwendung unter Last spürbar erhöht.
2. HTTP 202 Accepted als sofortige Bestätigung
HTTP 202 Accepted signalisiert, dass eine Anfrage zur Verarbeitung angenommen wurde, die Verarbeitung selbst aber noch nicht abgeschlossen ist, im Unterschied zu HTTP 200 OK oder HTTP 201 Created, die eine bereits abgeschlossene Operation signalisieren. Die Antwort auf einen Long-Running-Operation-Trigger sollte deshalb sofort mit 202 antworten, typischerweise binnen Millisekunden, verbunden mit einer Job-ID und einem Location-Header, der auf den zugehörigen Status-Endpoint verweist.
Dieser Statuscode ist Teil des HTTP-Standards selbst und wird von REST-Clients und Frameworks korrekt als "angenommen, aber nicht fertig" interpretiert, im Gegensatz zu einem missbräuchlich verwendeten 200 mit einem selbst erfundenen Status-Feld im Body, das viele generische HTTP-Tools nicht automatisch erkennen.
<?php
declare(strict_types=1);
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Messenger\MessageBusInterface;
final class ReportGenerationController
{
public function __construct(private readonly MessageBusInterface $bus) {}
public function trigger(array $reportParams): JsonResponse
{
$jobId = bin2hex(random_bytes(16));
$this->bus->dispatch(new GenerateReportMessage($jobId, $reportParams));
return new JsonResponse(
['jobId' => $jobId, 'status' => 'pending'],
202,
['Location' => "/api/reports/jobs/{$jobId}"]
);
}
}
3. Der Status-Endpoint für Polling
Der über den Location-Header referenzierte Status-Endpoint sollte mindestens den aktuellen Status (pending, processing, completed, failed) zurückgeben, idealerweise ergänzt um einen numerischen Fortschritt und eine geschätzte Restdauer, sofern diese sinnvoll berechenbar ist. Solange der Job noch läuft, antwortet der Endpoint mit HTTP 200 und dem aktuellen Status, nicht mit 202, weil die Statusabfrage selbst sofort abgeschlossen ist, auch wenn der referenzierte Job noch nicht fertig ist.
Ist der Job abgeschlossen, sollte der Status-Endpoint entweder das fertige Ergebnis direkt enthalten (bei kleineren Ergebnissen) oder einen Link zum Ergebnis liefern (bei größeren Ergebnissen wie generierten Dateien), zusammen mit einem klaren completed-Status, damit der Client eindeutig erkennt, dass weiteres Polling nicht mehr nötig ist.
4. Sinnvolle Polling-Intervalle statt aggressivem Dauerabfragen
Ein Client, der den Status-Endpoint alle 100 Millisekunden abfragt, erzeugt unnötige Last, während ein zu seltenes Polling (etwa alle 30 Sekunden) die wahrgenommene Reaktionsfähigkeit für kurze Jobs unnötig verschlechtert. Ein bewährtes Muster ist exponentiell steigendes Polling, beginnend bei kurzen Intervallen (etwa 1 Sekunde) und ansteigend bis zu einem Maximalintervall (etwa 10 Sekunden), um kurze Jobs schnell zu erkennen, ohne bei langen Jobs unnötig Last zu erzeugen.
Der Status-Endpoint kann diesen Prozess zusätzlich unterstützen, indem er über einen Retry-After-Header dem Client explizit mitteilt, wie lange er vor der nächsten Abfrage warten soll, statt den Client mit einer festen, hartcodierten Client-Logik allein zu lassen. Dieser Header ist Teil des HTTP-Standards und wird von vielen HTTP-Client-Bibliotheken automatisch respektiert.
5. Webhooks als Alternative zu reinem Polling
Für Clients, die eine eigene öffentlich erreichbare URL betreiben können, ist ein Webhook-Callback oft effizienter als Polling: Statt wiederholt nachzufragen, registriert der Client bei der ursprünglichen Anfrage eine Callback-URL, die der Server bei Abschluss des Jobs mit dem Ergebnis aufruft. Dieser Ansatz reduziert unnötigen Traffic drastisch, besonders bei Jobs mit stark variabler, schwer vorhersagbarer Laufzeit.
Webhooks bringen eigene Komplexität mit sich, etwa Retry-Logik bei fehlgeschlagener Zustellung, Signatur-Verifikation gegen gefälschte Callbacks und die Notwendigkeit, dass der Client öffentlich erreichbar ist, weshalb viele APIs beide Mechanismen parallel anbieten: Polling als einfache Standardoption, Webhooks als effizientere Option für Clients, die den zusätzlichen Aufwand rechtfertigen können.
6. Fehlerbehandlung bei fehlgeschlagenen Long-Running-Operations
Schlägt ein Job während der asynchronen Verarbeitung fehl, sollte der Status-Endpoint einen expliziten failed-Status mit einer strukturierten Fehlerbeschreibung liefern, statt den Job unbegrenzt im pending-Status zu belassen, was den Client zu endlosem, sinnlosem Polling zwingen würde. Ein Timeout auf Job-Ebene, der einen Job nach einer maximalen Laufzeit automatisch als failed markiert, verhindert zusätzlich, dass hängende Hintergrundprozesse unentdeckt bleiben.
Für Jobs, die teilweise erfolgreich waren (etwa ein Datenexport, der bei 80 Prozent abbricht), sollte der Status-Endpoint dies explizit als eigenen Zustand kommunizieren, verbunden mit Informationen darüber, welcher Teil bereits abgeschlossen wurde, statt den gesamten Job pauschal als failed zu markieren. Diese differenzierte Fehlerkommunikation gibt dem Client die Möglichkeit, gezielt nur den fehlgeschlagenen Teil erneut anzustoßen, statt den kompletten Job von vorne zu starten.
7. Wie lange Job-Status-Informationen aufbewahrt werden sollten
Job-Status-Informationen sollten nicht unbegrenzt aufbewahrt werden, sondern nach einer angemessenen Frist (etwa 7 bis 30 Tage, abhängig vom Anwendungsfall) automatisch bereinigt werden, um den Speicherbedarf des Status-Speichers (oft Redis oder eine dedizierte Datenbanktabelle) nicht unkontrolliert wachsen zu lassen. Ein Abruf eines abgelaufenen Job-Status sollte mit HTTP 404 Not Found antworten, nicht mit einem irreführenden pending-Status.
Diese Aufbewahrungsfrist sollte klar in der API-Dokumentation kommuniziert werden, damit Clients wissen, wie lange sie sich auf die Verfügbarkeit eines Job-Ergebnisses verlassen können, bevor sie es selbst dauerhaft speichern müssen. Ein automatisierter Cleanup-Job, der abgelaufene Einträge periodisch entfernt, verhindert zusätzlich, dass die Aufbewahrungsfrist nur auf dem Papier existiert, aber in der Praxis nie durchgesetzt wird, was insbesondere bei einer hohen Anzahl täglich erzeugter Jobs schnell zu einem spürbaren Speicherproblem werden kann.
8. Laufende Operationen gezielt abbrechbar machen
Ein oft übersehener, aber praktisch wichtiger Aspekt ist die Möglichkeit, einen laufenden Job explizit abzubrechen, etwa wenn ein Nutzer einen versehentlich gestarteten, ressourcenintensiven Export stoppen möchte, statt ihn bis zum Ende laufen zu lassen und das Ergebnis danach zu verwerfen. Ein DELETE auf den Job-Status-Endpoint ist eine naheliegende, REST-konforme Möglichkeit, einen Abbruch anzufordern.
Wichtig ist, dass ein Abbruch-Request selbst asynchron behandelt wird: Der Server bestätigt die Abbruchanfrage sofort, der eigentliche Hintergrundprozess muss aber selbst regelmäßig prüfen, ob ein Abbruch angefordert wurde (etwa über ein Flag im Job-Status-Speicher), da eine sofortige, harte Unterbrechung eines laufenden Prozesses inkonsistente Zwischenzustände hinterlassen kann. Der Job-Status sollte deshalb einen eigenen cancelling-Zustand kennen, bevor der endgültige cancelled-Status tatsächlich erreicht wird.
9. Long-Running Operations im Überblick
Die folgende Tabelle vergleicht Polling und Webhooks als Kommunikationsmechanismen.
| Mechanismus | Vorteil | Nachteil |
|---|---|---|
| Polling | Einfach zu implementieren, keine öffentliche Client-URL nötig | Unnötiger Traffic bei falscher Intervall-Wahl |
| Webhooks | Effizient, sofortige Benachrichtigung bei Abschluss | Client braucht öffentlich erreichbare URL |
| 202 Accepted | Sofortige Bestätigung, klare HTTP-Semantik | Erfordert zusätzlichen Status-Endpoint |
| Retry-After-Header | Steuert Polling-Intervall serverseitig | Wird nicht von allen Clients automatisch respektiert |
Mironsoft
OpenAPI-Design, Symfony-APIs und API-Sicherheit
APIs, die externe Teams ohne Rückfragen integrieren können?
Wir prüfen bestehende REST-APIs auf inkonsistente Fehlerformate, fehlende OpenAPI-Dokumentation und Sicherheitslücken und bauen daraus eine API, die klar dokumentiert, versioniert und gegen Missbrauch abgesichert ist.
API-Review
OpenAPI-Spezifikation, Fehlerformate und Statuscodes auf Konsistenz prüfen.
Symfony-Umsetzung
DTOs, Serializer und Validator für saubere, typsichere Request/Response-Modelle einsetzen.
Security-Audit
Rate-Limiting, Auth-Schemes und Input-Validierung gegen echte Angriffsflächen absichern.
10. Zusammenfassung
Long-Running Operations: Das Wichtigste auf einen Blick
202 Accepted
Signalisiert sofortige Annahme einer Operation, die noch nicht abgeschlossen ist, mit Job-ID und Location-Header.
Status-Endpoint
Liefert aktuellen Status, Fortschritt und bei Abschluss das Ergebnis oder einen Link darauf.
Polling-Intervalle
Exponentiell steigend statt fest, idealerweise gesteuert über den Retry-After-Header.
Webhooks als Alternative
Effizienter als Polling für Clients mit öffentlich erreichbarer Callback-URL.