Import trwający godzinami, raport pobierający dane z kilku źródeł lub zadanie koordynujące integracje może zużywać zasoby długo po tym, jak przestanie być użyteczne. Bez jawnych limitów trudno też ustalić, czy proces nadal pracuje, utknął czy nie został ukończony. Projektowanie limitów czasu dla długotrwałych procesów PHP wymaga określenia, jak długo może trwać każdy etap, jak zatrzymać pracę i jaki status zachować, jeśli wykonanie zostanie przerwane.
Celem nie jest narzucenie jednej wartości dla całego procesu. Chodzi o ustalenie spójnego budżetu czasowego od początku do końca, przekazywanie go operacjom zależnym i zapewnienie, że końcowy status odzwierciedla przebieg pracy. Timeout, żądanie anulowania i wymuszone zakończenie to różne sytuacje, które należy traktować odmiennie.
Dlaczego potrzebny jest jawny limit

Proces bez deadline’u może bezterminowo zajmować pamięć, połączenia i zasoby obliczeniowe. Może też pozostawić zadanie oznaczone jako aktywne, mimo że nie robi ono już postępów. W środowisku z workerami problem się nasila: zablokowane wykonanie zajmuje workera i może opóźniać kolejne zadania.
Limity PHP lub serwera nie zastępują polityki aplikacji. Na przykład max_execution_time może ograniczać czas wykonywania skryptu, ale niekoniecznie odpowiada całkowitemu czasowi oczekiwania akceptowanemu przez biznes. Ponadto limity serwera WWW, PHP-FPM, systemu kolejkowego, klienta HTTP i bazy danych mogą działać niezależnie. Jeśli się różnią, jedna warstwa może zakończyć pracę, zanim inna zdąży zapisać odpowiedni status.
Najpierw ustal, gdzie działa proces i które komponenty mogą go zatrzymać. Zadanie konsolowe, worker kolejki i żądanie WWW korzystają z różnych mechanizmów i mają różne konsekwencje. Limit aplikacji powinien współistnieć z tymi zewnętrznymi kontrolami, a nie polegać na tym, że któraś z nich sama rozwiąże problem zakończenia.
Rozróżnij timeout, deadline i anulowanie
- Timeout operacji: maksymalny czas oczekiwania na konkretne wywołanie, takie jak zapytanie lub żądanie HTTP.
- Globalny deadline: graniczny moment dla całego procesu. Etapy dzielą pozostały czas, zamiast każdy z nich otrzymywać cały dostępny czas.
- Żądanie anulowania: jawna decyzja o zaprzestaniu pracy, na przykład dlatego, że użytkownik anulował zadanie lub system usunął je z kolejki.
Timeout informuje, że operacja nie zakończyła się w wyznaczonym budżecie. Sam w sobie nie oznacza, że cały proces powinien się zatrzymać: zależy to od etapu i reguł produktu. Anulowanie jest natomiast sygnałem sterującym, który proces powinien sprawdzać i obsługiwać, gdy może to zrobić bezpiecznie. Żadne z tych rozwiązań nie gwarantuje, że system operacyjny lub supervisor zatrzyma wykonanie w dokładnie określonym punkcie; wymuszone zakończenie może nastąpić między kolejnymi sprawdzeniami.
Reprezentuj deadline jako bezwzględny moment w czasie lub jako pozostały czas obliczany na podstawie zegara monotonicznego. Przed rozpoczęciem etapu sprawdź, czy pozostał jeszcze zapas czasu. Dzięki temu sekwencja operacji z indywidualnymi timeoutami nie przekroczy znacznie globalnego limitu. W PHP umieść obliczanie czasu i sprawdzanie anulowania we współdzielonym obiekcie lub serwisie, zamiast powielać rozproszone porównania.
Podziel budżet między etapy i zależności
Podziel proces na obserwowalne etapy, na przykład odczyt, transformację, zapis i koordynację zewnętrzną. Określ, ile czasu może zużyć każdy z nich, tak aby ich wykonanie mieściło się w globalnym deadline’ie. Limity nie muszą być identyczne: etap z ograniczoną pracą lokalną może wymagać innego limitu niż wywołanie zdalnej usługi.
Przy wywoływaniu zależności używaj mniejszej z wartości: jej operacyjnego timeoutu i czasu pozostałego do deadline’u procesu. Jeśli biblioteka na to pozwala, osobno konfiguruj limity nawiązania połączenia i odczytu. Szybkie połączenie nie oznacza, że odpowiedź również nadejdzie szybko, a timeout odczytu niekoniecznie obejmuje cały wcześniejszy czas. Stosuj tę samą zasadę do zapytań SQL, operacji przechowywania danych i procesów podrzędnych, korzystając z mechanizmów udostępnianych przez używane biblioteki klienckie.
Jeśli operacji nie można anulować, nie przedstawiaj jej jako natychmiastowo przerywalnej. Możesz ograniczyć czas oczekiwania, odizolować ją w osobnej jednostce pracy lub zaprojektować przepływ tak, aby sprawdzać anulowanie przed operacją i po niej. W przypadku procesów podrzędnych zachowaj identyfikator potrzebny do zażądania ich zakończenia i sprawdź, czy proces rzeczywiście się zakończył; wysłanie sygnału nie potwierdza jego zakończenia.
Zatrzymaj pracę i pozostaw zrozumiały status
Gdy deadline upłynie lub nadejdzie żądanie anulowania, nie rozpoczynaj kolejnych etapów. W bezpiecznych punktach dokończ lokalne działania niezbędne do zarejestrowania wyniku i zwolnij zasoby, nad którymi masz kontrolę, takie jak kursory czy pliki tymczasowe. Blok czyszczenia nie powinien próbować kończyć pozostałej pracy: sam również powinien mieć ograniczony czas działania i nie może ukrywać przyczyny zatrzymania.
Zapisuj status zadania w trwałym miejscu, powiązanym ze stabilnym identyfikatorem. Co najmniej rejestruj rozróżnialne statusy, na przykład running, completed, cancelled i timed_out, a także bieżący etap i istotne znaczniki czasu. Dodaj zrozumiały dla operatorów powód, nie zapisując niepotrzebnych danych wrażliwych. Jeśli to możliwe, aktualizuj status atomowo i uwzględnij możliwość, że proces zakończy się przed ukończeniem tej aktualizacji.
Wynik prezentowany osobie, która uruchomiła zadanie, powinien odróżniać stan „anulowane” od „niepowodzenie z powodu przekroczenia limitu czasu”. Jeśli po nagłym zakończeniu nie można jeszcze potwierdzić statusu, pokaż go jako nieznany lub oczekujący na weryfikację, a nie jako ukończony. Metryki i logi strukturalne mogą zawierać czas trwania, etap i rodzaj zatrzymania; unikaj rejestrowania sekretów, prywatnych treści i śladów bez kontroli dostępu.
Określ, czy można wznowić pracę po częściowych efektach
Przerwanie może nastąpić po zapisaniu danych, ale przed zarejestrowaniem zakończenia etapu. Dlatego decyzja o możliwości wznowienia nie polega wyłącznie na zapisaniu numeru etapu: trzeba zrozumieć, jakie efekty mogły już wystąpić. W przypadku operacji lokalnych transakcja może grupować powiązane zmiany, jeśli pozwalają na to jej czas trwania i używany silnik. Nie utrzymuj otwartej transakcji podczas długiego wywołania zewnętrznego.
W pracy wsadowej rejestruj ukończone jednostki za pomocą stabilnego klucza i określ, kiedy jednostka jest rzeczywiście zatwierdzona. Jeśli po ponownym uruchomieniu zapis zostanie powtórzony, projekt powinien zapobiegać duplikatom lub rozpoznawać, że efekt już istnieje. Integracja zewnętrzna, której nie można anulować, mogła przyjąć żądanie, nawet jeśli klient przestał czekać; status powinien odzwierciedlać tę niepewność, a nie zakładać, że nie wystąpił żaden efekt.
Jeśli wznowienie od ostatniego zapisanego punktu nie jest bezpieczne, oznacz zadanie do sprawdzenia lub dedykowanej procedury odzyskiwania. Automatyczne wznowienie od nieprecyzyjnego znacznika może doprowadzić do nieprawidłowego wyniku. Udokumentuj, które etapy można powtarzać, które wymagają weryfikacji i jakie warunki pozwalają uznać proces za ukończony.
Testuj limity i przerwania
Testy powinny weryfikować zarówno zatrzymanie, jak i końcowy status, a nie tylko sprawdzać, czy rzucono wyjątek. Korzystaj z kontrolowanych zależności, aby symulować wolne wywołanie, zapytanie przekraczające deadline i odpowiedź przychodzącą dokładnie na granicy limitu. Sprawdź, czy czas trwania etapu nie pozwala zignorować globalnego deadline’u.
Przetestuj anulowanie między etapami oraz podczas operacji, którą można zatrzymać dopiero po jej powrocie. Symuluj też nagłe zakończenie w istotnych punktach: po efekcie zewnętrznym, przed zapisaniem statusu i podczas czyszczenia. Po ponownym uruchomieniu workera sprawdź, czy status jest zrozumiały i czy polityka wznawiania nie duplikuje ani nie pomija pracy. Testy powinny korzystać z odizolowanych danych i nie mogą zależeć od rzeczywistych usług zewnętrznych.
Lista kontrolna dla istniejącego procesu

- Ustal limity PHP, serwera, workera i każdego używanego klienta.
- Określ globalny deadline i budżety etapów tak, aby praca wszystkich etapów mieściła się w jego ramach.
- Jeśli to możliwe, przekazuj pozostały czas do HTTP, bazy danych i innych procesów.
- Sprawdzaj anulowanie w bezpiecznych punktach i po jego otrzymaniu nie rozpoczynaj nowej pracy.
- Zapisuj odrębne statusy, bieżący etap i przyczynę zakończenia.
- Opisz, jakie częściowe efekty mogą pozostać i jakie warunki pozwalają wznowić pracę.
- Testuj opóźnienia, anulowanie, nagłe zakończenia i status widoczny po ponownym uruchomieniu.
Solidny projekt nie obiecuje, że każdą operację można natychmiast zatrzymać. Jawnie określa dostępny czas, ogranicza oczekiwanie na każdą zależność i zachowuje informacje wystarczające do zinterpretowania przerwania. Dzięki temu długotrwały proces przestaje być nieprzejrzystym przebiegiem, a staje się jednostką, którą można obsługiwać i weryfikować.



