Node.jsアプリケーションのパフォーマンスと拡張性を向上させる方法

Node.jsは現代Web開発において広く採用されているプラットフォームですが、アプリケーションのスケールアップに伴うパフォーマンス課題や拡張性の限界に対処する必要があります。本記事では、Node.jsアプリケーションの最適化手法について具体的なコード例を交えて説明します。

1. マルチプロセスによる負荷分散

Node.jsのシングルスレッドアーキテクチャは高負荷時にボトルネックとなる可能性があります。Clusterモジュールを活用することで、複数CPUコアを効率的に利用できます。

const cluster = require('cluster');
const http = require('http');
const os = require('os');

if (cluster.isMaster) {
  console.log(`マスタープロセス ${process.pid} 起動`);

  os.cpus().forEach((_, index) => {
    cluster.fork();
  });

  cluster.on('exit', (worker) => {
    console.log(`ワーカー ${worker.process.pid} 終了`);
  });
} else {
  http.createServer((req, res) => {
    res.writeHead(200);
    res.end('こんにちは!');
  }).listen(8000);

  console.log(`ワーカー ${process.pid} 起動`);
}

2. キャッシュ戦略の導入

Redisなどの外部キャッシュシステムを組み込むことでDBアクセス頻度を削減できます。

const express = require('express');
const redis = require('redis');
const app = express();
const cacheClient = redis.createClient();

app.get('/api/data', (req, res) => {
  const cacheKey = 'api_response_v1';

  cacheClient.get(cacheKey, (err, cached) => {
    if (err) return res.status(500).send(err);

    if (cached) {
      res.json(JSON.parse(cached));
    } else {
      const freshData = { status: '最新データ', timestamp: Date.now() };
      cacheClient.setex(cacheKey, 3600, JSON.stringify(freshData));
      res.json(freshData);
    }
  });
});

app.listen(3000, () => console.log('サーバー起動: 3000ポート'));

3. 非同期処理の最適化

Promiseベースの非同期処理により、イベントループの効率を最大化できます。

const fs = require('fs').promises;

async function processFile(filePath) {
  try {
    const content = await fs.readFile(filePath, 'utf8');
    const lines = content.split('\n').filter(Boolean);
    console.log(`処理行数: ${lines.length}`);
  } catch (error) {
    console.error(`ファイル処理失敗: ${error.message}`);
  }
}

processFile('./input.txt');

4. ストリーミングによる大容量ファイル処理

ReadStreamとWriteStreamの組み合わせでメモリ使用量を抑制します。

const fs = require('fs');

const inputStream = fs.createReadStream('big_data.bin');
const outputStream = fs.createWriteStream('backup.bin');

inputStream.on('data', (buffer) => {
  outputStream.write(buffer);
});

inputStream.on('end', () => {
  outputStream.end();
  console.log('コピー完了');
});

5. パフォーマンス監視ツールの活用

hrtime APIを使用した処理時間測定により、ボトルネックの特定が可能です。

function benchmarkExecution() {
  const startTime = process.hrtime.bigint();
  
  // 重い計算処理
  for (let i = 0; i < 1e7; i++) {}
  
  const duration = process.hrtime.bigint() - startTime;
  console.log(`処理時間: ${Number(duration / 1e6)}ms`);
}

benchmarkExecution();

6. HTTP圧縮の有効化

compressionミドルウェアによりレスポンスサイズを削減します。

const express = require('express');
const compression = require('compression');
const app = express();

app.use(compression());

app.get('/', (req, res) => {
  res.send('圧縮されたコンテンツを表示中');
});

app.listen(3000, () => console.log('サーバー起動'));

7. プロセス管理ツールの導入

PM2を用いたプロセス監視と自動再起動機能により安定性を確保します。

# インストール
npm install pm2 -g

# アプリケーション起動
pm2 start server.js --no-daemon

# ステータス確認
pm2 list

# 緊急停止
pm2 kill

8. DB接続プールの最適化

接続プールの設定によりデータベース操作のオーバーヘッドを軽減します。

const mysql = require('mysql2/promise');

const pool = mysql.createPool({
  host: 'db.example.com',
  user: 'app_user',
  password: 'secure_password',
  database: 'production_db',
  waitForConnections: true,
  connectionLimit: 15
});

async function fetchUsers() {
  const conn = await pool.getConnection();
  try {
    const [rows] = await conn.query('SELECT * FROM members');
    return rows;
  } finally {
    conn.release();
  }
}

タグ: Node.js redis pm2 express ストリーミング

6月19日 21:51 投稿