NodeJS 速習チュートリアル

Node.js プロセスマネジメント

1. プロセスマネジメントとは?

Node.js におけるプロセスマネジメントとは、アプリケーションの**ライフサイクル(Lifecycle)**を制御することを指します。

具体的には以下のような内容が含まれます:

  • アプリケーションの起動と停止
  • クラッシュ後の自動再起動
  • パフォーマンスのモニタリング
  • システムシグナルの処理
  • 環境変数の管理

2. プロセス情報へのアクセス

process オブジェクトを使用すると、現在の Node.js プロセスに関する詳細情報の取得や制御が可能です。

以下に便利なプロパティをいくつか紹介します:

// プロセスID (PID) の取得
console.log('プロセスID (PID):', process.pid);

// プラットフォーム情報の取得
console.log('プラットフォーム:', process.platform);
console.log('Node.js バージョン:', process.version);

// メモリ使用量 (バイト単位)
console.log('メモリ使用量:', process.memoryUsage());

// コマンドライン引数
console.log('引数:', process.argv);

3. プロセスの終了

Node.js プログラムを停止するタイミングは、以下のメソッドで制御できます。

3.1 正常終了

// 成功として終了 (ステータスコード 0)
process.exit();

// または明示的に指定
process.exit(0);

3.2 エラーによる終了

// エラーとして終了 (ステータスコード 1)
process.exit(1);

3.3 beforeExit イベント

// 終了前にクリーンアップ処理を実行
process.on('beforeExit', (code) => {
  console.log('終了コード:', code, 'で終了しようとしています');
});

4. プロセスイベントの処理

Node.js プロセスは、システムのシグナルやイベントに応答できます。

4.1 Ctrl+C (SIGINT) の処理

// Ctrl+C をハンドルする
process.on('SIGINT', () => {
  console.log('\nSIGINT を受信しました。終了するには Control-D を押してください。');
  // 必要に応じてクリーンアップを実行
  process.exit(0);
});

4.2 プロセス終了シグナル (SIGTERM) の処理

process.on('SIGTERM', () => {
  console.log('SIGTERM を受信しました。クリーンアップを実行中...');
  server.close(() => {
    console.log('サーバーを停止しました');
    process.exit(0);
  });
});

4.3 未捕捉の例外 (Uncaught Exceptions)

process.on('uncaughtException', (err) => {
  console.error('未捕捉の例外が発生しました:', err);
  // クリーンアップを実行し、エラーとして終了
  process.exit(1);
});

5. プロセスマネージャー (Process Managers)

本番環境(プロダクション)では、アプリケーションを安定稼働させるためにプロセスマネージャーを使用します。もっともポピュラーな選択肢は PM2 です。

5.1 PM2 のグローバルインストール

npm install -g pm2

5.2 基本的な PM2 コマンド

# アプリケーションの起動
pm2 start app.js

# 実行中のすべてのアプリケーションを表示
pm2 list

# リソースのモニタリング
pm2 monit

# アプリケーションログの表示
pm2 logs

# アプリケーションの停止
pm2 stop アプリ名

# アプリケーションの再起動
pm2 restart アプリ名

# PM2 のリストからアプリケーションを削除
pm2 delete アプリ名

5.3 PM2 の設定

詳細な設定を行うには、エコシステムファイル(Ecosystem file)を作成します:

// ecosystem.config.js
module.exports = {
  apps: [{
    name: 'my-app',
    script: 'app.js',
    instances: 'max', // CPUコア数に合わせて最大インスタンスで実行
    autorestart: true, // クラッシュ時に自動再起動
    watch: false, // ファイル変更の監視(本番環境では通常 false)
    max_memory_restart: '1G', // メモリが1GBを超えたら再起動
    env: {
      NODE_ENV: 'development',
    },
    env_production: {
      NODE_ENV: 'production',
    }
  }]
};

6. 環境変数 (Environment Variables)

環境変数は、異なる環境(開発、テスト、本番)でアプリケーションの挙動を切り替えるためのキーと値のペアです。

6.1 環境変数へのアクセス

// 特定の環境変数を取得
const apiKey = process.env.API_KEY;

// 定義されていない場合のデフォルト値を設定
const port = process.env.PORT || 3000;

// 本番環境かどうかをチェック
const isProduction = process.env.NODE_ENV === 'production';

// すべての環境変数を表示
console.log('環境変数一覧:', process.env);

6.2 .env ファイルからの読み込み

# dotenv パッケージをインストール
npm install dotenv
// .env ファイルから環境変数をロード
require('dotenv').config();

// .env 内の変数にアクセス可能になる
console.log('データベース URL:', process.env.DATABASE_URL);

環境変数のベストプラクティス:

  • 機密データ(APIキーなど)をバージョン管理(Gitなど)にコミットしない
  • ローカル開発には .env を使用する
  • 本番環境ではホスティングプラットフォームの設定画面から環境変数を設定する
  • 必要な環境変数は README にドキュメント化しておく

7. 子プロセス (Child Processes)

Node.js は、child_process モジュールを使用してシステムコマンドや他のスクリプトを実行できます。

7.1 シンプルなコマンドの実行 (exec)

const { exec } = require('child_process');

exec('ls -la', (error, stdout, stderr) => {
  if (error) {
    console.error(`エラー: ${error.message}`);
    return;
  }
  if (stderr) {
    console.error(`標準エラー出力: ${stderr}`);
    return;
  }
  console.log(`出力結果: ${stdout}`);
});

7.2 大規模な出力に対応する (spawn)

const { spawn } = require('child_process');

// 大量のデータをストリーミング形式で扱うのに適しています
const child = spawn('find', ['/', '-type', 'f']);

child.stdout.on('data', (data) => {
  console.log(`ファイル発見: ${data}`);
});

child.stderr.on('data', (data) => {
  console.error(`エラー: ${data}`);
});

child.on('close', (code) => {
  console.log(`子プロセスがコード ${code} で終了しました`);
});

8. プロセスモニタリングとパフォーマンス

8.1 メモリ使用量

// メモリ使用量を MB 単位で取得
function getMemoryUsage() {
  const used = process.memoryUsage();
  return {
    rss: `${Math.round(used.rss / 1024 / 1024 * 100) / 100} MB`,
    heapTotal: `${Math.round(used.heapTotal / 1024 / 1024 * 100) / 100} MB`,
    heapUsed: `${Math.round(used.heapUsed / 1024 / 1024 * 100) / 100} MB`,
    external: `${Math.round(used.external / 1024 / 1024 * 100) / 100} MB`
  };
}

// 5秒ごとにメモリ使用量をモニタリング
setInterval(() => {
  console.log('メモリ使用状況:', getMemoryUsage());
}, 5000);

8.2 CPU 使用率

const startUsage = process.cpuUsage();

// CPU に負荷のかかる処理のシミュレーション
for (let i = 0; i < 1000000000; i++) {}

const endUsage = process.cpuUsage(startUsage);
console.log('CPU 使用量 (ユーザー):', endUsage.user / 1000, 'ms');
console.log('CPU 使用量 (システム):', endUsage.system / 1000, 'ms');

9. まとめ(Key Takeaways)

  • Process オブジェクト: システムおよびプロセス情報へのアクセス。
  • プロセス制御: アプリケーションライフサイクルの開始、停止、管理。
  • 環境変数: 環境に応じた挙動の設定。
  • 子プロセス: システムコマンドや外部スクリプトの実行。
  • エラーハンドリング: 未捕捉の例外や拒否された Promise の処理。
  • シグナル: SIGINT や SIGTERM などのシステムシグナルへの応答。
  • PM2: 本番環境におけるプロセスマネジメントに不可欠なツール。
  • パフォーマンスモニタリング: メモリおよび CPU 使用率の追跡。

効果的なプロセスマネジメントは、特に本番環境において、信頼性が高くメンテナンスしやすい Node.js アプリケーションを構築するために極めて重要です。