Laravelのデータベースキュー投入とジョブ処理で非同期処理を実現する~劇的に高速化できるユーザーレスポンス~
- 公開日
- 更新日
- カテゴリ:Laravel
- タグ:Laravel,MySQL,Queues,Job,worker

Laravel では、ジョブキューを実装すると高速なユーザ体験を提供できますが、前回の記事 ではその流れをメインとしていたため、sync でのキュー投入とジョブ処理を実装しました。よって実質非同期処理は行われていませんでした。
今回は、データベースを用いてキュー投入を行い、非同期のジョブ処理を実装していきます。
Contents
開発環境
今回の開発環境は以下の通りです。
- Linux CentOS 7
- Apache 2.4
- MySQL 8.0/5.7
- PHP 7.2/7.1
Laravel のバージョンについては、5.7/5.6/5.5 にて動作確認済みです。
上記環境でなくても Artisan コマンドが叩ける事、そして MySQL を使えれば OK です。それと、Laravel での DB 接続は終えておいてください。
Laravel のルートディレクトリを「laravel/」とします。
尚、キューとジョブの基本的な使い方については、以下を参考にしてください。
Laravel のキュー投入によるジョブ処理を導入する
キュー用のデータベースを作成
まずは、キューを投入するテーブルを作成します。
Laravel ルートディレクトリに移動し、以下の Artisan コマンドを叩いてマイグレーションファイルを作成します。
# laravel ルートディレクトリへ移動
cd /path/to/laravel
# artisan コマンドでマイグレーションファイルを作成する
php artisan queue:table
# 実行結果
[demo@localhost laravel]$ php artisan queue:table
Migration created successfully!
laravel/database/migrations に、マイグレーションファイル「xxxx_create_jobs_table.php 」が生成されます。
laravel
├─ database
│ ├─ migrations
│ │ └─ xxxx_create_jobs_table.php
生成されたマイグレーションファイルを見てみます。
- laravel/database/migrations/xxxx_create_jobs_table.php
-
<?php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateJobsTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('jobs', function (Blueprint $table) { $table->bigIncrements('id'); $table->string('queue')->index(); $table->longText('payload'); $table->unsignedTinyInteger('attempts'); $table->unsignedInteger('reserved_at')->nullable(); $table->unsignedInteger('available_at'); $table->unsignedInteger('created_at'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('jobs'); } }
カラム構成[id][queue][payload][attempts][reserved_at][available_at][created_at]にて、jobs テーブルが作成される。という事のようです。 それではマイグレートして jobs テーブルを作成します。以下の Artisan コマンドを叩きます。
# laravel ルートディレクトリへ移動
cd /path/to/laravel
# artisan コマンドでマイグレートを行い、job テーブルを作成する
php artisan migrate
# 実行結果
[demo@localhost laravel]$ php artisan migrate
Migrating: xxxx_create_jobs_table
Migrated: xxxx_create_jobs_table
MySQL にログインして、テーブルが作成されているか確認します。
mysql> show tables;
+----------------------+
| Tables_in_laravel_db |
+----------------------+
| jobs |
| migrations |
| password_resets |
| users |
+----------------------+
mysql> show columns from jobs;
+--------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+---------------------+------+-----+---------+----------------+
| id | bigint(20) unsigned | NO | PRI | NULL | auto_increment |
| queue | varchar(255) | NO | MUL | NULL | |
| payload | longtext | NO | | NULL | |
| attempts | tinyint(3) unsigned | NO | | NULL | |
| reserved_at | int(10) unsigned | YES | | NULL | |
| available_at | int(10) unsigned | NO | | NULL | |
| created_at | int(10) unsigned | NO | | NULL | |
+--------------+---------------------+------+-----+---------+----------------+
jobs テーブルの作成を確認できました。
キューサービスをデータベースへ切り替える
デフォルトでは sync になっていますので、laravel/.env を開いて、キューサービスを database へ切り替えます。
# Laravel 5.7 〜
QUEUE_CONNECTION=database
# Laravel 〜 5.6
QUEUE_DRIVER=database
バージョンによって.env の設定名が違う事に注意してください。切り替えはこれで完了です。
ファイル操作クラスの作成
今回は比較用に同期処理と非同期処理の2つを実装するので、共通処理をまとめておきます。(ここはジョブとかキューとかは関係ありません)
- laravel/app/Http/Components/FileOperation.php
-
<?php namespace App\Http\Components; class FileOperation { /** * テキストファイルを作成する * @return string */ public function makeTextFile() { $file = sprintf('%s/test.txt', storage_path('texts')); if(file_exists($file)) unlink($file); touch($file); return $file; } /** * ファイルに指定回数分の追加書き込みを行う * @param string $file * @param int $max */ public function write(string $file, int $max) { for($i=0; $i< $max; $i++ ) { $current = file_get_contents($file); $current .= $i; file_put_contents($file, $current); } } }
テキストファイルを作成する makeTextFile() メソッドと、テキストファイルに指定回数分の追加書き込みを行う write() メソッドを定義しています。
ジョブクラスの作成と実装
次に、ジョブクラスを作成し、処理を実装していきます。 artisan コマンドを叩いて、ジョブクラスを生成します。
# laravel ルートディレクトリへ移動
cd /path/to/laravel
# artisan コマンドでジョブクラス生成する
php artisan make:job GenerateTextFile
# 実行結果
[demo@localhost laravel]$ php artisan make:job GenerateTextFile
Job created successfully.
laravel/app/Jobs 配下に GenerateTextFile.php が生成されます。
laravel
├─ app
│ ├─ Jobs
│ │ ├─ GenerateTextFile.php
ファイルを開いて処理を実装します。
- laravel/app/Jobs/GenerateTextFile.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; use App\Http\Components\FileOperation; class GenerateTextFile implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; private $file; private $max; private $fp; public function __construct($file, $max) { $this->file = $file; $this->max = $max; $this->fp = new FileOperation(); } public function handle() { // 書き込み $this->fp->write($this->file, $this->max); } }
コントローラ・ビュー・ルーティング作成
今回の検証用のコントローラとビューを作成して、ルーティングを行います。検証用という事で、キュー投入処理版と通常処理版の2つを書いていきます。
- ルーティング laravel/routes/web.php
-
Route::get('sample/queues/none', 'SampleController@queuesNone'); Route::get('sample/queues/database', 'SampleController@queuesDatabase');
- http://YOURDOMAIN/sample/queues/none → 通常の同期処理を行う
- http://YOURDOMAIN/sample/queues/database → 非同期処理を行う
コントローラ
- laravel/app/Http/Controllers/SampleController.php
-
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Jobs\GenerateTextFile; use App\Http\Components\FileOperation; class SampleController extends Controller { const MAX = 3000; // ループ回数 private $fp; public function __construct() { $this->fp = new FileOperation(); } public function queuesNone() { $start = time(); $file = $this->fp->makeTextFile(); $this->fp->write($file, self::MAX); return view('sample_queues', ['start' => $start]); } public function queuesDatabase() { $start = time(); $file = $this->fp->makeTextFile(); GenerateTextFile::dispatch($file, self::MAX); return view('sample_queues', ['start' => $start]); } }
queuesNone() メソッドは通常処理(同期処理)用、queuesDatabase() メソッドは非同期処理用になります。 時間計測のために処理開始時の Unix タイムスタンプをビューへ渡しています。 そして、書き込み回数を定数で 3000 回に指定しています。
ビュー
- laravel/resources/views/sample_queues.blade.php
-
html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>Sample Queues</title> </head> <body> <p> Queues test!! </p> <?php if(!empty($start)) { echo time() - $start; } ?> </body> </html>
現在のタイムスタンプから、コントローラから受け取ったタイムスタンプの差分を出す事で、処理にかかった時間(秒数)を出力しています。 ## キューワーカを起動する
動作確認を行う前に、キューワーカーを起動します。これを行わないと、データベースキューに投入されたジョブが処理されません。
# laravel ルートディレクトリへ移動
cd /path/to/laravel
# artisan コマンドでキューワーカーを起動する
php artisan queue:work
コンソールが待機状態になれば、ワーカーが起動したことになります。
動作確認
キューワーカーを起動させたら、ブラウザからアクセスして動作確認を行いましょう。
として、それぞれブラウザからアクセスしてみます。
通常処理(同期処理)
非同期処理
3000 回のループ処理ですが、通常処理の場合は表示までに 11 秒かかっているのに対し、非同期処理の場合は処理自体をキューに投げて別処理になっているので、表示まではわずか 1 秒未満と非常に高速です。
キューワーカーを起動したコンソール画面を確認してみましょう。
[demo@localhost laravel]$ php artisan queue:work
[2019-02-11 18:56:19][1] Processing: App\Jobs\GenerateTextFile
[2019-02-11 18:56:31][1] Processed: App\Jobs\GenerateTextFile
実行結果が出力されています。
まとめ
以上でデータベースを利用したキュー&ジョブ処理の作業は完了です。
処理をキューへ投下する事で処理が分離され、非常に高速にレスポンスを返せるようになります。
また、テーブルに挿入されたキュー情報は処理されたら削除されるので、データベースも圧迫されません。
そのほかにも、処理の優先順位付けや、処理遅延時間の設定など、色々行えるので、是非試してみてください。