Около 2% запросов отдают 500, всегда с одного хоста, и локально это не воспроизводится. Определите причину.
Прод отдаёт 500 примерно на 2% запросов. Ни локально, ни на staging это не воспроизводится, а все четыре сервера приложения выкачены час назад с одного тега релиза.
Ограничения: балансировщик здоров, база здорова, а падающие запросы равномерно размазаны по маршрутам, а не собраны на одном эндпоинте. У вас есть трекер ошибок и доступ по shell на каждый хост.
[error tracker] 1 issue, 2141 events in 1h server_name: app-03 (100%)
Error: Call to undefined method App\Billing\Invoice::totalWithTax()
/var/www/current/app/Http/Controllers/InvoiceController.php:88
release: 20260710-1a2b3c
[app-03] readlink /var/www/current -> /var/www/releases/20260710-1a2b3c
[app-03] php-fpm master started -> 31 days ago
[app-03] opcache.validate_timestamps -> 0
[app-03] opcache_get_status(): cached script
/var/www/current/app/Billing/Invoice.php timestamp 2026-07-09
Определите причину.
Симлинк на app-03 смотрит на новый релиз, но мастер FPM там ни разу не перезапускали, а opcache.validate_timestamps равен 0, поэтому воркеры до сих пор выполняют скомпилированный Invoice прошлого релиза — класс, в котором нет totalWithTax(). Сброс OPcache после переключения молча не отработал на этом хосте. Сбросьте там OPcache или перезагрузите FPM и заставьте деплой падать, если хост не подтвердил сброс.
- ✗Читать отказ, запертый на одном хосте, как проблему железа, а не как устаревший рантайм на этом хосте
- ✗Верить, что симлинк на новый релиз означает, что выполняется именно новый код
- ✗Позволять деплою рапортовать успех, когда шаг сброса OPcache упал на одной машине парка
- →При
opcache.validate_timestampsравном 0 что вообще заставляет PHP перечитать изменившийся файл? - →Где в деплое место сбросу OPcache и как доказать, что он отработал на каждом хосте?
Три сигнала складываются в один вывод.
readlink /var/www/current -> новый релиз (симлинк переключён)
php-fpm master started -> 31 день назад (FPM не перезагружали)
opcache.validate_timestamps -> 0 (mtime не перепроверяется)
opcache_get_status() -> Invoice.php от 2026-07-09 (прошлый релиз)
Симлинк переключился, а исполняется не он. При validate_timestamps=0 OPcache никогда не сверяет mtime файла: однажды скомпилированный скрипт живёт в разделяемой памяти до явного сброса. Мастер FPM на app-03 не перезапускали 31 день, значит сброса там не было — и воркеры продолжают выполнять Invoice прошлого релиза, где метода totalWithTax() ещё нет. Отсюда и Call to undefined method ровно на одном хосте, и «2% запросов»: это доля трафика, которую балансировщик отдаёт app-03.
Локально не воспроизводится по той же причине: на dev validate_timestamps=1.
# лечение на месте
cachetool opcache:reset --fcgi=127.0.0.1:9000 # или systemctl reload php8.3-fpm
# и в деплой — проверяемым шагом, а не best-effort
for host in app-01 app-02 app-03 app-04; do
ssh "$host" 'cachetool opcache:reset --fcgi=127.0.0.1:9000' || exit 1
done
Ключевое — деплой обязан падать, если сброс не подтверждён хотя бы одним хостом. Молча пропущенный шаг оставляет один сервер в прошлом релизе, и это выглядит не как сломанный деплой, а как «плавающая» ошибка.