これまでにコメントをまとめてデータベース登録するような機能を作成した.登録には1件あたり1.33秒が必要であり,5件登録すると7秒近くユーザが待たされることも確認した.ここでは,キュー (Queue,FIFO, 待ち行列) を導入することで,大量のデータ登録がバックグラウンドで処理され,ユーザが待たされることがなくなるような仕組みを作ってみよう.
まず,キューを利用するために,.env ファイルを編集する.つまりキューを同期せずにデータベースで管理するように編集する.
.env (抜粋)
BROADCAST_DRIVER=log
CACHE_DRIVER=file
# QUEUE_CONNECTION=sync
QUEUE_CONNECTION=database
SESSION_DRIVER=file
SESSION_LIFETIME=120
続いて,キューのジョブと失敗したジョブを管理するためのテーブルを作成するためのマイグレーションファイルを生成する.
[vagrant@localhost laravelQueue]$ php artisan queue:table ⏎ Migration created successfully! [vagrant@localhost laravelQueue]$ php artisan queue:failed-table ⏎ Migration created successfully! [vagrant@localhost laravelQueue]$
生成されたマイグレーションファイルを実行して,2つのテーブルを追加する.
[vagrant@localhost laravelQueue]$ php artisan migrate:status ⏎ +------+--------------------------------------------+-------+ | Ran? | Migration | Batch | +------+--------------------------------------------+-------+ | Yes | 2020_02_24_174420_create_comments_table | 1 | | No | 2020_02_24_212331_create_jobs_table | | | No | 2020_02_24_212400_create_failed_jobs_table | | +------+--------------------------------------------+-------+ [vagrant@localhost laravelQueue]$ php artisan migrate ⏎ Migrating: 2020_02_24_212331_create_jobs_table Migrated: 2020_02_24_212331_create_jobs_table (0.01 seconds) Migrating: 2020_02_24_212400_create_failed_jobs_table Migrated: 2020_02_24_212400_create_failed_jobs_table (0.01 seconds) [vagrant@localhost laravelQueue]$ php artisan migrate:status ⏎ +------+--------------------------------------------+-------+ | Ran? | Migration | Batch | +------+--------------------------------------------+-------+ | Yes | 2020_02_24_174420_create_comments_table | 1 | | Yes | 2020_02_24_212331_create_jobs_table | 2 | | Yes | 2020_02_24_212400_create_failed_jobs_table | 2 | +------+--------------------------------------------+-------+ [vagrant@localhost laravelQueue]$
次に,コメントを一気に登録するジョブファイルを生成する.
[vagrant@localhost laravelQueue]$ php artisan make:job StoreComments ⏎ Job created successfully. [vagrant@localhost laravelQueue]$
上のコマンドによって,次のような app/Jobs/StoreComments.php ファイルが生成された.
app/Jobs/StoreComments.php
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
class StoreComments implements ShouldQueue
{
  use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
  /**
   * Create a new job instance.
   *
   * @return void
   */
  public function __construct()
  {
    //
  }
  /**
   * Execute the job.
   *
   * @return void
   */
  public function handle()
  {
    //
  }
}
キューの準備ができたので,実際にデータベースに保存するジョブを記述していこう.
 (抜粋)
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use App\Comment;
class StoreComments implements ShouldQueue
{
  use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
  protected $n_comments;    // ここの記述を忘れないように!
  /**
   * Create a new job instance.
   *
   * @return void
   */
  public function __construct($n_comments)
  {
    $this->n_comments = (int) $n_comments;
  }
  /**
   * Execute the job.
   *
   * @return void
   */
  public function handle()
  {
    for ($i = 1; $i <= $this->n_comments; $i++) {
      usleep(1330000);  // 1.33 秒スリープする
      $comment = new Comment();
      $comment->title = $i . '件目のコメント';
      $comment->body = 'コメント' . $i . 'の本文';
      $comment->save();
    }
  }
}
ボタンが押されたときに,コントローラで登録処理するのではなく,コントローラからバックグラウンドでジョブを呼び出すように編集しよう.
app/Http/Controllers/CommentsController.php (抜粋)
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Comment;
use App\Jobs\StoreComments;
class CommentsController extends Controller
{
  public function index()
  {
    ... (中略) ...
  }
  public function create()
  {
    ... (中略) ...
  }
  public function store(Request $request)
  {
    $this->validate($request, [
      'numberOfComments' => 'required|integer|min:1|max:1000'
    ]);
    // キューに入れる
    StoreComments::dispatch($request->numberOfComments);
    return redirect('/comments');
  }
}
キューの作成ができたので,実際に動作を確認してみよう.なお,キューを利用するためには,web サーバだけでなく,キューを処理するためのワーカも実行して置かなければならない.つまり,Putty (コマンドプロンプト,ターミナル,端末など) を2つ以上起動し,それぞれの端末で web サーバとワーカを起動しておく.
[vagrant@localhost laravelQueue]$ php artisan serve --host=192.168.33.105 --port 8000 ⏎ Laravel development server started: <http://192.168.33.105:8000>
[vagrant@localhost laravelQueue]$ php artisan queue:work ⏎
Web サーバとワーカの起動ができたら,コメントを登録してみよう.例として,すでに18件の登録がある状態で,10件のコメントを追加してみる.キューを使った場合は,ボタンを押した直後に次の画面に遷移していることが確認できるはずである.また,遷移した直後は登録件数が18件のままである.何度かページの再読み込みをすると,登録件数が徐々に増え,14秒程度で28件にまで増えている.
下の図は登録件数が18件の状態で,10件の登録ボタンを押した直後の状態である..
このとき,ワーカの端末には,次のようなメッセージが表示される.
[vagrant@localhost laravelQueue]$ php artisan queue:work
[2020-02-24 21:41:26][1] Processing: App\Jobs\StoreComments
キューの処理が終了すると,ワーカの端末に次のメッセージが表示される
[vagrant@localhost laravelQueue]$ php artisan queue:work
[2020-02-24 21:41:26][1] Processing: App\Jobs\StoreComments
[2020-02-24 21:41:40][1] Processed:  App\Jobs\StoreComments
下の図はキューの処理が終了したあとにブラウザの再読み込みボタンをクリックした状態である.10件のデータが追加されていることが確認できる.
このように,若干の時間を要する処理を行うときには,キューを利用することでユーザの使い勝手を向上させることができる.
キューを利用するときの注意点の一つとして,タイムアウトが挙げられる.これまでの例のように数秒〜10秒程度で処理が終了するような場合はキューが便利である.しかしながら,キューは60秒でタイムアウトになる.
例えば,100件のコメントデータを登録しようとするとおよそ133秒必要になる.実際に100件を登録しようとしたとき,およそ1分経過後にワーカが強制終了し(プロンプトに戻り),44件目までしか登録できなかった.
[vagrant@localhost laravelQueue]$ php artisan queue:work
[2020-02-24 22:08:49][2] Processing: App\Jobs\StoreComments
[vagrant@localhost laravelQueue]$  ← ← 1分経過後に表示された
100件の登録を実行したが,44件目の登録後に強制終了した.
以上,確認したとおり,キューは数秒〜10秒程度の処理をバックグラウンドで実行するときには非常に便利な仕組みである.これ以上の時間を要する処理をバックグラウンドで行うためにはコマンド(コンソール)を使うと良い.