それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

laravel で ジョブ・キューを行う

laravel で ジョブ・キューを行う

  • ジョブキューを体験する
    • ジョブキューの準備
    • コマンドの準備
    • ジョブ・キュー実行する
  • 失敗時の取り扱い

ジョブキューを体験する

Laravelのジョブとキューの仕組みを作って体験してみる。

WEB-UIを作るとめんどくさいので、コマンド・コンソールからジョブ・キューを作って試す。

プロジェクトの準備

プロジェクトを作る && プロジェクト基本設定

composer create-project laravel/laravel job-queue-example

プロジェクトの初期設定

cd job-queue-example/
sed -r 's/^(VITE|PUSHER|AWS|MAIL|REDIS|DB|MEMCACHE|QUEUE)/#\1/' -i .env  
sed -e '/DB_CONNECTION/i QUEUE_CONNECTION=database' -i .env
sed -e '/DB_CONNECTION/i DB_CONNECTION=sqlite'     -i .env
touch database/database.sqlite
sed '/indent_size/d' -i .editorconfig # インデントはエディタに従う

ジョブキューの準備(テーブル)

php artisan queue:table
php artisan queue:failed-table
php artisan migrate

ジョブを投入するコマンドを作る

php artisan make:command AddMyJob

ジョブを作る

php artisan make:job MyJob

ソースコードを書く

app/Jobs/MyJob.php

<?php
class MyJob implements ShouldQueue {
  use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

  public function __construct (
    protected string $message //このジョブは文字列を受け取る
  ) {}

  public function handle (): void {
    dump($this->message);
  }
}

app/Console/Commands/AddMyJob.php

<?php
class AddMyJob extends Command {
  protected $signature = 'app:add-my-job {message}';

  public function handle () {
    $msg = $this->argument('message');//ジョブに文字列を渡す。
    MyJob::dispatch($msg);
  }
}

ジョブを動かす。

ワーカーの起動

php artisan -v queue:work

ジョブの投入

php artisan app:add-my-job 'Hello MyJob'

結果の確認

ワーカ側にジョブの実行結果が表示される

INFO  Processing jobs from the [default] queue.

  2023-11-28 08:24:49 App\Jobs\MyJob 2 .................... RUNNING
"Hello MyJob" // app/Jobs/MyJob.php:19
  2023-11-28 08:24:50 App\Jobs\MyJob 2 ............... 23.96ms DONE

ただ、これだけど、まともに使えるとは言い難い。

ジョブを失敗させる。

<?php
class MyJob implements ShouldQueue {
  /* 中略 */ 
  public function handle (): void {
    throw new \Exception('Error');
  }
}

再起動して失敗ジョブをテストする。

## 再起動(ワーカー
php artisan -v queue:work 

ジョブの投入

php artisan app:add-my-job 'Hello MyJob'

結果

  INFO  Processing jobs from the [default] queue.

  2023-11-28 08:30:27 App\Jobs\MyJob 4 .................... RUNNING
  2023-11-28 08:30:27 App\Jobs\MyJob 4 ............... 20.65ms FAIL

失敗ジョブの確認

php artisan queue:failed

ジョブは実行されて、jobs テーブルから削除され、failed に移動する。

リトライ機能で再挑戦する。

php artisan queue:retry all

ジョブ・キュのポイント

失敗させてわかるのだが、 ここで問題になるのは、ワーカ再起動が必要な点。

また、デフォルトでワーカがジョブをチェックするのは3秒毎である。このタイミングを伸ばせる

php artisan -v queue:work --sleep=10 # 10s毎にチェック php artisan -v queue:work --sleep=30 # 30s毎 php artisan -v queue:work --sleep=60 # 60s毎

だが、--sleep=60の場合,max_exection_time(default=30sec)を超えてsleepすると停止する。

キューに名前をつける

指定した名前のキューだけを待つ。

キューに投入するときに名前を指定する

<?php
class AddMyJob extends Command {
  protected $signature = 'app:add-my-job {message}';

  public function handle () {
    $msg = $this->argument('message');
    // ->onQueue('myQueue'); で名前を指定
    MyJob::dispatch($msg)->onQueue('myQueue');
  }
}

名前が一致しないジョブは処理対象から外される。

php artisan -v queue:work --queue=myQueue

ジョブの構造

ジョブにはペイロードが設定されている。ここに実行するジョブの情報が入っている。

array:10 [
  "uuid" => "3135f558-cf36-479c-ad42-cb10cea6f069"
  "displayName" => "App\Jobs\MyJob"
  "job" => "Illuminate\Queue\CallQueuedHandler@call"
  "maxTries" => null
  "maxExceptions" => null
  "failOnTimeout" => false
  "backoff" => null
  "timeout" => null
  "retryUntil" => null
  "data" => array:2 [
    "commandName" => "App\Jobs\MyJob"
    "command" => "O:14:"App\Jobs\MyJob":1:{s:10:"\x00*\x00message";s:11:"Hello MyJob";}"
  ]
]

ペイロードをみると、commandNameがジョブを実行するクラスで "App\Jobs\MyJob"となっている。 "App\Jobs\MyJob"のインスタンスが、commandに serialize()されて格納されているとわかる。

ここから、Modelは必須ではない。とわかる。

時間のかかるジョブ

class MyJob implements ShouldQueue {
  /** 略 **/
  public function handle (): void {
    sleep(31);
    dump($this->message);
  }
}

時間のかかるジョブを作ると・・・このように、中断されてしまう。ことがある。

takuya@wsl:job-queue-example$ php artisan -v queue:work

   INFO  Processing jobs from the [default] queue.

  2023-11-28 09:10:20 App\Jobs\MyJob 23 .......... RUNNING
  2023-11-28 09:11:20 App\Jobs\MyJob 23 .......... 1m FAIL
Killed

このあたりを考えると、「時間のかかるジョブ」を後ろに回すと考えるのは、早計であるとわかる。

laravelのジョブの実行時間設定、phpのmax_execution_timeをちゃんと確認してからジョブを回しましょう

参考資料