Ein Import, der Stunden dauert, ein Bericht, der mehrere Quellen abfragt, oder eine Aufgabe, die Integrationen koordiniert, kann noch lange Ressourcen verbrauchen, nachdem sie nicht mehr nützlich ist. Ohne explizite Grenzen ist außerdem schwer zu erkennen, ob der Prozess arbeitet, blockiert ist oder unvollständig blieb. Zeitlimits für lang laufende PHP-Prozesse zu gestalten bedeutet, festzulegen, wie lange jede Phase dauern darf, wie die Arbeit beendet wird und welcher Status erhalten bleibt, wenn die Ausführung unterbrochen wird.
Ziel ist nicht, dem gesamten Prozess einen einzigen Wert vorzugeben. Vielmehr geht es darum, ein durchgängiges Zeitbudget festzulegen, es an abhängige Vorgänge weiterzugeben und dafür zu sorgen, dass der Endstatus den tatsächlichen Verlauf widerspiegelt. Ein Timeout, ein angeforderter Abbruch und eine erzwungene Beendigung sind unterschiedliche Situationen und müssen auch unterschiedlich behandelt werden.
Warum eine explizite Zeitbegrenzung nötig ist

Ein Prozess ohne Frist kann Speicher, Verbindungen und Ausführungskapazität unbegrenzt belegen. Außerdem kann eine Aufgabe weiterhin als aktiv markiert sein, obwohl sie keinen Fortschritt mehr macht. In einer Umgebung mit Workern verschärft sich das Problem: Eine blockierte Ausführung belegt einen Worker und kann nachfolgende Aufgaben verzögern.
PHP- oder Serverlimits ersetzen keine Anwendungsrichtlinie. Beispielsweise kann max_execution_time die Ausführung eines Skripts begrenzen, entspricht aber nicht unbedingt der Wartezeit, die das Geschäft insgesamt akzeptiert. Zudem können die Limits des Webservers, von PHP-FPM, des Queue-Managers, des HTTP-Clients und der Datenbank unabhängig voneinander greifen. Wenn sie voneinander abweichen, kann eine Schicht die Arbeit beenden, ohne dass eine andere den entsprechenden Status speichern kann.
Ermitteln Sie zunächst, wo der Prozess ausgeführt wird und welche Komponenten ihn stoppen können. Ein Konsolenbefehl, ein Queue-Worker und eine Webanfrage haben unterschiedliche Mechanismen und Folgen. Das Anwendungslimit muss mit diesen externen Kontrollen zusammenspielen und darf sich nicht darauf verlassen, dass eine davon allein die Beendigung übernimmt.
Timeout, Deadline und Abbruch unterscheiden
- Timeout eines Vorgangs: Die maximale Wartezeit für einen konkreten Aufruf, etwa eine Abfrage oder eine HTTP-Anfrage.
- Globale Deadline: Der späteste Zeitpunkt für den gesamten Prozess. Die Phasen teilen sich die verbleibende Zeit, statt jeweils die gesamte Frist zu erhalten.
- Angeforderter Abbruch: Die ausdrückliche Entscheidung, die Arbeit einzustellen, etwa weil ein Benutzer die Aufgabe abgebrochen hat oder das System sie aus einer Warteschlange entfernt hat.
Ein Timeout zeigt an, dass ein Vorgang nicht innerhalb seines Zeitbudgets abgeschlossen wurde. Daraus folgt nicht automatisch, dass der gesamte Prozess beendet werden muss: Das hängt von der Phase und den Produktregeln ab. Ein Abbruch ist dagegen ein Steuersignal, das der Prozess prüfen und berücksichtigen muss, sobald dies sicher möglich ist. Keine der beiden Situationen garantiert, dass ein Betriebssystem oder ein Supervisor die Ausführung exakt an einem bestimmten Punkt stoppt; eine erzwungene Beendigung kann zwischen zwei Prüfungen erfolgen.
Stellen Sie die Deadline als absoluten Zeitpunkt oder als verbleibende Zeit dar, die anhand einer monotonen Uhr berechnet wird. Prüfen Sie vor Beginn einer Phase, ob noch Zeit verfügbar ist. So wird verhindert, dass eine Folge von Vorgängen mit individuellen Timeouts deutlich mehr Zeit als das globale Zeitbudget verbraucht. Kapseln Sie in PHP die Berechnung und die Abfrage des Abbruchs in einem gemeinsam genutzten Objekt oder Dienst, statt verstreute Vergleiche zu duplizieren.
Das Zeitbudget auf Phasen und Abhängigkeiten verteilen
Teilen Sie den Prozess in beobachtbare Phasen auf, zum Beispiel Lesen, Transformation, Schreiben und externe Koordination. Legen Sie fest, wie viel Zeit jede Phase verbrauchen darf, ohne die globale Deadline zu überschreiten. Die Fristen müssen nicht alle gleich sein: Eine Phase mit begrenzter lokaler Arbeit kann ein anderes Limit benötigen als ein Aufruf an einen Remote-Dienst.
Verwenden Sie beim Aufruf einer Abhängigkeit den kleineren Wert aus ihrem operativen Timeout und der verbleibenden Prozesszeit. Konfigurieren Sie Verbindungs- und Lesezeitlimits getrennt, sofern die Bibliothek dies ermöglicht. Eine schnelle Verbindung verhindert nicht, dass die Antwort zu lange dauert, und ein Lese-Timeout umfasst nicht unbedingt die gesamte vorherige Wartezeit. Wenden Sie dasselbe Prinzip auf SQL-Abfragen, Speichervorgänge und Unterprozesse an und nutzen Sie dabei die Kontrollmöglichkeiten des jeweiligen Clients.
Wenn ein Vorgang keinen Abbruch unterstützt, stellen Sie ihn nicht als sofort unterbrechbar dar. Sie können begrenzen, wie lange Sie warten, ihn in einer separaten Arbeitseinheit isolieren oder den Ablauf so gestalten, dass der Abbruch davor und danach geprüft wird. Bewahren Sie bei Unterprozessen die erforderliche Kennung auf, um deren Beendigung anzufordern, und prüfen Sie, ob der Prozess tatsächlich beendet wurde; ein Signal zu senden bestätigt nicht, dass der Prozess beendet ist.
Arbeit beenden und einen verständlichen Status hinterlassen
Wenn die Deadline abläuft oder ein Abbruch eingeht, starten Sie keine neuen Phasen. Schließen Sie an sicheren Punkten die unbedingt erforderlichen lokalen Aktionen ab, um das Ergebnis zu protokollieren, und geben Sie kontrollierbare Ressourcen wie Cursor oder temporäre Dateien frei. Der Bereinigungsblock sollte nicht versuchen, die ausstehende Arbeit abzuschließen: Auch seine Dauer muss begrenzt sein, und er darf den Grund für die Beendigung nicht verschleiern.
Speichern Sie den Aufgabenstatus dauerhaft und verknüpfen Sie ihn mit einer stabilen Kennung. Erfassen Sie mindestens unterscheidbare Statuswerte wie running, completed, cancelled und timed_out sowie die aktuelle Phase und relevante Zeitstempel. Fügen Sie einen für Operatoren verständlichen Grund hinzu, ohne unnötige sensible Daten zu speichern. Aktualisieren Sie den Status nach Möglichkeit atomar und berücksichtigen Sie, dass der Prozess abstürzen oder beendet werden kann, bevor diese Aktualisierung abgeschlossen ist.
Das Ergebnis, das dem Initiator der Aufgabe angezeigt wird, muss zwischen „abgebrochen“ und „wegen Fristüberschreitung fehlgeschlagen“ unterscheiden. Lässt sich der Status nach einer abrupten Beendigung noch nicht bestätigen, zeigen Sie ihn als unbekannt oder als noch zu überprüfend an, nicht als abgeschlossen. Metriken und strukturierte Protokolle können Dauer, Phase und Art der Beendigung enthalten; vermeiden Sie es, Geheimnisse, private Inhalte oder nicht zugriffsgeschützte Stacktraces zu protokollieren.
Entscheiden, ob sich Teilergebnisse fortsetzen lassen
Eine Unterbrechung kann eintreten, nachdem Daten geschrieben wurden, aber bevor die Phase als abgeschlossen gespeichert wurde. Die Entscheidung, ob sich die Arbeit fortsetzen lässt, besteht daher nicht nur darin, die Phasennummer zu speichern: Sie müssen verstehen, welche Auswirkungen bereits eingetreten sein könnten. Bei lokalen Vorgängen kann eine Transaktion zusammengehörige Änderungen bündeln, sofern Dauer und Engine dies zulassen. Halten Sie eine Transaktion nicht während eines langen externen Aufrufs offen.
Protokollieren Sie bei Batch-Verarbeitung abgeschlossene Einheiten mit einem stabilen Schlüssel und legen Sie fest, wann eine Einheit tatsächlich bestätigt ist. Wird ein Schreibvorgang beim Neustart wiederholt, muss der Entwurf Duplikate verhindern oder erkennen, dass die Auswirkung bereits eingetreten ist. Eine externe Integration, die keinen Abbruch unterstützt, kann eine Anfrage angenommen haben, obwohl der Client nicht länger gewartet hat; der Status muss diese Unsicherheit widerspiegeln, statt anzunehmen, dass keine Auswirkung eingetreten ist.
Wenn eine Fortsetzung ab dem letzten gespeicherten Punkt nicht sicher ist, markieren Sie die Aufgabe zur Überprüfung oder für eine spezielle Wiederherstellung. Eine automatische Fortsetzung ab einem ungenauen Marker kann ein falsches Ergebnis erzeugen. Dokumentieren Sie, welche Phasen wiederholbar sind, welche überprüft werden müssen und unter welchen Bedingungen der Prozess als abgeschlossen gelten kann.
Zeitlimits und Unterbrechungen testen
Tests müssen sowohl die Beendigung als auch den Endstatus prüfen, nicht nur, ob eine Ausnahme ausgelöst wurde. Verwenden Sie kontrollierbare Abhängigkeiten, um einen langsamen Aufruf, eine Anfrage, die ihr Zeitlimit überschreitet, und eine Antwort zu simulieren, die genau an der Grenze eintrifft. Prüfen Sie, dass die Dauer einer Phase die globale Deadline nicht außer Kraft setzt.
Testen Sie einen Abbruch zwischen Phasen und während eines Vorgangs, der erst nach seiner Rückkehr angehalten werden kann. Simulieren Sie außerdem abrupte Beendigungen an relevanten Punkten: nach einer externen Auswirkung, vor dem Speichern des Status und während der Bereinigung. Prüfen Sie nach dem Neustart des Workers, ob der Status verständlich ist und die Wiederaufnahmerichtlinie weder Arbeit dupliziert noch auslässt. Diese Tests müssen isolierte Daten verwenden und dürfen nicht von echten externen Diensten abhängen.
Checkliste für einen bestehenden Prozess

- Ermitteln Sie die Limits von PHP, Server, Worker und allen verwendeten Clients.
- Legen Sie eine globale Deadline und Zeitbudgets für Phasen fest, deren Summe sie nicht überschreitet.
- Geben Sie die verbleibende Zeit nach Möglichkeit an HTTP, die Datenbank und andere Prozesse weiter.
- Prüfen Sie den Abbruch an sicheren Punkten und starten Sie nach seinem Eingang keine neue Arbeit.
- Speichern Sie unterschiedliche Statuswerte, die aktuelle Phase und den Grund für die Beendigung dauerhaft.
- Beschreiben Sie, welche Teilergebnisse zurückbleiben können und unter welchen Bedingungen eine Fortsetzung möglich ist.
- Testen Sie Verzögerungen, Abbrüche, abrupte Beendigungen und den nach einem Neustart sichtbaren Status.
Ein solides Design verspricht nicht, dass sich jeder Vorgang sofort beenden lässt. Es macht die verfügbare Zeit explizit, begrenzt die Wartezeit bei jeder Abhängigkeit und bewahrt genügend Informationen, um eine Unterbrechung einzuordnen. Diese Kombination macht aus einem lang laufenden Prozess eine steuerbare und überprüfbare Arbeitseinheit statt einer undurchsichtigen Ausführung.



