2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【CentOS 8/Apache 2.4】Laravel 5.5でメール送信をキューイングを用いて速くする【非同期化】

Last updated at Posted at 2021-01-10

1. 概要

本記事ではCentOSApacheLaravel環境下でのMailファサードによるメール送信において、キューイングを用いることで非同期化させ、処理速度を向上させる方法について紹介します。

まず、キューとは「先入れ先出しの規則でデータを取り扱うデータ構造」を指します。

キューとは、最も基本的なデータ構造の一つで、要素を入ってきた順に一列に並べ、先に入れた要素から順に取り出すという規則で出し入れを行うもの。順番を待つ人の行列と同じ仕組みであるため「待ち行列」とも訳される。

引用元:IT用語辞典 e-Words「キュー(待ち行列)とは

このキューを用いてデータの管理を行うことをキューイングといい、これにより処理を非同期化させることができ、同期的に処理する場合に比べて処理速度が向上します。
キューイングのイメージ.png
上図の左側のように、同期処理では一つの処理が完了するまで次の処理は実行されません。この為、メール送信ような重い処理を挟んだ場合にリソースのムダ及び不要な待ち時間が発生してしまいます。

そこで、上図の右側のように、重い処理を一旦キューに送って後続処理を優先させ、バックグラウンドでキューの処理を行うことで上記の問題を解決できます。

Laravelプロジェクトでのメール送信はMailファサードを用いるのが一般的ですが、Mailファサードには同期的にメール送信するsendメソッドの他に、キューイングするqueueメソッドがデフォルトで用意されています。

しかし、こちらのメソッドはメール送信ジョブをエンキューするのみで、ジョブをバックグラウンドで永続的に処理させるにはプロセスモニタをOSに別途インストールする必要があります。

公式マニュアル(Laravel 5.5)では、プロセスモニタとしてSupervisorが紹介されていますが、載っているコマンドがUbuntuへのインストールの場合のものであるため、本記事では**CentOSへのインストール方法**を紹介します。

2. メール送信を非同期化させる手順

2-1. 前提条件

  • CentOS 8
  • Apache 2.4
  • PHP 7.4
  • Laravel 5.5
  • プロジェクトのルートディレクトリ名はlaravelとします
  • プロジェクトのルートディレクトリの絶対パスは/var/www/html/laravelとします

本記事では上記環境であるとの前提で解説します。

参考にされる場合は適宜読み替えて下さい🙇‍♂️

2-2. キュードライバーの設定

まず、キューイングを行う際のドライバーを指定します。Laravel指定できるキュードライバーには次の6種類があります。

  • sync
  • 同期処理、ローカル用途、デフォルトではこれが指定されている
  • database
  • データベースにキュー用のテーブルを作成してそれを利用する
  • redis
  • Redisを利用する
  • sqs
  • Amazon SQSを利用する
  • beanstalkd
  • Beanstalkdを利用する
  • null
  • キューされたジョブを破棄する

本記事ではライブラリを別途インストールする必要がなく、既存のDBにテーブルを追加するだけで利用できるdatabaseを用います。

.envファイルにてキュードライバーを指定します。

.env
QUEUE_DRIVER=database

念のため1config/queue.phpでも、キュードライバーの指定を変更します。

config/queue.php
    /*
    |--------------------------------------------------------------------------
    | Default Queue Driver
    |--------------------------------------------------------------------------
    |
    | Laravel's queue API supports an assortment of back-ends via a single
    | API, giving you convenient access to each back-end using the same
    | syntax for each one. Here you may set the default queue driver.
    |
    | Supported: "sync", "database", "beanstalkd", "sqs", "redis", "null"
    |
    */

    // envメソッドの第二引数をデフォルトの`sync`から指定したいドライバーに変更
    'default' => env('QUEUE_DRIVER', 'database'),

2-3. キューとなるテーブルの作成

次に、キューとなるテーブルを生成するマイグレーションファイルを作成します。

ターミナル
php artisan queue:table

また、失敗した処理を保存するテーブルを作成するマイグレーションファイルを同時に作成しても良いです。こちらは必須ではありません。

ターミナル
php artisan queue:failed-table

マイグレーションを実行しテーブルを作成します。

ターミナル
php artisan migrate

2-4. メール送信でキューイングを使用する

Mailファサードqueueメソッドを用いるだけです。これだけでメール送信のジョブが上述のマイグレーションで作成されたjobsテーブルに投入されます。

mailableクラス
Mail::to($request->user())
    ->queue(new OrderShipped($order));

❗️queueメソッドの注意点❗️

queueメソッドを用いる場合、Mailableインスタンス(ここではOrderShippedクラスのインスタンス)の引数としてPHPで定義済みのクラスを含むオブジェクト型の多くは渡すことができない2点に注意が必要です。

例えば、ユーザーがフォームに入力した情報をメール内で使用するために、Requestクラスのインスタンスをオブジェクト型として格納した$requestを引数に渡す場合を考えます。

この場合、sendメソッドでは問題なく送信できますが、queueメソッドでは例外Serialization of 'Closure' is not allowedが発生します。

mailableクラス
// 問題なし
Mail::to($request->user())
    ->send(new OrderShipped($request, $order));

// エラー発生
Mail::to($request->user())
    ->queue(new OrderShipped($request, $order));

この問題は、引数をオブジェクト型以外の型として渡すことで回避できます。
メールテンプレートでどの様にユーザーのリクエストボディにアクセスするかによりますが、具体的には次の様な方策が考えられます。

mailableクラス
// 数値型や文字型のプロパティとして渡す
Mail::to($request->user())
    ->queue(new OrderShipped($request->name, $order));

// 配列として渡す
Mail::to($request->user())
    ->queue(new OrderShipped($request->input(), $order));

配列として渡す場合、inputメソッドの他

  • allメソッド
  • exceptメソッド
  • onlyメソッド

も使用することができます。詳しくは下記記事が参考になります。
Qiita「Requestの各メソッド(query(), get(), all()...)の使い分け」 by @piotzkhider さん

また、メールテンプレートで$request->nameのようにアロー演算子を用いてプロパティにアクセスしている場合、こちらも$request['name']のように配列の値を参照するように変更しましょう。

❓オブジェクト型が渡せない理由❓

キューイングの際のシリアル化ができない場合があるためです。

先述したように、Requestインスタンスを渡してqueueメソッドを使用すると、例外Serialization of 'Closure' is not allowedが発生します。直訳すると「『クロージャ』のシリアル化は許可されていません」となります。

シリアル化とは「値の型や構造を保った状態でデータの受け渡しや保存ができる状態にすること」を意味し、今回の場合はjobsテーブルに保存する際に、型や構造の情報が失われないように値のシリアル化が行われているはずです。

参考:侍エンジニア塾ブログ「PHPのシリアライズを知ろう!便利な使い方徹底解説

シリアル化にはPHPの変数操作関数であるserializeが用いられていると推測され、マニュアルには次の様に記載されています。

serialize() は、resource および一部の object 以外のすべての型を処理します。
(中略)
注意:
PHPの組み込みオブジェクトの多くはシリアル化できないことに注意しましょう。

引用元:PHPマニュアル「PHP: serialize - Manual

組み込みオブジェクトとは、PHPに標準で組み込まれているオブジェクトであり、その中にClosureクラスがあります。

参考:PHPマニュアル「PHP: 定義済のクラス - Manual

つまり、エラーメッセージ「Serialization of 'Closure' is not allowed」が示している通り、定義済みのクラスであるClosureRequestオブジェクトに含まれているためにシリアル化が出来ずに例外が発生したと考えられます。

2-5. Supervisorのインストール

CentOS 8へのインストール方法としては次のWebページが参考になります。

参考:CloudWater「Installing Supervisor on CentOS 8

インストールする前にシステムパッケージを最新の状態にアップデート。

ターミナル
sudo dnf update -y

次いで、CentOS標準のリポジトリでは提供されていないパッケージを、yumコマンドでインストール可能にするEPELリポジトリをインストールし、アップデート。

参考:Webセキュリティの小部屋「CentOS に EPEL リポジトリを追加する

ターミナル
sudo dnf install epel-release
sudo yum update

Supervisorをインストールする。

ターミナル
sudo yum -y install supervisor

2-6. ログファイルの作成

キューワーカーをバッググラウンドで実行させた場合のログを記録するファイルを追加します。

ログファイルの配置場所は自由に決められますが、今回はプロジェクトのルートディレクトリ内のlaravel/storage/logs内に配置し、ファイル名はsupervisor.logとします。

下記コマンド実行後に:wqで空ファイルとして保存します。

ターミナル
vi /var/www/html/laravel/storage/logs/supervisor.log

本記事では、バックグラウンドプログラムはapacheが実行するので、ファイルの所有者とパーミッションを変更します。パーミッションは所有者以外が書き込みできないように744とします。

ターミナル
sudo chown apache:apache /var/www/html/laravel/storage/logs/supervisor.log
sudo chmod 744 /var/www/html/laravel/storage/logs/supervisor.log

参考:「パーミッション早見表

2-7. 設定ファイルの編集

Supervisorのインストールにより、設定ファイル/etc/supervisord.confが追加されているはずです。このファイルで追加の設定ファイルを読み込む記述を追記します。

ターミナル
sudo vi /etc/supervisord.conf
/etc/supervisord.conf

# (中略)

[include]
files = supervisord.d/*.ini
# 下記行を追加 
files = supervisord.d/*.conf

さらに、/etc/supervisord.confと同時に/etc/supervisord.dディレクトリも追加されているはずなので、そのディレクトリ内に読み込ませる設定ファイル/etc/supervisord.d/laravel-worker.confを作成します。

ターミナル
sudo vi /etc/supervisord.d/laravel-worker.conf
/etc/supervisord.d/laravel-worker.conf
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/html/laravel/artisan queue:work --sleep=3 --tries=10
autostart=true
autorestart=true
redirect_stderr=true
numprocs=8
user=apache
stdout_logfile=/var/www/html/laravel/storage/logs/supervisor.log

キューワーカーの実行コマンドはphp artisan queue:workですが、apacheが実行できるように絶対パスで記述します。

今回はオプションとして最大試行回数とスリープ時間を--sleep=3--tries=10として指定しましたが、他にも様々なオプションがあります。

参考:ReadDouble「Laravel 5.5 キュー

また、stdout_logfileには先程作成したログファイルの絶対パスを指定します。

コピペする際には、commandstdout_logfileのパスをそれぞれご自身のプロジェクトでのパスに置き換えて下さい。

他の項目についての詳細については下記をご参照下さい。

参考:Supervisor 4.2.1 documentation「Configuration File

2-8. デーモン化と起動

設定が完了したので、アプリ起動時にプロセスが自動実行されるようにします。

ターミナル
sudo systemctl start supervisord
sudo systemctl enable supervisord

動作確認などで既にスタートしていた場合や、上記のconfファイルを編集した場合は再起動させましょう。

ターミナル
sudo systemctl restart supervisord

プロセスがちゃんと動いているかどうか確認したい場合は以下のコマンドを実行します。

ターミナル
sudo systemctl status supervisord

下記のようにActive: active (running)…と表示され、/etc/supervisord.d/laravel-worker.confcommand項目で設定したコマンドがnumprocs項目で設定した回数分表示されていれば問題ありません(下記では301829301836のプロセス)。

ターミナル
● supervisord.service - Process Monitoring and Control Daemon
   Loaded: loaded (/usr/lib/systemd/system/supervisord.service; enabled; vendor preset: disabled)
   Active: active (running) since Sun 2021-01-10 xx:xx:xx JST; xs ago
  Process: 301824 ExecStart=/usr/bin/supervisord -c /etc/supervisord.conf (code=exited, status=0/SUCCESS)
 Main PID: 301828 (supervisord)
    Tasks: 9 (limit: 5049)
   Memory: 144.6M
   CGroup: /system.slice/supervisord.service
           ├─301828 /usr/bin/python3.6 /usr/bin/supervisord -c /etc/supervisord.conf
           ├─301829 php /var/www/html/laravel/artisan queue:work --sleep=3 --tries=10
           ├─301830 php /var/www/html/laravel/artisan queue:work --sleep=3 --tries=10
           ├─301831 php /var/www/html/laravel/artisan queue:work --sleep=3 --tries=10
           ├─301832 php /var/www/html/laravel/artisan queue:work --sleep=3 --tries=10
           ├─301833 php /var/www/html/laravel/artisan queue:work --sleep=3 --tries=10
           ├─301834 php /var/www/html/laravel/artisan queue:work --sleep=3 --tries=10
           ├─301835 php /var/www/html/laravel/artisan queue:work --sleep=3 --tries=10
           └─301836 php /var/www/html/laravel/artisan queue:work --sleep=3 --tries=10

 1月 10 xx:xx:xx 160-251-23-222 systemd[1]: Starting Process Monitoring and Control Daemon...
 1月 10 xx:xx:xx 160-251-23-222 systemd[1]: Started Process Monitoring and Control Daemon.

これでキューワーカーをバックグラウンドで実行させることができました🎉

不備や改善点がございましたら、コメント下さるとありがたいです🙇‍♂️

3. 参考記事一覧

  1. envメソッドの第二引数はデフォルト値の指定で、第一引数がセットされていない/読み込めない場合に用いられます。指定しなくても通常は問題ありませんが、ここでは予想外の不具合に備えて変更しています。

  2. 一部渡すことのできる例外はあるようです。しかし、ほとんどのオブジェクト型でエラーが出ると考えられますので、オブジェクト型以外の型を渡しておく方が無難です。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?