Node.js での Redis を使用したバックグラウンドジョブ
最終更新日 2024年05月15日(水)
Table of Contents
Web アプリケーションで、エンドユーザーのリクエストに可能な限り迅速に応えることは重要です。Web リクエストの実行時間が 500 ミリ秒を超えないようにする、というのが経験則です。完了に 1 秒、2 秒、あるいはそれ以上かかるリクエストがアプリに存在する場合、代わりにバックグラウンドジョブを使用することを検討してください。
このアーキテクチャパターンについての詳細は、Worker dyno、バックグラウンドジョブ、キューイング の記事を参照してください。
Node.js サーバーで、計算コストが高いリクエストによってイベントループがブロックされる可能性がある場合、この考慮事項はさらに重要です。このブロックによって、計算が完了するまでサーバーが新しいリクエストに応答できなります。この計算を別のプロセスに分離すれば、Web サーバーの軽量性と応答性が維持されます。
この記事では、ワーカーキューとサンプル Node.js アプリケーションで Bull を使用してバックグラウンドジョブのキューを管理する方法について説明します。
この記事では、Redis (ローカル開発用) と Heroku CLI がインストール済みであることを前提としています。
dyno を使用してこのチュートリアルを完了した場合、使用量のカウントに入ります。低料金プランを使用してこのチュートリアルを完了することをお勧めします。資格のある学生の皆様は、新しい Heroku for GitHub Students プログラムを通じてプラットフォームクレジットを申請できます。
はじめに
以下の手順を完了して、このアプリケーションを Heroku アカウントに複製します。
ダッシュボードから行う場合
- (デプロイ) をクリックします。
- ジョブが処理されるよう、ダッシュボードの
Resources
(リソース) タブでworker
プロセスを少なくとも 1 つの dyno にスケーリングします。 - ブラウザでアプリを開いて新しいジョブを開始し、完了するまで監視します。
CLI から行う場合
$ git clone git@github.com:heroku-examples/node-workers-example.git
$ cd node-workers-example
$ heroku create
$ heroku addons:create heroku-redis
$ git push heroku main
$ heroku ps:scale worker=1
$ heroku open
アプリケーションの概要
アプリケーションは 2 つのプロセスで構成されます。
web
— フロントエンドアセットを提供し、新しいバックグラウンドジョブを受け付けて、既存のジョブのステータスを報告する Express サーバー。worker
— 受信ジョブを実行する小さな Node.js プロセス。
これらのプロセスは、特定のアプリケーションのニーズに基づいて個別にスケーリングできます。Heroku のプロセスモデルについての詳細は、「プロセスモデル」の記事を参照してください。
web
プロセスは、新しいジョブを開始してそれらを監視する簡略化したフロントエンドインターフェースの例を実装する index.html
および client.js
ファイルを提供します。
Web プロセス
server.js
はごく小さな Express サーバーです。注目すべき重要な点は、Redis サーバーへの接続と、名前付きワークキューの設定です。
let REDIS_URL = process.env.REDIS_URL || 'redis://127.0.0.1:6379';
let workQueue = new Queue('work', REDIS_URL);
もう 1 つの重要な注意点は、POST
リクエストを受信したときのジョブの開始です。
app.post('/job', async (req, res) => {
let job = await workQueue.add();
res.json({ id: job.id });
});
通常、バックグラウンドジョブの開始への直接アクセスをこのようにクライアントに付与することはありませんが、この単純な例は説明を目的としたものです。
ワーカープロセス
worker.js
は、throng を使用してワーカープロセスのクラスターを起動します。この例では、ジョブは解決の前に少しスリープしますが、これは独自のワーカーの作成を開始するのに良い機会です。
並列性に関する 2 つの概念を理解することが重要です。1 つ目は、ワーカーの数です。
let workers = process.env.WEB_CONCURRENCY || 2;
各ワーカーは、独立したイベントループを持つスタンドアロンの Node.js プロセスです。Heroku の dyno では、デフォルト値は WEB_CONCURRENCY
環境変数で設定されます。この値は dyno のメモリ量に応じて増減しますが、特定のアプリケーションに合わせたチューニングが必要な場合があります。詳細は、「Node.js アプリケーションの並列性の最適化」を参照してください。
並列性に関する 2 つ目の概念は、各ワーカーが一度に処理するジョブの最大数です。
let maxJobsPerWorker = 50;
workQueue.process(maxJobsPerWorker, async (job) => {
// ...
});
各ワーカーは Redis キューからジョブを選択して処理します。この設定は、各ワーカーが一度に処理を試みるジョブの数を制御します。
多くの場合、アプリケーションに合わせてこの設定をチューニングするタスクを完了する必要があります。各ジョブで主に待機するのが、外部の API やサービスのようなネットワーク応答である場合、この設定値を大きくします。各ジョブの CPU 使用率が高い場合は、この設定を 1
まで下げることを検討します。この場合、ワーカープロセスの起動数を増やしてみることをお勧めします。
クライアント Web アプリ
client.js
では、ジョブを開始してその進捗状況を監視できるよう、ごく小さな Web フロントエンドを実装します。本番環境では、ジョブキューの状態を監視するための複数の公式 UI が Bull によって推奨されています。
この記事で説明したのは、Bull の機能のほんの一例です。以下を含むさらに多くの機能が備わっています。
- キューの優先順位付け
- 速度制限
- ジョブのスケジューリング
- 再試行
これらの機能の使用方法について詳しくは、Bull のドキュメントを参照してください。