Импорт, который длится часами, отчёт, запрашивающий данные из нескольких источников, или задача, координирующая интеграции, могут продолжать потреблять ресурсы ещё долго после того, как перестанут быть полезными. Без явно заданных ограничений также трудно понять, процесс ещё выполняется, заблокирован или завершился не полностью. При проектировании ограничений времени для длительных PHP-процессов нужно определить, сколько может длиться каждый этап, как останавливается работа и какой статус сохраняется при прерывании выполнения.
Цель не в том, чтобы задать одно значение для всего процесса. Нужно установить согласованный общий бюджет времени, передавать его зависимым операциям и отражать произошедшее в конечном статусе. Тайм-аут, запрос на отмену и принудительное завершение — разные ситуации, и обрабатывать их следует по-разному.
Почему необходимо явное ограничение

Процесс без срока выполнения может бесконечно удерживать память, соединения и ресурсы для выполнения. Кроме того, задача может оставаться помеченной как активная, хотя прогресс уже не идёт. В среде с worker-процессами проблема усугубляется: заблокированный запуск занимает worker и может задерживать последующие задачи.
Ограничения PHP или сервера не заменяют политику приложения. Например, max_execution_time может ограничить выполнение скрипта, но не обязательно соответствует общему времени ожидания, приемлемому для бизнеса. Кроме того, ограничения веб-сервера, PHP-FPM, менеджера очередей, HTTP-клиента и базы данных могут действовать независимо друг от друга. Если они различаются, один уровень может завершить работу, не дав другому сохранить соответствующий статус.
Сначала определите, где выполняется процесс и какие компоненты могут его остановить. У задачи командной строки, worker-процесса очереди и веб-запроса разные механизмы и последствия. Ограничение на уровне приложения должно сочетаться с внешними средствами контроля, а не полагаться на то, что одно из них самостоятельно обеспечит завершение.
Различайте тайм-аут, deadline и отмену
- Тайм-аут операции: максимальное время ожидания конкретного вызова, например запроса к базе данных или HTTP-запроса.
- Общий deadline: предельный момент времени для всего процесса. Этапы используют оставшееся время, а не получают каждый полный срок выполнения.
- Запрос на отмену: явное решение прекратить работу, например потому, что пользователь отменил задачу или система удалила её из очереди.
Тайм-аут означает, что операция не завершилась в пределах выделенного ей времени. Сам по себе он не обязывает останавливать весь процесс: это зависит от этапа и правил продукта. Отмена, напротив, — сигнал управления, который процесс должен проверять и обрабатывать, когда это можно сделать безопасно. Ни тайм-аут, ни отмена не гарантируют, что операционная система или supervisor остановит выполнение в точный момент: принудительное завершение может произойти между проверками.
Представляйте deadline как абсолютный момент времени или как оставшееся время, рассчитанное по монотонным часам. Перед началом этапа проверяйте, осталось ли время. Так последовательность операций с отдельными тайм-аутами не израсходует значительно больше времени, чем предусмотрено общим deadline. В PHP инкапсулируйте расчёт и проверку отмены в общем объекте или сервисе, а не дублируйте разрозненные сравнения.
Распределяйте бюджет времени между этапами и зависимостями
Разбейте процесс на наблюдаемые этапы, например чтение, преобразование, запись и координацию с внешними системами. Определите, сколько времени может занять каждый этап, не превышая общий deadline. Значения не обязательно должны быть одинаковыми: этап с ограниченной локальной работой может требовать иного лимита, чем вызов удалённого сервиса.
При вызове зависимости используйте меньшее из двух значений: её рабочий тайм-аут и оставшееся время процесса. Если библиотека позволяет, задавайте ограничения на установление соединения и чтение отдельно. Быстрое соединение не гарантирует быстрого ответа, а тайм-аут чтения не обязательно включает всё предшествующее время. Применяйте тот же принцип к SQL-запросам, операциям с хранилищем и дочерним процессам, используя доступные средства контроля соответствующего клиента.
Если операцию нельзя отменить, не представляйте её как немедленно прерываемую. Можно ограничить время ожидания, изолировать её в отдельной рабочей единице или проверять отмену до и после выполнения. Для дочерних процессов сохраняйте идентификатор, необходимый для запроса на завершение, и проверяйте, что процесс действительно остановился: отправка сигнала не означает подтверждённого завершения.
Останавливайте работу и сохраняйте понятный статус
Когда истекает deadline или поступает запрос на отмену, не начинайте новые этапы. В безопасных точках выполните необходимые локальные действия для регистрации результата и освободите контролируемые ресурсы, например курсоры или временные файлы. Очистка не должна пытаться завершить оставшуюся работу: у неё тоже должно быть ограниченное время выполнения, и она не должна скрывать причину остановки.
Сохраняйте статус задачи в долговременном хранилище и связывайте его со стабильным идентификатором. Как минимум фиксируйте различимые статусы, например running, completed, cancelled и timed_out, а также текущий этап и важные временные отметки. Добавьте понятное операторам описание причины, не сохраняя ненужные конфиденциальные данные. По возможности обновляйте статус атомарно и учитывайте, что процесс может завершиться до окончания обновления.
Результат, отображаемый инициатору задачи, должен различать статусы «отменена» и «не завершилась в срок». Если после аварийного завершения статус пока нельзя подтвердить, показывайте его как неизвестный или ожидающий проверки, а не как завершённый. В метрики и структурированные логи можно включать длительность, этап и тип остановки; не записывайте секреты, конфиденциальное содержимое или трассировки без контроля доступа.
Определите, можно ли возобновить работу после частичных изменений
Прерывание может произойти после записи данных, но до регистрации завершения этапа. Поэтому возможность возобновления определяется не только сохранённым номером этапа: нужно понимать, какие изменения могли произойти. Для локальных операций транзакция может объединить связанные изменения, если это допускают её длительность и СУБД. Не держите транзакцию открытой во время длительного внешнего вызова.
При пакетной обработке фиксируйте завершённые единицы работы со стабильным ключом и определяйте, когда единица действительно считается подтверждённой. Если после перезапуска запись выполняется повторно, система должна предотвращать дубликаты или распознавать уже существующий результат. Внешняя интеграция, которую нельзя отменить, могла принять запрос, хотя клиент перестал ждать ответа; статус должен отражать эту неопределённость, а не предполагать, что изменений не произошло.
Если безопасно продолжить с последней сохранённой точки нельзя, пометьте задачу для проверки или специального восстановления. Автоматическое возобновление с неточной контрольной точки может привести к неверному результату. Задокументируйте, какие этапы можно повторять, какие требуют проверки и при каких условиях процесс можно считать завершённым.
Тестируйте ограничения и прерывания
Тесты должны проверять и остановку, и конечный статус, а не только факт выброса исключения. Используйте контролируемые зависимости, чтобы моделировать медленный вызов, запрос, превысивший лимит времени, и ответ, поступающий точно на границе срока. Убедитесь, что длительность этапа не позволяет игнорировать общий deadline.
Проверьте отмену между этапами и во время операции, которую можно остановить только после её возврата. Также смоделируйте аварийное завершение в важных точках: после внешнего эффекта, до сохранения статуса и во время очистки. После перезапуска worker-процесса проверьте, что статус понятен, а политика возобновления не приводит к дублированию или пропуску работы. В таких тестах следует использовать изолированные данные и не обращаться к реальным внешним сервисам.
Контрольный список для существующего процесса

- Определите ограничения PHP, сервера, worker-процесса и каждого используемого клиента.
- Задайте общий deadline и бюджеты этапов, суммарно не превышающие его.
- По возможности передавайте оставшееся время HTTP-клиенту, базе данных и другим процессам.
- Проверяйте отмену в безопасных точках и не начинайте новую работу после её получения.
- Сохраняйте различные статусы, текущий этап и причину завершения.
- Опишите возможные частичные изменения и условия возобновления работы.
- Тестируйте задержки, отмены, аварийные завершения и статус после перезапуска.
Надёжная архитектура не обещает, что любую операцию можно остановить мгновенно. Она явно задаёт доступное время, ограничивает ожидание каждой зависимости и сохраняет достаточно информации, чтобы понять, что произошло при прерывании. Благодаря этому длительный процесс перестаёт быть непрозрачным выполнением и становится управляемой и проверяемой рабочей единицей.



