【開発日誌#30】LaravelでQueue(キュー)を用いた、大容量CSVをダウンロードする方法 S3にファイルを保存 URLをメールで送信
【はじめに】
今回は、LaravelでQueueを用いてCSVファイルをS3へ保存、そして保存先のURLをメールで送信する実装を行ないました。その方法をご紹介させていただきます。
この記事では、
- Queueの設定
- CSVのファイルを作成
- CSVをS3に保存
- S3の保存先のURLを作成
- 作成したURLをMailで送信
- Jobの設定
を組み合わせて利用する方法をまとめています。
【Queueの仕組み】
Queue(キュー)とは
データ構造の一つで、処理の”待ち行列”を実現する際によく使われます。要素を入ってきた順に一列に並べ、先に入れた要素から順に取り出すという規則で出し入れを行う、先入先出のデータ構造です。
Job(ジョブ)とは
処理そのものです。QueueにJobが登録され、QueueにあるJobが実行されます。
Dispatch(ディスパッチ)とは
job(ジョブ)をqueue(キュー)に送ることです。
Worker(ワーカー)とは
Jobを実行する役割です。
【背景】
今回はすでに、CSVダウンロードが実装されている状態からの改修作業でした。
参照元のテーブルレコード数が多くなったときに、ダウンロード実行に時間が掛かってしまい、タイムアウトでダウンロードができないという問題が発生したため、対策を行いました。
【作業内容】
タイムアウトの対策として、Queueを使用して、CSV作成をバックグラウンドで処理、S3に保存。S3の保存先のURLをメールで送信しました。
ユーザーは、メールで送られたURLから、生成済みのCSVをダウンロードするため、タイムアウトが解消され、無事にダウンロードを実行できました。
☆ポイント☆
Queueによるバックグラウンド処理を用いて、タイムアウトを解消したことです。
なぜ、タイムアウトにならないのか?
Queueにより非同期処理を行うため、処理が分かれるためです。
非同期処理について、次のイメージをご覧ください。
非同期処理は、タスクを実行中にその処理を中断することなく、別のタスクを実行できます。
今回の場合、ダウンロードボタンを押すとタスク1とタスク2が実行されます。
次のイメージをご覧ください。
- タスク1 「ダウンロードありがとうございます。CSVダウンロードリンクをメールで送信しました。」と表示(処理時間が短い)
- タスク2 バックグラウンドでCSV作成(処理時間が長い)
ユーザー表示はタスク1で、処理時間が短いため、タイムアウトエラーにならない、ということになります。
注意点として、CSV作成をQueueで処理する場合、バックグラウンドで処理されるため、最後のダウンロードまでできないです。
そのため、タスク2(バックグラウンド)でCSV作成の後どうするか、指示をしてあげる必要があります。
今回は、保存先のURLをMailで送信するという対応を行なっています。
【実装内容】
作業手順です。
1:Queueの設定
2:CSVのファイルを作成
3:CSVをS3に保存
4:S3の保存先URLを作成
5:作成したURLをMailで送信する
6:Jobの設定
この流れで進めることにします。
※今回は改修作業でしたので、2:CSVのファイルを作成、S3の接続設定は割愛します。
■対象ファイル
各々の作業環境によってファイル名は異なりますが、今回の対象ファイルを参考に記載します。
- app/Service/ProjectService.php (function applicantsDownload)
- app/Http/Controllers/ProjectController.php (function applicantsDownload)
- app/Mail/CsvDownloadMail.php
- resources/views/csv_download.blade.php
- app/Jobs/CsvCreate.php
■関係図
Jobの実行ファイルと、Job本体のファイルがあります。
Job本体の中に、CSV ・S3へ保存・メール内容などを記載します。
1:Queueの設定
Queueの設定方法はいくつかありますが、今回はDatabaseを用いた方法をご紹介します。
・Queue用のテーブル作成
ターミナルで下記コマンドの実行
php artisan queue:table
php artisan migrate
Queue用の jobsテーブル と
キューの実行失敗時に使用される failed_jobsテーブルが作成されます。
・Jobクラス作成
ターミナルで下記コマンドの実行
php artisan make:job CsvCreateJob
実行すると、app/Jobsファルダに、CsvCreateJob.phpが作成されます。
ここに、Job処理を記載します。
・envの設定
.envを下記で設定
QUEUE_CONNECTION=database
QUEUE_DIRVER=database
Queueは、デフォルトでは同期処理です。
非同期処理にするために上記設定を行います。
2:CSVのファイルを作成
この部分は割愛させていただきます。今回は、PHPで実装していますが、
”Laravel Excel”というライブラリーを用いると、比較的シンプルに作成できます。
3:CSVをS3に保存
下記をCSV作成を実装しているファイルに記載します。
今回は、ProjectService.php の function applicantsDownload に記載します。
下記コードで、ストレージのapp/applicants_csv/{$fileName}にファイルを保存します。
Storage::disk('local')->put('applicants_csv/' . $fileName, '');
下記コードで、S3の applicants_csv/{$fileName}に、ストレージの app/applicants_csv/{$fileName}にあるファイルを保存しています。
Storage::disk('s3')->put('applicants_csv/' . $fileName, file_get_contents(storage_path('app/applicants_csv/' . $fileName)));
4:S3の保存先URLを生成
下記コードを、CSV作成を実装しているファイルに記載します。
今回は、ProjectService.php の function applicantsDownload に記載します。
return Storage::disk('s3')->temporaryUrl('applicants_csv/' . $fileName, now()->addDay());
このコードは、S3保存先のURLを返します。
※temporaryUrlは、S3ドライバーを使用して保存されたファイルへの、有効期限付きURLを作成できる便利なメソッドです。now()->addDay()で有効期限を1日に設定しています。
注意点として、ファイル保存先がlocalの場合、エラーになってしまうので、保存先がS3の場合に、使用可能です。
5:生成したURLをMailで送信する
ターミナルで下記コードを実行し、app/Mailに CsvDownloadMail.phpを作成します。
php artisan make:mail CsvDownloadMail
CsvDownloadMailに、
下記コードを記載します。
$urlを受け取り、使用します。
protected $url;protected $url;
public function __construct($url)
{
$this->url = $url;
}
下記コードで、メールの中身を設定
public function build()
{
return $this->subject('件名')
->view('emails.csv_download') //メール本文のview
->with(['url' => $this->url]); //メール本文にurlを渡す
}
6:Jobの設定
Jobの実行ファイルに下記コードを記載します。
今回は CsvCreate.php に記載します。
$projectId, $searchを受け取り使用
protected $projectId;
protected $search;
public function __construct($projectId, $search)
{
$this->projectId = $projectId;
$this->search = $search;
}
下記コードで、Jobの実行を行います。
今回は、ProjectController.php に記載します。
この引数を実行ファイル(CsvCreate.php)の引数と合わせます。
CsvCreate::dispatch($projectId, $search);
今回は、applicantsDownloadで、
- CSVファイル作成
- CSVをS3に保存
- S3の保存先URLを生成
を実装しています。
CsvCreate.phpでは、
- applicantsDownload
- CsvDownloadMailを作成して、送信
を実行しています。
public function handle(ProjectService $projectService)
{
$project = Project::find($this->projectId);
$url = $projectService->applicantsDownload($this->projectId, $this->search);
try {
Mail::to($project->login_mail_address)->send(new CsvDownloadMail($url, $project->name));
} catch (Exception $e) {
Log::error(__CLASS__ . ' メール送信に失敗', ['exception' => $e->getTrace()]);
}
}
これで、実装完了です。
【まとめ】
いかがでしたでしょうか。今回は、Queueによる非同期処理を用いて、時間が掛かる処理をバックエンドで行う方法をご紹介しました。この手法は、CSV作成以外にも、さまざまなユースケースで活用できます。
この記事があなたの開発に少しでも役立てば幸いです。