Node.jsアプリケーションのスケーリング方法としてのクラスタリングの手法
このプログラムの一環として、著者は寄付先として「Society of Women Engineers」を選びました。
はじめに
複数のCPUを備えたシステム上でNode.jsプログラムを実行すると、デフォルトでは単一のCPUを使用するプロセスが作成されます。Node.jsはJavaScriptコードを実行するために単一のスレッドを使用するため、アプリケーションへのすべてのリクエストは単一のCPU上で実行されているスレッドで処理されなければなりません。アプリケーションにCPU集中のタスクがある場合、オペレーティングシステムはそれらを単一のCPUで完了するまでスケジュールする必要があります。これにより、あまりにも多くのリクエストを受けると、単一のプロセスが圧倒されてパフォーマンスが低下する可能性があります。プロセスがクラッシュすると、ユーザーはアプリケーションにアクセスできなくなります。
解決策として、Node.jsは同じマシン上で同じアプリケーションの複数のコピーを作成し、同時に動作させるクラスターモジュールを導入しました。また、ラウンドロビンアルゴリズムを使用して、プロセス間で負荷を均等に分散するためのロードバランサーも備えています。単一のインスタンスがクラッシュした場合、まだ動作している残りのプロセスによってユーザーに対してサービスを提供することができます。負荷が複数のプロセス間で均等に共有されるため、アプリケーションのパフォーマンスが大幅に向上し、単一のインスタンスが過負荷になることを防ぎます。
このチュートリアルでは、4つ以上のCPUを搭載したマシン上でクラスタモジュールを使用してNode.jsアプリケーションをスケーリングします。まず、クラスタリングを使用しないアプリケーションを作成し、それをクラスタリングを使用するように変更します。また、pm2モジュールを使用してアプリケーションを複数のCPUにスケールさせます。さらに、ロードテストツールを使用して、クラスタリングを使用したアプリケーションと使用していないアプリケーションのパフォーマンスを比較し、pm2モジュールを評価します。
前提条件
このチュートリアルを実行するためには、以下が必要です:
- A system with four or more CPUs.If you’re using a remote server with Ubuntu 22.04, you can set up your system by following our Initial Server Setup.
- Node.js set up in your development environment. If you’re running Ubuntu 22.04, follow Option 3 of How To Install Node.js on Ubuntu 22.04. For other systems, visit How To Install Node.js and Create a Local Development Environment.
- Basic knowledge for using Express, which you can refresh with How To Get Started with Node.js and Express.
ステップ1:プロジェクトディレクトリの設定
このステップでは、プロジェクトのディレクトリを作成し、後でこのチュートリアルで構築するアプリケーションの依存関係をダウンロードします。Step 2では、Expressを使用してアプリケーションを構築します。Step 3では、組み込みのnode-clusterモジュールを使用して複数のCPUにスケールし、Step 4でloadtestパッケージで測定します。その後、pm2パッケージでスケールし、Step 5で再度測定します。
始めるために、ディレクトリを作成してください。cluster_demoと呼んだり、好きなディレクトリ名を付けることができます。
- mkdir cluster_demo
次に、ディレクトリに移動してください。
- cd cluster_demo
そして、プロジェクトを初期化し、同時にpackage.jsonファイルも作成します。
- npm init -y
「-y」オプションは、NPMにすべてのデフォルトオプションを受け入れるように指示します。
コマンドが実行されると、以下の結果が出力されます。
Wrote to /home/sammy/cluster_demo/package.json: { “name”: “cluster_demo“, “version”: “1.0.0”, “description”: “”, “main”: “index.js”, “scripts”: { “test”: “echo \”Error: no test specified\” && exit 1″ }, “keywords”: [], “author”: “”, “license”: “ISC” }
あなたの特定のプロジェクトに合った以下の特性に注意してください。
- name: the name of the npm package.
- version: your package’s version number.
- main: the entry point of your project.
他のプロパティについて詳しく学ぶには、NPMのドキュメンテーションのpackage.jsonセクションを参照してください。
次に、お好みのエディタでpackage.jsonファイルを開いてください(このチュートリアルではnanoを使用します)。
- nano package.json
あなたのpackage.jsonファイルに、パッケージのインポート時にESモジュールをサポートするために、ハイライトされたテキストを追加してください。
{
...
"author": "",
"license": "ISC",
"type": "module"
}
CTRL+Xでファイルを保存し、閉じる。 (CTRL+Xで=CTRL+Xでファイルを)
次に、次のパッケージをダウンロードします。
- express: a framework for building web applications in Node.js.
- loadtest: a load testing tool, useful for generating traffic to an app to measure its performance.
- pm2: a tool that automatically scales an app to multiple CPUs.
次のコマンドを実行して、Expressパッケージをダウンロードしてください。
- npm install express
次に、ロードテストとpm2パッケージをグローバルにダウンロードするためのコマンドを実行してください。
- npm install -g loadtest pm2
必要な依存関係をインストールしたので、クラスタリングを使用しないアプリケーションを作成します。
ステップ2 — クラスターを使用せずにアプリケーションを作成する
このステップでは、ユーザーごとにCPU集中タスクを開始する単一のルートを含むサンプルプログラムを作成します。このプログラムではクラスターモジュールを使用しないため、1つのCPUでアプリの単一のインスタンスを実行する場合のパフォーマンスの影響にアクセスできます。後でチュートリアルでクラスターモジュールのパフォーマンスと比較します。
好きなテキストエディタまたはnanoを使用して、index.jsファイルを作成してください。
- nano index.js
index.jsファイルに次の行を追加して、Expressをインポートしてインスタンス化してください。
import express from "express";
const port = 3000;
const app = express();
console.log(`worker pid=${process.pid}`);
最初の行では、expressパッケージをインポートします。2行目では、アプリケーションのサーバーが待機するポートをポート3000に設定します。その後、app変数をExpressのインスタンスに設定します。その後、組み込みのプロセスモジュールを使用して、アプリケーションのプロセスのプロセスIDをコンソールにログします。
次に、CPUに負荷をかけるループを含む/ heavyというルートを定義するために、これらの行を追加してください。
...
app.get("/heavy", (req, res) => {
let total = 0;
for (let i = 0; i < 5_000_000; i++) {
total++;
}
res.send(`The result of the CPU intensive task is ${total}\n`);
});
「ヘビーなルートでは、total変数を500万回増やすループを定義します。その後、res.send()メソッドを使用してtotal変数の値を含むレスポンスを送信します。CPUに負荷をかけるタスクの例は任意のものですが、複雑さを追加せずにCPUに負荷をかけるタスクを示しています。ルートの名前には他の名前も使用できますが、このチュートリアルでは/heavyを使用して重いパフォーマンスタスクを示します。」
次に、Expressモジュールのlisten()メソッドを呼び出して、port変数に格納されているポート3000でサーバーがリスニングするようにしてください。
...
app.listen(port, () => {
console.log(`App listening on port ${port}`);
});
完全なファイルは以下の条件と一致します。
import express from "express";
const port = 3000;
const app = express();
console.log(`worker pid=${process.pid}`);
app.get("/heavy", (req, res) => {
let total = 0;
for (let i = 0; i < 5_000_000; i++) {
total++;
}
res.send(`The result of the CPU intensive task is ${total}\n`);
});
app.listen(port, () => {
console.log(`App listening on port ${port}`);
});
コードの追加が終わったら、保存してファイルを終了してください。その後、ノードコマンドを使用してファイルを実行してください。
- node index.js
コマンドを実行すると、出力結果が以下のようになります。
worker pid=11023 App listening on port 3000
出力は、実行中のプロセスのプロセスIDを示し、サーバーがポート3000でリッスンしていることを確認するメッセージが表示されます。
アプリケーションが正常に動作しているかをテストするために、別のターミナルを開き、以下のコマンドを実行してください。
- curl http://localhost:3000/heavy
Note
ssh -L 3000:localhost:3000 あなたの非ルートユーザー@あなたのサーバーのIP
接続したら、以下のコマンドを使用してアプリにリクエストを送信してください:
curl http://localhost:3000/heavy
出力は次のように一致します。
The result of the CPU intensive task is 5000000
出力は、CPU負荷の高い計算からの結果を提供します。
このポイントでは、CTRL+Cでサーバーを停止することができます。
index.jsファイルをノードコマンドで実行すると、オペレーティングシステム(OS)はプロセスを作成します。プロセスは、オペレーティングシステムが実行中のプログラムに対して作成する抽象化です。OSはプログラムのためにメモリを割り当て、すべてのOSプロセスを含むプロセスリストにエントリを作成します。そのエントリはプロセスIDです。
プログラムのバイナリは、それからプロセスに割り当てられたメモリに配置され、読み込まれます。そこから実行が開始されます。実行中は、システム内の他のプロセスに対する認識はありませんし、プロセス内で起こったことは他のプロセスに影響を与えません。
Node.jsアプリケーションは、複数のCPUを持つサーバーで実行されるため、シングルプロセスで動作します。したがって、この図では、すべての着信リクエストは、シングルCPU上で実行されているプロセスに向けられ、他のCPUはアイドル状態となります。
クラスターモジュールを使用せずにアプリを作成したので、次は複数のCPUを使用してアプリケーションを拡張するためにクラスターモジュールを使用します。
ステップ3 – アプリケーションのクラスタリング
このステップでは、クラスターモジュールを追加して、より多くの負荷を処理し、パフォーマンスを向上させるため同じプログラムの複数のインスタンスを作成します。クラスターモジュールを使用してプロセスを実行すると、マシンの各CPUで複数のプロセスを実行できます。
この図では、リクエストはプライマリプロセス内のロードバランサを通じて渡され、その後、ラウンドロビンアルゴリズムを使用してリクエストが各プロセスに分散される。
今、クラスターモジュールを追加します。ターミナルで、primary.jsファイルを作成してください。
- nano primary.js
以下のコードをprimary.jsファイルに追加して、依存関係をインポートしてください。
import cluster from "cluster";
import os from "os";
import { dirname } from "path";
import { fileURLToPath } from "url";
const __dirname = dirname(fileURLToPath(import.meta.url));
最初の2行では、クラスターとosモジュールをインポートします。次の2行では、dirnameとfileURLToPathをインポートし、__dirname変数の値をindex.jsファイルが実行されているディレクトリの絶対パスに設定します。これらのインポートは、ESモジュールを使用する場合には__dirnameが定義されず、デフォルトでCommonJSモジュールでのみ定義されるため、必要なものです。
次に、index.jsファイルを参照するために以下のコードを追加してください。
...
const cpuCount = os.cpus().length;
console.log(`The total number of CPUs is ${cpuCount}`);
console.log(`Primary pid=${process.pid}`);
cluster.setupPrimary({
exec: __dirname + "/index.js",
});
最初に、マシンのCPU数に基づいて cpuCount 変数を設定します。この数値は4以上である必要があります。次に、コンソールにCPU数をログ出力します。その後、すべてのリクエストを受け取るメインプロセスのプロセスIDをログ出力し、それをワーカープロセス間で分散させるロードバランサーを使用します。
その後、clusterモジュールのsetupPrimary()メソッドを使用してindex.jsファイルを参照し、それが各ワーカープロセスで実行されるようにします。
次に、プロセスを作成するために以下のコードを追加してください。
...
for (let i = 0; i < cpuCount; i++) {
cluster.fork();
}
cluster.on("exit", (worker, code, signal) => {
console.log(`worker ${worker.process.pid} has been killed`);
console.log("Starting another worker");
cluster.fork();
});
cpuCountの値に応じてループが繰り返され、各繰り返しでクラスターモジュールのfork()メソッドが呼び出されます。クラスターモジュールのon()メソッドを使用してexitイベントをアタッチし、プロセスがexitイベントを発生させたとき(通常、プロセスが終了したとき)にリッスンします。exitイベントがトリガーされると、終了したワーカーのプロセスIDをログに表示し、fork()メソッドを呼び出して新しいワーカープロセスを作成して終了したプロセスを置き換えます。
今後、あなたの完全なコードは以下と一致するようになります。
import cluster from "cluster";
import os from "os";
import { dirname } from "path";
import { fileURLToPath } from "url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const cpuCount = os.cpus().length;
console.log(`The total number of CPUs is ${cpuCount}`);
console.log(`Primary pid=${process.pid}`);
cluster.setupPrimary({
exec: __dirname + "/index.js",
});
for (let i = 0; i < cpuCount; i++) {
cluster.fork();
}
cluster.on("exit", (worker, code, signal) => {
console.log(`worker ${worker.process.pid} has been killed`);
console.log("Starting another worker");
cluster.fork();
});
追加する行を終えたら、ファイルを保存して終了してください。
次に、ファイルを実行してください。
- node primary.js
以下の出力は、ほぼ次のようになります(プロセスIDや情報の順序は異なる場合があります)。
The total number of CPUs is 4 Primary pid=7341 worker pid=7353 worker pid=7354 worker pid=7360 App listening on port 3000 App listening on port 3000 App listening on port 3000 worker pid=7352 App listening on port 3000
出力は4つのCPUと、ロードバランサーを含む1つの主プロセス、およびポート3000でリッスンしている4つのワーカープロセスを示します。
次に、2番目の端末に戻り、/heavyルートへのリクエストを送信してください。
- curl http://localhost:3000/heavy
出力がプログラムが正常に動作していることを確認します。
The result of the CPU intensive task is 5000000
今すぐサーバーを停止できます。
現時点では、あなたのマシンのすべてのCPU上で4つのプロセスが実行されています。
クラスタリングをアプリケーションに追加することで、クラスタモジュールを使用するプログラムとクラスタモジュールを使用しないプログラムのパフォーマンスを比較することができます。
ステップ4- ロードテストツールを使用してパフォーマンスを比較する
このステップでは、loadtestパッケージを使用して、2つのプログラムに対してトラフィックを生成します。クラスターモジュールを使用するprimary.jsプログラムと、クラスタリングを使用しないindex.jsプログラムのパフォーマンスを比較します。クラスターモジュールを使用したプログラムは、クラスタリングを使用しないプログラムよりも高速で、特定の時間内により多くのリクエストを処理できることに気付くでしょう。
最初に、クラスターモジュールを使用せずに単一のインスタンスでのみ実行するindex.jsファイルのパフォーマンスを測定します。
最初のターミナルで、サーバーを起動するために index.js ファイルを実行してください。
- node index.js
アプリが動作している出力を受け取ります。
worker pid=7731 App listening on port 3000
次に、サーバーにリクエストを送信するために、ロードテストパッケージを使用するために、2番目の端末に戻ってください。
- loadtest -n 1200 -c 200 -k http://localhost:3000/heavy
-nオプションは、パッケージが送信するリクエストの数を受け入れます。ここでは1200のリクエストを指定しています。-cオプションは、サーバーに同時に送信するリクエストの数を受け入れます。
リクエストが送信されると、パッケージは以下のような出力を返します。
Requests: 0 (0%), requests per second: 0, mean latency: 0 ms Requests: 430 (36%), requests per second: 87, mean latency: 1815.1 ms Requests: 879 (73%), requests per second: 90, mean latency: 2230.5 ms Target URL: http://localhost:3000/heavy Max requests: 1200 Concurrency level: 200 Agent: keepalive Completed requests: 1200 Total errors: 0 Total time: 13.712728601 s Requests per second: 88 Mean latency: 2085.1 ms Percentage of the requests served within a certain time 50% 2234 ms 90% 2340 ms 95% 2385 ms 99% 2406 ms 100% 2413 ms (longest request)
以下の指標に注目して、この出力から把握してください。 (I need to take note of the following metrics from this output.)
- Total time measures how long it took for all the requests to be served. In this output, it took just over 13 seconds to serve all 1200 requests.
- Requests per second measures the number of requests the server can handle per second. In this output, the server handles 88 requests per second.
- Mean latency measures the time it took to send a request and get a response, which is 2085.1 ms in the sample output.
これらのメトリックは、ネットワークやプロセッサの速度によって異なりますが、以下の例に近い値となるでしょう。
index.jsファイルのパフォーマンスを計測したので、サーバーを停止できます。
次に、クラスターモジュールを使用しているprimary.jsファイルのパフォーマンスを測定します。
それをするには、最初の端末に戻ってprimary.jsファイルを再実行してください。
- node primary.js
以前と同じ情報の返信をお送りします。
The total number of CPUs is 4 Primary pid=7841 worker pid=7852 App listening on port 3000 worker pid=7854 App listening on port 3000 worker pid=7853 worker pid=7860 App listening on port 3000 App listening on port 3000
第2のターミナルで、loadtestコマンドをもう一度実行してください。
- loadtest -n 1200 -c 200 -k http://localhost:3000/heavy
終了すると、同様の出力が届きます(ただし、システムのCPU数によって異なる場合があります)。
Requests: 0 (0%), requests per second: 0, mean latency: 0 ms Target URL: http://localhost:3000/heavy Max requests: 1200 Concurrency level: 200 Agent: keepalive Completed requests: 1200 Total errors: 0 Total time: 3.412741962 s Requests per second: 352 Mean latency: 514.2 ms Percentage of the requests served within a certain time 50% 194 ms 90% 2521 ms 95% 2699 ms 99% 2710 ms 100% 2759 ms (longest request)
「クラスターモジュールを使用して実行されているプライマリ.jsアプリの出力によると、処理時間はクラスタリングを使用しないプログラムから13秒から3秒まで短縮されました。サーバーが1秒間に処理できるリクエストの数も、以前の88から352に3倍になっていますので、サーバーは大量の負荷を処理できることがわかります。もう一つ重要なメトリックは平均レイテンシーで、それは2085.1msから514.2msに大幅に減少しました。」
この返信によれば、スケーリングがうまくいっており、応用プログラムが遅延なしでより短時間でより多くのリクエストを処理できることが確認されました。もしマシンをCPUの数を増やすようにアップグレードすれば、アプリケーションは自動的にCPUの数に合わせてスケーリングし、さらなるパフォーマンス向上が期待できます。
あなたのターミナル出力のメトリクスは、ネットワークとプロセッサの速度によって異なりますことをご注意ください。合計時間と平均レイテンシは大幅に低下し、合計時間は急速に増加します。
クラスターモジュールとの比較を行い、アプリがより良いパフォーマンスを示すことがわかったので、サーバーを停止することができます。次に、クラスターモジュールの代わりにpm2を使用します。
ステップ5- クラスタリングのためにpm2を使用する
これまでに、クラスターモジュールを使用してマシンのCPUの数に応じてワーカープロセスを作成してきました。また、ワーカープロセスが終了した際に自動的に再起動する機能も追加しました。このステップでは、クラスターモジュールを基にしたpm2プロセスマネージャーを使用して、アプリの自動スケーリングの代替手段を設定します。このプロセスマネージャーにはロードバランサーが含まれており、マシンのCPUと同じ数だけのワーカープロセスを自動的に作成することができます。また、プロセスの監視も行え、ワーカープロセスが終了した場合には自動的に新しいワーカープロセスを生成することもできます。
使うためには、このチュートリアルの中でのindex.jsファイルをスケールする必要があるファイルと一緒に、pm2パッケージを実行する必要があります。
最初のターミナルで、次のコマンドを使ってpm2クラスタを開始してください。
- pm2 start index.js -i 0
-iオプションは、pm2が作成するワーカープロセスの数を指定するものです。引数に0を渡すと、pm2は自動的にマシンのCPUの数と同じ数のワーカープロセスを作成します。
pm2コマンドを実行すると、ワーカープロセスに関する詳細がpm2に表示されます。
… [PM2] Spawning PM2 daemon with pm2_home=/home/sammy/.pm2 [PM2] PM2 Successfully daemonized [PM2] Starting /home/sammy/cluster_demo/index.js in cluster_mode (0 instance) [PM2] Done. ┌─────┬──────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐ │ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │ ├─────┼──────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤ │ 0 │ index │ default │ 1.0.0 │ cluster │ 7932 │ 0s │ 0 │ online │ 0% │ 54.5mb │ nod… │ disabled │ │ 1 │ index │ default │ 1.0.0 │ cluster │ 7939 │ 0s │ 0 │ online │ 0% │ 50.9mb │ nod… │ disabled │ │ 2 │ index │ default │ 1.0.0 │ cluster │ 7946 │ 0s │ 0 │ online │ 0% │ 51.3mb │ nod… │ disabled │ │ 3 │ index │ default │ 1.0.0 │ cluster │ 7953 │ 0s │ 0 │ online │ 0% │ 47.4mb │ nod… │ disabled │ └─────┴──────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
テーブルには各従業員のプロセスID、ステータス、CPUの利用率、メモリの消費量が含まれており、プロセスの振る舞いを理解するために活用できます。
pm2でクラスタを起動すると、パッケージはバックグラウンドで実行され、システムを再起動しても自動的に再開されます。
労働プロセスからログを読みたい場合は、次のコマンドを使用できます。
- pm2 logs
ログの出力が受け取れます。
[TAILING] Tailing last 15 lines for [all] processes (change the value with –lines option) /home/sammy/.pm2/pm2.log last 15 lines: … PM2 | 2022-12-25T17:48:37: PM2 log: App [index:3] starting in -cluster mode- PM2 | 2022-12-25T17:48:37: PM2 log: App [index:3] online /home/sammy/.pm2/logs/index-error.log last 15 lines: /home/sammy/.pm2/logs/index-out.log last 15 lines: 0|index | worker pid=7932 0|index | App listening on port 3000 0|index | worker pid=7953 0|index | App listening on port 3000 0|index | worker pid=7946 0|index | worker pid=7939 0|index | App listening on port 3000 0|index | App listening on port 3000
直近の8行では、ログが4つの実行中のワーカープロセスごとの出力を提供しており、プロセスIDとポート番号3000が含まれています。この出力によって、すべてのプロセスが実行されていることが確認されます。
以下のコマンドを使用して、プロセスの状態も確認できます。
- pm2 ls
出力は以下のテーブルに一致します。
┌─────┬──────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐ │ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │ ├─────┼──────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤ │ 0 │ index │ default │ 1.0.0 │ cluster │ 7932 │ 5m │ 0 │ online │ 0% │ 56.6mb │ nod… │ disabled │ │ 1 │ index │ default │ 1.0.0 │ cluster │ 7939 │ 5m │ 0 │ online │ 0% │ 55.7mb │ nod… │ disabled │ │ 2 │ index │ default │ 1.0.0 │ cluster │ 7946 │ 5m │ 0 │ online │ 0% │ 56.5mb │ nod… │ disabled │ │ 3 │ index │ default │ 1.0.0 │ cluster │ 7953 │ 5m │ 0 │ online │ 0% │ 55.9mb │ nod… │ disabled │ └────┴────────────────────┴──────────┴──────┴───────────┴──────────┴──────────┘
クラスタが稼働しているので、パフォーマンスをテストするために同じ端末で次のコマンドを入力してください。
- loadtest -n 1200 -c 200 -k http://localhost:3000/heavy
出力結果は以下のように非常に近くなります。
Requests: 0 (0%), requests per second: 0, mean latency: 0 ms Target URL: http://localhost:3000/heavy Max requests: 1200 Concurrency level: 200 Agent: keepalive Completed requests: 1200 Total errors: 0 Total time: 3.771868785 s Requests per second: 318 Mean latency: 574.4 ms Percentage of the requests served within a certain time 50% 216 ms 90% 2859 ms 95% 3016 ms 99% 3028 ms 100% 3084 ms (longest request)
総時間、秒間リクエスト数、および平均待ち時間は、クラスタモジュールを使用した場合とほぼ同様のメトリックが生成されることを示しています。この配置は、pm2のスケーリングが同様に機能していることを示しています。
pm2のワークフローを改善するために、アプリケーションの設定情報を渡すための設定ファイルを作成することができます。この手法を使えば、オプションの指定なしにクラスタを開始または再起動することができます。
設定ファイルを使用するためには、現在のクラスタを削除してください。
- pm2 delete index.js
消えたことに対する返答をお受け取りいただけます。
[PM2] Applying action deleteProcessId on app [index.js](ids: [ 0, 1, 2, 3 ]) [PM2] [index](2) ✓ [PM2] [index](1) ✓ [PM2] [index](0) ✓ [PM2] [index](3) ✓ ┌────┬────────────────────┬──────────┬──────┬───────────┬──────────┬──────────┐ │ id │ name │ mode │ ↺ │ status │ cpu │ memory │ └────┴────────────────────┴──────────┴──────┴───────────┴──────────┴──────────┘
次に、設定ファイルを生成してください。 (Tsugi ni, settei fairu o seisei shite kudasai.)
- pm2 ecosystem
出力は、ファイルが生成されたことを確認します。
File /home/sammy/cluster_demo/ecosystem.config.js generated
ESモジュールをサポートするために、.js を .cjs にリネームします。
- mv ecosystem.config.js ecosystem.config.cjs
エディタを使って、設定ファイルを開いてください。
- nano ecosystem.config.cjs
「ecosystem.config.cjs」ファイルに、以下のハイライト部分のコードを追加してください。
module.exports = {
apps : [{
script: 'index.js',
watch: '.',
name: "cluster_app",
instances: 0,
exec_mode: "cluster",
}, {
script: './service-worker/',
watch: ['./service-worker']
}],
deploy : {
production : {
user : 'SSH_USERNAME',
host : 'SSH_HOSTMACHINE',
ref : 'origin/master',
repo : 'GIT_REPOSITORY',
path : 'DESTINATION_PATH',
'pre-deploy-local': '',
'post-deploy' : 'npm install && pm2 reload ecosystem.config.cjs --env production',
'pre-setup': ''
}
}
};
スクリプトオプションは、pm2パッケージが生成する各プロセスで実行されるファイルを受け入れます。nameプロパティは、クラスターを識別できる任意の名前を受け入れます。これによって、停止、再起動、その他のアクションが必要な場合に役立ちます。instancesプロパティは、希望するインスタンスの数を受け入れます。instancesを0に設定すると、pm2はCPUの数と同じ数のプロセスを生成します。exec_modeはクラスターオプションを受け入れ、pm2がクラスターモードで実行されるように指示します。
作業が終わりましたら、ファイルを保存して閉じてください。
クラスタを開始するには、次のコマンドを実行してください。
- pm2 start ecosystem.config.cjs
以下の応答を受け取ります:
[PM2][WARN] Applications cluster_app, service-worker not running, starting… [PM2][ERROR] Error: Script not found: /home/node-user/cluster_demo/service-worker [PM2] App [cluster_app] launched (4 instances)
最後の行は、4つのプロセスが実行されていることを確認しています。このチュートリアルではサービスワーカースクリプトを作成していないため、サービスワーカーが見つからないというエラーは無視しても構いません。
クラスタが稼働しているか確認するために、状態をチェックしてください。
- pm2 ls
4つのプロセスが実行されていることを確認する応答を受け取ります。
┌────┬────────────────────┬──────────┬──────┬───────────┬──────────┬──────────┐ │ id │ name │ mode │ ↺ │ status │ cpu │ memory │ ├────┼────────────────────┼──────────┼──────┼───────────┼──────────┼──────────┤ │ 0 │ cluster_app │ cluster │ 0 │ online │ 0% │ 56.9mb │ │ 1 │ cluster_app │ cluster │ 0 │ online │ 0% │ 57.6mb │ │ 2 │ cluster_app │ cluster │ 0 │ online │ 0% │ 55.9mb │ │ 3 │ cluster_app │ cluster │ 0 │ online │ 0% │ 55.9mb │ └────┴────────────────────┴──────────┴──────┴───────────┴──────────┴──────────┘
クラスターを再起動したい場合は、ecosystem.config.cjsファイルで定義したアプリ名「cluster_app」を使用することができます。
- pm2 restart cluster_app
クラスターが再起動します。
Use –update-env to update environment variables [PM2] Applying action restartProcessId on app [cluster_app](ids: [ 0, 1, 2, 3 ]) [PM2] [cluster_app](0) ✓ [PM2] [cluster_app](1) ✓ [PM2] [cluster_app](2) ✓ [PM2] [cluster_app](3) ✓ ┌────┬────────────────────┬──────────┬──────┬───────────┬──────────┬──────────┐ │ id │ name │ mode │ ↺ │ status │ cpu │ memory │ ├────┼────────────────────┼──────────┼──────┼───────────┼──────────┼──────────┤ │ 0 │ cluster_app │ cluster │ 1 │ online │ 0% │ 48.0mb │ │ 1 │ cluster_app │ cluster │ 1 │ online │ 0% │ 47.9mb │ │ 2 │ cluster_app │ cluster │ 1 │ online │ 0% │ 38.8mb │ │ 3 │ cluster_app │ cluster │ 1 │ online │ 0% │ 31.5mb │ └────┴────────────────────┴──────────┴──────┴───────────┴──────────┴──────────┘
クラスターの管理を続けるために、以下のコマンドを実行することができます。
Command | Description |
---|---|
pm2 start app_name |
Starts the cluster |
pm2 restart app_name |
Kills the cluster and starts it again |
pm2 reload app_name |
Restarts the cluster without downtime |
pm2 stop app_name |
Stops the cluster |
pm2 delete app_name |
Deletes the cluster |
pm2モジュールとクラスターモジュールを使用して、アプリケーションのスケーリングができます。
結論
このチュートリアルでは、クラスターモジュールを使用してアプリケーションをスケーリングしました。最初に、クラスターモジュールを使用しないプログラムを作成しました。次に、マシン上の複数のCPUにアプリをスケールするためにクラスターモジュールを使用したプログラムを作成しました。その後、クラスターモジュールを使用するアプリと使用しないアプリのパフォーマンスを比較しました。最後に、クラスターモジュールの代わりにpm2パッケージを使用してアプリを複数のCPUにスケールしました。
さらに進むために、モジュールについて詳しく学ぶためにクラスターモジュールのドキュメントページを訪れることができます。
もしもあなたがpm2を使い続けたいのであれば、PM2-プロセス管理ドキュメンテーションを参照してみてください。また、Ubuntu 22.04上でNode.jsアプリケーションを本番環境に設定する方法については、pm2を使用した当社のチュートリアルも試してみることができます。
Node.jsには、worker_threadsモジュールも同梱されており、それを使用することで、CPU負荷の高いタスクをワーカースレッド間で分割して高速に完了させることができます。Node.jsでマルチスレッディングを使用する方法については、当社のチュートリアルを試してみてください。また、専用のウェブワーカーを使用することで、フロントエンドでCPUに負荷のかかるタスクを最適化することもできます。ウェブワーカーを使用したCPUに負荷のかかるタスクの処理方法については、当社のチュートリアルに従ってください。CPUに負荷のかかるタスクがアプリケーションのリクエスト/レスポンスサイクルに影響を与えないようにする方法を学びたい場合は、Node.jsとBullMQを使用した非同期タスクの処理方法について詳細をご覧ください。