Laravelのデータベースキュー投入とジョブ処理で非同期処理を実現する~劇的に高速化できるユーザーレスポンス~
- 公開:
- 更新:
- カテゴリ: PHP Laravel
- タグ: Laravel,MySQL,5.5,Queues,Job,worker,5.6,5.7
Laravelでは、ジョブキューを実装すると高速なユーザ体験を提供できますが、前回の記事ではその流れをメインとしていたため、syncでのキュー投入とジョブ処理を実装しました。よって実質非同期処理は行われていませんでした。
今回は、データベースを用いてキュー投入を行い、非同期のジョブ処理を実装していきます。
開発環境
今回の開発環境は以下の通りです。
- 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
-
<!DOCTYPE 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
コンソールが待機状態になれば、ワーカーが起動したことになります。
動作確認
キューワーカーを起動させたら、ブラウザからアクセスして動作確認を行いましょう。
- 通常処理(同期処理) → http://YOURDOMAIN/sample/queues/none
- 非同期処理 → http://YOURDOMAIN/sample/queues/database
として、それぞれブラウザからアクセスしてみます。
- 通常処理(同期処理)
-
- 非同期処理
-
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
実行結果が出力されています。
まとめ
以上でデータベースを利用したキュー&ジョブ処理の作業は完了です。
処理をキューへ投下する事で処理が分離され、非常に高速にレスポンスを返せるようになります。
また、テーブルに挿入されたキュー情報は処理されたら削除されるので、データベースも圧迫されません。
そのほかにも、処理の優先順位付けや、処理遅延時間の設定など、色々行えるので、是非試してみてください。