本日は小ネタです!

Laravelで簡易的なキューシステムを利用してみたのですが、動かなくなることがあり その理由について簡単に解説してみたいと思います。最後までお付き合いをいただければ幸いです。

本記事のゴール

Laravel Queue(キューワーカー)でデフォルトのsyncオプションを適用して Queueを発行した => 動かなくなった時の問題を特定し、解消します。

以下に手順を書きますが、結論をみたい!という方はこちら からどうぞ。

事象が起きた手順

バッチ処理を作成

Laravelでは、以下のコマンドでバッチ処理を作成することができます。

php artisan make:command [CommandName]

私の方ではコマンド名は「TestBatchCommand」のように末尾をCommandとしますが、それに沿わなくても作れると思います。

作ったコマンドは以下のように実行できます。

php artisan app:test-batch-command

「app:test-batch-command」とある部分については、作成したファイルの以下の部分に記載があり、make:command実行時に 自動的に設定されます。また、作成済みのバッチ一覧についてはphp artisan listから確認することも可能です。

app/Console/Command/TestBatchCommand.php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;

class TestBatchCommand extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'app:test-batch-command'; // <= ここに入る

    // 省略

    // ⇩ ここの説明を入れておくとphp artisan listを実行した時表示されて、どんなコメントかわかりやすい
    protected $description = 'Command description';

    // 省略
}

バッチから、Jobを呼ぶようにする。

このままでは、バッチの中身が重い処理だった場合。重複して呼ばれてしまいます。

例えば、以下のように実際に実行を行うhandle()の中で、100000行くらいMySQLのテーブルからレコードを取得してきた場合。

$manyRecords = User::where('from', date('2001-01-01'))
 ->where('to',date('2030-12-31'))
 ->get();

foreach($manyRecords as $manyRecord) {
  // 更新・登録など時間がかかり重い処理を1行ずつ行う。
}

メモリに一時的に結果を載せるため、並行して同じ処理が何度も呼ばれると(例えばシステムの要件面での要求から、毎時実行で処理しなければいけないのに、1時間以内に処理が収まりきらない など)最悪の場合PHPに割り当てられたメモリ上限をオーバーして、処理が失敗します。

参考URL:【PHP】Allowed memory size of *** bytes exhaustedエラーの対処法まとめ

対処としてはメモリの上限を上げることもできますが、無駄な重複処理は呼ばないようにするとシンプルに対処しましょう。

(そもそもの問題としては、**重い処理が設計上適切かはよく考えて実装を行う必要があります。**しかし、今回は設計面でも回避できないケースを前提として考えているので念の為お伝えします。)

Jobを作成

そこで、ジョブを作成し、キューから呼び出す という仕組みが必要になってきます。

PHPの場合非同期で処理を行うケースが少ないのでイメージしにくいところではありますが、

常時起動するキューワーカーが => ジョブクラスを呼び出す

という形で処理されるようになります。

php artisan make:job TestCommandJob

こちらで、ジョブを作成し、作成したCommandの中で以下のように書いて呼び出しを行います。

TestCommandJob::dispatch();
Syncモードで動かす

本来であれば、キューワーカーを別プロセスで常時立ち上げておかねば、CommandからJobは呼ばれません。 ただ、Laravelには主にテスト用に、即時ジョブが呼び出されるSyncモード というのがあり、今回はそれを使います。

特に、.envの下記の箇所が変更されていなければ、SyncモードでTestCommandJobは呼び出されるはずです。

QUEUE_CONNECTION=sync

# 以下のコマンドをプロジェクトルートで実行して反映
php artisan config:cache

ここを、databaseにするとMySQLのテーブルでキューの実行状況は管理できます。ほかにredismemcached,なんとdynamodbも選べるようです。AWS使っている人は一度は試してみたいものですね...!

さて、今日はSyncで動かした時に起きた問題についてです。

Jobが呼ばれなくなった。

私の方で、Jobクラスでテストを行っていて、以下のように処理を入れたことがありました。

public function handle() {
  // ...
  Log::info("### show log message to laravel.log");
  exit;
}
tail -f /path/to/app/storage/logs/laravel.log

でログ出力を待ち受けて確認できます。

すると、初回はログにメッセージが出たのですが、

### show log message to laravel.log

その後何度php artisan app:test-batch-command で呼び出しを行っても、このメッセージは出ませんでした!

なんだかJobが空振りしている気がします...。

結論

syncモードで再起動したい場合はキャッシュを手で消そう

結論としては、syncモードは状態をキャッシュファイルで管理している ので、それを消すことで、再度実行できます。

exitで Jobから出た場合は、正常にジョブが完了せず、判定上実行され続けていることになります。

/path/to/project/storage/framework/cache/data/[ascii2文字]/[ascii2文字]/[ascii40文字]

にファイルがあるので消しましょう。

cd /path/to/project/storage/framework/cache/data/[ascii2文字]/[ascii2文字]
rm [ascii40文字]

他にもsyncモードで動かしている場合、data以下に複数のディレクトリが存在する可能性もあります。どれが消したいファイルか、タイムスタンプから確認した上で消しましょう。

私はそこまでしていないのですが、開発用途であれば、/path/to/project/storage/framework/cache/data/丸ごと消す Command を作ってもいいかもです。

補足: 以下は役に立たなかった

syncモードでは、再起動やqueueのクリアは役に立たないようです。databaseモードであれば使えるでしょう。

php artisan queue:restart
php artisan queue:clear

参考記事: How to stop a laravel SyncQueue

まとめ

ここで思ったよりハマってしまいました。公式ドキュメント にも特にこの点の記載がなかったので、開発用途でなければLaravelでのキューは知見が多いdatabaseモードを素直に使うのが望ましいかもしれませんね...。

この記事が何かのお役に立てれば幸いです。
最後までお読みいただきありがとうございました!