エンジニア初心者がPHP/Laravelを学ぶ 仕事

【Laravel】メール送信が遅すぎてイライラしてたけど、キュー(Queue)で解決した話


ことの発端は仕事でWebアプリを作ってて、メール送信機能を実装したときのこと。送信ボタンを押してからメールが受信されるまで4〜6秒もかかってて、これはさすがにヤバいと思ったのがきっかけ。

メール送信が遅い理由を調べてみた結果

最初はなんでこんなに遅いのか理解できなかった。普通にMail::send()を使ってるだけなのに。
調べてみると、Laravelでメールを送信するときは以下の処理が同期的に実行されるらしい:

  • メール内容の組み立て
  • SMTPサーバーへの接続
  • メールの送信処理
  • 接続の終了

要するに、この全部が終わるまでユーザーは待たされるってこと。特にSMTPサーバーとの通信が外部サービスとのやり取りになるから、ネットワークの状況によってはもっと時間がかかることもある。
これは確実に改善しないとダメだと思った。

キューって何?

キューは簡単に言うと、時間のかかる処理を後回しにしてバックグラウンドで実行する仕組み。
メール送信で例えると:

  • ユーザーが送信ボタンを押す
  • 「メール送信タスクをキューに追加」だけして即座にレスポンス
  • 実際のメール送信は別のプロセスで非同期に処理

これなら待機時間ゼロで画面が切り替わるし、メール送信も確実に実行される。理想的すぎる。

事前準備も忘れずに

キューを使う前に、.env ファイルでメール設定をしておく必要がある。Mailtrapを使う場合は以下のような感じ:

MAIL_MAILER=smtp
MAIL_HOST=mailpit
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"

あと、キューのマイグレーションも実行しておく:

php artisan queue:table
php artisan migrate

これで jobs テーブル、job_batches テーブル、failed_jobs テーブルが作成される。

実際にやってみた

最初の実装(遅い版)

まず、普通にメール送信を実装してみた。

// routes/web.php
Route::get('send-mail', function () {
    Mail::send(new PostPublished());
    dd('mail sent');
});

メールクラスはartisanコマンドで作成:

php artisan make:mail PostPublished

これで app/Mail/PostPublished.php が作成される。中身を以下のように編集:

public function envelope()
{
    return new Envelope(
        to: 'test@gmail.com',
        subject: 'Post Published',
    );
}

public function content()
{
    return new Content(
        view: 'welcome',
    );
}

これでブラウザから /send-mail にアクセスしてみると、案の定4〜5秒くらい待たされる。やっぱり遅い。

キューを使った実装(高速版)

次にキューを使って改良してみた。まずジョブクラスを作成:

php artisan make:job SendMail

これで app/Jobs/SendMail.php が作成される。作ったジョブクラスに元のメール送信ロジックを移動:

<?php

namespace App\Jobs;

use App\Mail\PostPublished;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Mail;

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

    public function handle()
    {
        Mail::send(new PostPublished());
    }
}

routes/web.php ファイルを以下のように修正:

// routes/web.php
Route::get('/send-mail', function () {
    SendMail::dispatch();
    dd('mail sent');
});

結果が驚きすぎた
改良後にもう一度アクセスしてみると、一瞬で「mail sent」が表示された。マジで秒速。
メールが送信されてない場合は、キューにジョブを追加しただけで、実際にメールを送信するワーカーが動いてなかったから。
ワーカーを起動するコマンドを実行する必要がある:

php artisan queue:work

これを実行してからもう一度試すと、画面は一瞬で切り替わるし、ちゃんとメールも送信される。完璧。

ビフォーアフターの比較

改良前:

  • ユーザーがアクセス
  • 4〜5秒の待機時間(地獄)
  • メール送信完了

改良後:

  • ユーザーがアクセス
  • バックグラウンドでメール送信処理

この差は歴然。ユーザーエクスペリエンスが段違いに良くなった。

他にも使える場面がたくさんある

キューはメール送信以外でも使える:

  • 大容量ファイルのアップロード処理
  • 画像のリサイズやフォーマット変換
  • データの集計処理やレポート生成
  • 外部APIとの通信処理

要するに時間のかかる処理は全部キューに投げられる。これはかなり使える機能だと思った。

まとめ

Laravelのキュー機能を使えば、メール送信の待機時間問題は簡単に解決できる。実装も思ってたより簡単だし、効果は抜群。
ユーザビリティの改善って、こういう小さい積み重ねが大事だと改めて思った。待機時間が長いだけでユーザーは離脱しちゃうからね。
もしメール送信で同じ悩みを抱えてる人がいたら、ぜひキューを試してみてほしい。マジで世界が変わる。

-エンジニア初心者がPHP/Laravelを学ぶ, 仕事