cronジョブやキューワーカーが静かに停止していても、エラーログには何も残らない。本番環境で見落としがちな「無音障害」の実態と、Laravelで今すぐ実装できる死活監視の具体的な手順を解説します。
こんな状況、心当たりはありませんか?
「毎日夜中に自動でメールが送られているはずなのに、お客様から『最近メールが届かない』と言われた」——そんな報告が、ある日突然飛んでくる。
あるいは、バックグラウンドで注文データを処理するキューが止まっていて、気づいたのは3日後。その間に数百件のジョブが積み上がっていた……という事態は、決して珍しくありません。
cronジョブやキューワーカーが止まったとき、サーバーはエラーを吐かないのです。HTTPリクエストと違い、プロセスが死んでいても画面には何も表示されない。アクセスログにも残らない。監視ツールが反応しない。気づく手段がなければ、障害は静かに続きます。
この記事では、実際のクライアント案件で起きた「無音障害」の事例をもとに、LaravelのcronとキューワーカーをRailのように死活監視する実践的な実装手順を解説します。
なぜ「誰も気づかない」のか——無音障害の構造
Webアプリケーションの一般的な障害は、HTTPステータスコードやエラーログとしてすぐに可視化されます。しかしcronとキューは、本質的に非同期・バックグラウンドで動作するという特性上、失敗しても能動的に誰かへ通知しません。
典型的な「無音障害」のパターンは3つあります。
- cronジョブが実行されていない:サーバーのcrontab設定が消えていた、PHP-CLIのパスが変わっていた、
schedule:runコマンド自体がクラッシュしていた - キューワーカーがメモリ不足で落ちた:長時間稼働によるメモリリークでプロセスが終了し、Supervisorも再起動を諦めていた
- ジョブが失敗し続けている:最大リトライ回数を超えたジョブが
failed_jobsテーブルに積み上がっているが、誰も確認していない
こうした問題が厄介なのは、ビジネスへの影響が時間差で表れる点です。メール通知が止まれば顧客離れにつながり、受注処理が詰まれば手動対応のコストが発生する。「何か変だな」と気づいた頃には、すでに数日分のダメージが積み上がっています。
実際の案件で起きたこと
あるECサイトのクライアントから、「定期会員へのリマインドメールが届いていない」という連絡を受けたのは、障害発生から5日後のことでした。
調査すると、原因はサーバーリプレイス時にcrontabの設定を移行し忘れていたこと。php artisan schedule:runを1分ごとに呼び出すエントリが丸ごと消えていたため、メール送信スケジュールが完全に止まっていました。
ログには何も残っていません。500エラーも出ていない。外形監視ツール(死活監視URL)のアラートも鳴っていない。なぜなら、Webサーバーは正常に動いていたからです。
この経験を機に、当社ではすべてのLaravelプロジェクトにHeartbeat(ハートビート)監視とキュー監視を標準セットとして組み込むようにしました。
具体的な実装手順
1. Heartbeat監視でcronの「生存確認」を自動化する
Heartbeat監視とは、「定期的に外部サービスへ『生きています』と信号を送り、一定時間信号が途絶えたらアラートを上げる」という仕組みです。
代表的な無料サービスとして Healthchecks.io があります。アカウントを作成し、チェックのURLを発行したら、Laravelのスケジューラに組み込みます。
// app/Console/Kernel.php
protected function schedule(Schedule $schedule): void
{
// 例: 毎日深夜に会員リマインドメールを送信
$schedule->command('mail:send-reminders')
->dailyAt('02:00');
// Heartbeat: 毎分 healthchecks.io へ ping を送る
$schedule->call(function () {
$pingUrl = config('services.healthchecks.ping_url');
if ($pingUrl) {
Http::timeout(5)->get($pingUrl);
}
})->everyMinute()->name('healthcheck-ping')->withoutOverlapping();
}
.env に以下を追加します。
HEALTHCHECKS_PING_URL=https://hc-ping.com/your-unique-token
// config/services.php
'healthchecks' => [
'ping_url' => env('HEALTHCHECKS_PING_URL'),
],
これで、cronが止まると10分以内(期間は設定可能)にメール・Slackへ通知が届きます。
2. キューワーカーの監視——Supervisorとアラートの組み合わせ
キューワーカーの運用には Supervisor を使うのが定石ですが、Supervisorが再起動を試みて諦めた後はプロセスが落ちたままになります。これをLaravel側でも検知する仕組みを追加します。
; /etc/supervisor/conf.d/laravel-worker.conf
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/html/artisan queue:work redis \
--sleep=3 \
--tries=3 \
--max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/html/storage/logs/worker.log
stopwaitsecs=3600
この監視コマンドをスケジューラに登録します。
// Kernel.php に追加
$schedule->command('queue:monitor-health')->everyFiveMinutes();
3. failed_jobs テーブルの定期レポートを仕組み化する
失敗したジョブは failed_jobs テーブルに保存されますが、放置しがちです。週次で自動レポートをSlackに送る仕組みを作っておくと、問題の予兆を早期に掴めます。
$schedule->command('queue:failed-report')->weeklyOn(1, '09:00');
// queue:failed-report コマンドの核心部分
$failures = DB::table('failed_jobs')
->selectRaw('connection, queue, COUNT(*) as count')
->where('failed_at', '>=', now()->subWeek())
->groupBy('connection', 'queue')
->get();
// Slack通知などに渡す
よくある失敗パターンと対処法
失敗1:「Supervisorが動いているから大丈夫」という過信
Supervisorはプロセス管理ツールですが、ジョブの中身の正常性は保証しません。ワーカーが起動していても、全ジョブがタイムアウトで失敗し続けているケースがあります。ジョブの実行結果レベルでの監視は別途必要です。
失敗2:--tries を無制限にする
リトライ回数を設定しないと、壊れたジョブが永遠にキューに残り、ワーカーリソースを食い続けます。--tries=3 と --backoff=60(秒)を必ずセットしましょう。また、ジョブクラス側でも $tries と $backoff を明示することを推奨します。
失敗3:開発環境でしか動作確認していない
QUEUE_CONNECTION=sync(同期実行)のまま本番にデプロイすると、非同期処理がそもそも意図どおりに動きません。本番環境では redis または database ドライバを使い、staging環境でもワーカーを起動した状態でテストすることが必須です。
失敗4:サーバー移行時にcrontabを確認しない
冒頭の事例がまさにこれです。crontab -l の内容を .crontab.bak としてリポジトリに含めておくか、インフラをコード化(Ansible等)することで防げます。
flowchart TD
A[本番サーバー稼働中] --> B{cronは動いているか?}
B -->|はい| C{キューワーカーは生存中?}
B -->|いいえ| X[❌ 無音障害 — 誰も気づかない]
C -->|はい| D{failed_jobsは増えていないか?}
C -->|いいえ| X
D -->|正常| E[✅ Healthchecks.io にping送信]
D -->|失敗急増| F[⚠ Slack/メールにアラート]
E --> G[監視サービスがタイムアウトを検知]
G -->|pingあり| H[問題なし]
G -->|pingなし| I[🚨 アラート送信]サーバー管理、丸ごとお任せください
サーバー保守・運用
監視・障害対応・パフォーマンス改善まで、安定稼働をサポートします
※ 通常1営業日以内にご返信します
まとめと次のステップ
cronとキューの死活監視は、「あったほうがいい機能」ではなく、本番稼働の前提条件です。HTTPレイヤーの監視だけでは、バックグラウンド処理の障害は永遠に見つかりません。
Healthchecks.ioのような外部Heartbeatサービスは無料プランでも十分機能し、実装コストも1〜2時間程度です。それだけで、「5日後に気づく障害」を「10分で検知できる問題」に変えられます。
当社Fivenine Designでは、Laravelプロジェクトの新規開発・既存サイトの改修いずれにおいても、こうした死活監視の仕組みを標準で組み込んでいます。「今動いているサイトに監視が入っているか確認したい」「本番環境のインフラ設定を見直したい」という場合は、お気軽にご相談ください。