LaravelではJob(ジョブ)もQueue(キュー)も使用しなくてもアプリケーションの構築を行うことができるので、設定したこともなければ存在さえしらない人が多いかもしれません。本文書では入門者でもわかるようにJobの作成方法からQueueの使い方まで簡単なコードを使って説明を行っていきます。これまで使ったことがない人もぜひこの機会にQueueにチェレンジしてください。

用語の確認

Queue(キュー)、Job(ジョブ)と Worker(ワーカー) とは

キューは処理が実行されるのを待っているジョブのリストです。これだけでは難しいと思うので、コンビニを例にして説明します。

コンビニでは昼時になるとレジの前にたくさんの人がレジ待ちしていると思います。この列がキューを意味し、レジで行うお客様の会計処理1つ1つがジョブになります。キューとジョブは身近なところに存在するのでイメージすることは簡単です。

後ほどWorker(ワーカー)という単語がでてきますが、会計のジョブを処理する店員がワーカーに対応します。

同期と非同期

キューを利用するためには、同期処理と非同期処理という言葉を理解しておく必要があります。最近頻繁に街中で見かけるUberEatsを例に考えてみましょう。

お客様がスマホを通して注文を行います。注文を受け取った店は料理の調理を開始します。それと同時にUberEatsからお店の近くの配達員に連絡が入り、配達を行いたい配達員が決定され、お店に料理を取りに行きます。その後配達員がお店で料理を受け取ってお客様の元に料理を運びます。

これが同期処理で行われていると店が注文を受け取ってもすぐにUberEatsの配達員には連絡は行われません。配達員への連絡はお店の料理ができあがるまで待つ必要があります。料理ができあがったらUberEatsから配達員に連絡が入り、配達員が決定した後配達員はお店に料理を取りに向かいします。お店は配達員が料理をお客様の元に届けるまで次の注文を受け取らず待ちます。配達員がお客様に届けた連絡を受けて次の注文を受けます。

通常は非同期に処理が行われているので、お店に注文が入ると料理が出来上がるのを待たず、配達員に連絡が入り、調理中に配達員がお店に向かいます。お店は料理ができたら、次の注文の料理を開始します。

同期と非同期はお客様、お店にとってどちらがいいでしょうか?非同期だと思います。このように非同期処理をうまく活用することで効率よく処理を行うことができます。

この例でのキューは店にとっては注文です。一人しか料理人がいない場合は順番にキューに入った処理を行なっていかなければなりません。ジョブは注文を受けたメニューになります。UberEatsの仕組みは細かくはわかりませんが、配達員の場合は、1件配送依頼の処理が完了したら、また次の1件の配送依頼を受けるのであればキューはなく同期処理を行なっていることになります。

環境構築

ジョブとキューの動作確認を行うためには、Laravel環境の構築を行う必要があります。環境構築については簡単に手順を説明していきます。

Laravelのインストールと設定

composerコマンドでLaravelのインストールを行います。


$ composer create-project --prefer-dist laravel/laravel job_queue   

データベースにはsqliteを利用します。touchコマンドでデータベースファイルを作成します。コマンドはLaravelのインストールディレクトリjob_queueの下で実行します。


$ cd job_queue
$ touch database/database.sqlite

.envファイルでデータベースの設定をmysqlからsqliteに変更します。

MySQLよりもSQLiteのほうが設定が簡単なのでSQLiteを使用しています。MySQLでも問題ありません。

DB_CONNECTION=sqlite
.envファイルのDB_CONNECTION以外のデータベースに関わる設定は削除します。

ユーザ登録のための認証機能を使用するため、認証機能の追加を行います。


$ php artisan make:auth
Authentication scaffolding generated successfully.

php artisan migrateコマンドでテーブルの作成を行います。users, password_resetsテーブルが作成されます。


$ php artisan migrate
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (0.01 seconds)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table (0 seconds)

メール送信のSMTP設定

ジョブとキューの動作確認を行う際にメール送信を行うので、メール設定を行います。

ここではダミーのSMTPサーバのmailtrapを利用します。.envファイルを開いてmailTrapの接続情報を設定します。

MAIL_DRIVER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=XXXXXXXXXXXXX //各環境による異なる
MAIL_PASSWORD= XXXXXXXXXXXXX // 各環境による異なる
MAIL_FROM_ADDRESS=from@example.com
MAIL_FROM_NAME=Example
SMTPサーバが準備できない場合はMAIL_DRIVERにlogを設定してください。送信したメールの内容がログファイルに書き込まれます。ログファイルはstorage/logsの下に作成されます。

php artisan serveで開発用WEBサーバを起動し、ユーザ登録画面が表示されるか確認を行なってください。今後はこの画面からユーザ登録を行い、キューを使用する場合としない場合の比較を行なっていきます。

ユーザ登録画面
ユーザ登録画面

ここまでの作業でジョブ、キューの動作確認を行うためのLaravelの環境設定は完了です。

キューを使用しない場合

キューを使用せずユーザ登録後にWelocomeメールを送信するコードを記述します。

ユーザ登録の処理はApp¥Http¥Controllers¥Auth¥RegisterController.phpファイル内のcreateメソッドで行われているため、createメソッドの中に登録後の処理を記述します。


protected function create(array $data)
{
    $user = User::create([
        'name' => $data['name'],
        'email' => $data['email'],
        'password' => Hash::make($data['password']),
    ]);

 // 上記まででユーザの登録が完了するのでメール送信処理をここに記述

    return $user;
}
デフォルトでは$user変数は存在しませんが、処理を追加するために$user変数を追加し、処理後にreturn $userでユーザ情報を戻しています。

送信するメールの設定

送信するメールの内容を記述するためにMailableクラスのWelcomeMailを作成します。


$ php artisan make:mail WelcomeMail
Mail created successfully.

実行後、app¥Mailディレクトリの下にWelcomeMail.phpが作成されます。

WelcomeMail.phpのbuildメソッドを下記のように書き換えます。viewメソッドで指定したemails.welcomeファイルにはメールの内容を記述します。subjectメソッドはメールのタイトルを入力します。


public function build()
{
    return $this->view('emails.welcome')
                ->subject('This is welcome mail');
}

resource¥viewsディレクトリの下にemails¥welcome.blade.phpファイルを作成します。

welcome.blade.phpファイルには以下を書き込みます。


ユーザ登録ありがとうございます。

メール送信の処理を追加

App¥Http¥Controllers¥Auth¥RegisterController.phpファイル内のcreateメソッドの中にメール送信のコードを追加します。

登録したユーザのメールアドレス宛にWelcomeメールを送信する処理です。


// 中略 
use Mail;
use App\Mail\WelcomeMail;
// 中略 

protected function create(array $data)
{
    $user = User::create([
        'name' => $data['name'],
        'email' => $data['email'],
        'password' => Hash::make($data['password']),
    ]);

    Mail::to($user->email)->send(new WelcomeMail());
}

待ちの長さは環境にも依存しますがユーザがユーザ登録画面ですべての情報を入力してRegisterボタンを押してから次のページに行くまでに3-4秒間ブラウザが応答していない状態になることを必ず確認してください。この待ちがキューと関連しています。

ユーザ登録画面
ユーザ登録画面

ユーザの登録画面が完了するとメールが送信されます。mailtrapでメールを受信します。

MailTrapメール受信
MailTrapメール受信

キューを利用した場合

キューの設定ファイル

キューの設定は、.envファイルからconfig¥queue.phpファイルの中で行います。

キューの設定について

キューを利用していない場合、ユーザ登録のRegisterボタンを押してから登録後の次の画面に行くまでに数秒かかることを確認しました。これはメールを送信する処理が終わるまで次の処理が行えず待ちが生じているためです。

デフォルトではすべての処理が同期処理で行われるため、.envファイルのキューの設定を見ると同期のsynchronizeという意味を持つsyncが設定されています。


QUEUE_CONNECTION=sync

キューの設定ファイルであるconfig¥queue.phpファイルのconnectionsを見るとsync以外に”sync”, “database”, “beanstalkd”, “sqs”, “redis”などのキューが準備されています。

最初にもっとも設定が簡単なdatabaseをキューに利用します。そのためQUEUE_CONNECTIONをdatabaseに変更してください。

後ほど別のキューであるredisを使った場合の設定手順も確認します。

キューにデータベースを利用

.envファイルのQUEUE_CONNECTIONをsyncからdatabaseに変更します。キューはジョブをリスト化して保存しておく必要があるため保存場所が必要となります。設定値をdatabaseにするとキューの保存場所がデータベース内のテーブルになります。

そのためデータベースにキューを保存するテーブルを作成する必要がありますがテーブル作成についての処理はLaravel側で事前準備されているので、php artisan queue:tableコマンドを実行します。


$ php artisan queue:table
Migration created successfully!

app¥database¥migrationsディレクトリにjobsテーブルのマイグレーションファイルが作成されます。php artisan migrateコマンドを実行するとjobsテーブルが作成されます。キューを使用するとこのjobsテーブルの中に未実行のジョブが保存されていきます。


 $ php artisan migrate
Migrating: 2019_08_22_134429_create_jobs_table
Migrated:  2019_08_22_134429_create_jobs_table (0 seconds)

これでdatabaseキューを使う準備は完了です。

ジョブの作成

ジョブ単位でキューを利用するため先ほど作成したメール送信のコードをジョブとして登録します。ジョブの作成はphp artisan make:jobコマンドを使用します。


$ php artisan make:job SendWelcomeMail
Job created successfully.

ジョブはapp¥Jobsディレクトリの下に作成されます。作成されたSendWelcomeMail.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 SendWelcomeMail implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function __construct()
    {
        //
    }

    public function handle()
    {
        //
    }
}

メール送信のコードを追加します。


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 Mail;
use App\Mail\WelcomeMail;
use App\User;

class SendWelcomeMail implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public $user;

    public function __construct(User $user)
    {
        $this->user = $user;
    }

    public function handle()
    {
        Mail::to($this->user->email)->send(new WelcomeMail());
    }
}

ジョブの作成は完了です。

キューを使用

ジョブの作成が完了したので、キューを使用します。ジョブをキューを使って実行したい場合はdispatchメソッドを使用します。

dispatchメソッドを実行することでジョブはキューに送られます。

RegisterController.phpの処理にジョブSendWelcomeMailを追加します。


use App\Jobs\SendWelcomeMail;

// 中略

protected function create(array $data)
{
    $user = User::create([
        'name' => $data['name'],
        'email' => $data['email'],
        'password' => Hash::make($data['password']),
    ]);

    SendWelcomeMail::dispatch($user);

    return $user;
}

ユーザ登録を登録画面から実行するとキューを使用する前は次の画面に進むまでに数秒待つ必要がありましたがキューを使用するとすぐに次の画面に移動します。今の設定のままでは何分経過してもジョブの中で実行されているWelcomeメールが送信されず、MaiTrapのinBoxにもメールが届きません。

キューを保存しているjobsテーブルを確認してみましょう。

設定変更後もメールが送信されている場合はphp artisan serveコマンドで開発サーバを再起動してください。.envファイルの設定変更が反映されていない可能性があります。

jobsテーブルの確認

GUIツールを(TablePlus)使ってjobsテーブルの中身を確認するとSendWelcomeMailのジョブが登録されていることがわかります。

jobsテーブルに行が追加
jobsテーブルに行が追加

jobsテーブルにはキューに残っている未実行のジョブが保存されるのでSendWelcomeMailジョブが未実行のため、メールが送信されていないことがわかります。

ではジョブはどのタイミングで実行されるのでしょう?

キューにあるジョブの実行

キューの中に入っているジョブは自動で実行されるわけではなくジョブを実行する別のプロセスであるWorker(ワーカー)が必要となります。キューの中にあるジョブを実行するためには、ワーカーを起動する必要があります。ワーカーを起動するためにphp artisan queue:workコマンドを実行します。


 $ php artisan queue:work
[2019-08-25 15:34:09][3] Processing: App\Jobs\SendWelcomeMail
[2019-08-25 15:34:12][3] Processed:  App\Jobs\SendWelcomeMail

php artisan queue:work実行後、起動したワーカーによりキューに残っていたSendWelcomeMailの実行が開始(Processing)され、しばらくして処理が完了(Processed)します。完了するとMailtrapのinBoxにWelcomeメールが受信されていることを確認することができます。

メール送信にlogを設定している場合はログファイルにメールに関する情報が書き込まれます。

php artisan queue:workコマンドを実行し、プロセスを終了しなければワーカーは起動したままの状態となります。再度別のユーザを追加してみましょう。

今度はワーカーが起動している状態なので、ユーザを登録するとすぐにWelcomeメールが送信されます。ワーカーが処理を行なったことも確認することができます。


$ php artisan queue:work
[2019-08-25 15:34:09][3] Processing: App\Jobs\SendWelcomeMail
[2019-08-25 15:34:12][3] Processed:  App\Jobs\SendWelcomeMail
[2019-08-25 15:39:25][4] Processing: App\Jobs\SendWelcomeMail
[2019-08-25 15:39:28][4] Processed:  App\Jobs\SendWelcomeMail
ユーザ登録にキューを使用しているのでRegisrerボタンを押すとメール送信を待たず次の画面が表示されます。ワーカーがキューにジョブが入ったことを検知してジョブの処理を行います。

ワーカープロセスの状態はsupervisorで監視を行うことができます。Ubutun環境でのsupervisorの設定手順については下記の文書にまとめています。

キューにredisを利用した場合

キューにredisを利用する場合は、.envファイルを更新する必要があります。

redisのインストールに関しては下記を参照してください。

QUEUE_CONNECTIONをredisに変更します。REDIS_HOSTなどredisに関する設定がありますが、デフォルトのまま設定の変更を行っていなければそのままの値で変更する必要はありません。


QUEUE_CONNECTION=redis
 ・
 ・
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

Predisパッケージのインストール

LaravelのQueueとしてredisを使用するためには、Predisパッケージをインストールする必要があります。composerコマンドを使用してインストールを行います。


$ composer require predis/predis
Using version ^1.1 for predis/predis
predisパッケージがインストールされていない場合は、Class ‘Predis\Client’ not found エラーが発生するのでPredisパッケージを忘れずにインストールしてください。

処理の実行

ジョブなどの設定は何も設定変更を行わず、ユーザの登録を行います。redisに変更されたことを確認するためにワーカーを起動している場合は一度停止してください。

ユーザ登録後にGUIツール(TablePlus)を使ってredisに接続し、ジョブの情報が登録されていないか確認を行います。

redis上のdb0にLaravelのジョブ情報が登録されていることがわかります。

Laravelのジョブの確認
Laravelのジョブの確認

キュー(redis上)に登録されたジョブを実行するためにワーカーを起動します。起動と同時にジョブが実行されるのがわかります。


$ php artisan queue:work
[2019-08-27 02:09:05][mnXLozJ5R3hEeV4FZrTIhhDEwQmCl45i] Processing: App\Jobs\SendWelcomeMail
[2019-08-27 02:09:08][mnXLozJ5R3hEeV4FZrTIhhDEwQmCl45i] Processed:  App\Jobs\SendWelcomeMail

Processedでジョブの処理が完了していることを確認後、Mailtrapを見るとWelcomeメールが届いていることを確認することができます。また、ジョブの処理完了後redisに登録されたジョブもなくなります。

まとめ

このようにキューとジョブを利用するとことでユーザ登録時のユーザの待ちのストレスを解消することができました。キューとジョブをうまく活用することでアプリケーション中での処理待ちを減らすことができます。

本文書を理解できたら下記の文書を読み進めることをおすすめしています。