1. Home
  2. PHP
  3. Laravel
  4. Laravelのデータベースキュー投入とジョブ処理で非同期処理を実現する~劇的に高速化できるユーザーレスポンス~

Laravelのデータベースキュー投入とジョブ処理で非同期処理を実現する~劇的に高速化できるユーザーレスポンス~

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

Laravel では、ジョブキューを実装すると高速なユーザ体験を提供できますが、前回の記事 ではその流れをメインとしていたため、sync でのキュー投入とジョブ処理を実装しました。よって実質非同期処理は行われていませんでした。

今回は、データベースを用いてキュー投入を行い、非同期のジョブ処理を実装していきます。

Contents

  1. 開発環境
  2. キュー用のデータベースを作成
  3. キューサービスをデータベースへ切り替える
  4. ファイル操作クラスの作成
  5. ジョブクラスの作成と実装
  6. コントローラ・ビュー・ルーティング作成
    1. コントローラ
    2. ビュー
  7. 動作確認

開発環境

今回の開発環境は以下の通りです。

  • 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

コンソールが待機状態になれば、ワーカーが起動したことになります。

動作確認

キューワーカーを起動させたら、ブラウザからアクセスして動作確認を行いましょう。

  • 通常処理(同期処理) → 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

実行結果が出力されています。

まとめ

以上でデータベースを利用したキュー&ジョブ処理の作業は完了です。

処理をキューへ投下する事で処理が分離され、非常に高速にレスポンスを返せるようになります。

また、テーブルに挿入されたキュー情報は処理されたら削除されるので、データベースも圧迫されません。

そのほかにも、処理の優先順位付けや、処理遅延時間の設定など、色々行えるので、是非試してみてください。

Author

rito

  • Backend Engineer
  • Tokyo, Japan
  • PHP 5 技術者認定上級試験 認定者
  • 統計検定 3 級