正在加载,请稍候…

Node.js Performance Optimization: Profiling, Clustering, and Worker Threads

Optimize Node.js applications for high throughput — CPU profiling, memory leak detection, cluster mode, worker threads, libuv tuning, and V8 internals.

Node.js Performance Fundamentals

Node.js is single-threaded but highly concurrent — understanding its event loop is key to optimization.

CPU Profiling with --prof

# Generate V8 profile
node --prof app.js

# Process the profile
node --prof-process isolate-*.log > profile.txt

# Or use clinic.js
npm install -g clinic
clinic doctor -- node app.js
clinic flame -- node app.js

Event Loop Monitoring

import { monitorEventLoopDelay } from 'perf_hooks';

const h = monitorEventLoopDelay({ resolution: 20 });
h.enable();

setInterval(() => {
  console.log({
    min: h.min / 1e6 + 'ms',
    max: h.max / 1e6 + 'ms',
    mean: h.mean / 1e6 + 'ms',
    p99: h.percentile(99) / 1e6 + 'ms',
  });
  h.reset();
}, 5000);

Cluster Mode

// cluster.js
import cluster from 'cluster';
import os from 'os';
import { createServer } from './app.js';

if (cluster.isPrimary) {
  const numCPUs = os.cpus().length;
  console.log(`Primary ${process.pid}: forking ${numCPUs} workers`);
  
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }
  
  cluster.on('exit', (worker, code, signal) => {
    console.log(`Worker ${worker.process.pid} died — restarting`);
    cluster.fork(); // Auto-restart
  });
} else {
  const app = createServer();
  app.listen(3000, () => {
    console.log(`Worker ${process.pid}: listening on port 3000`);
  });
}

Worker Threads for CPU-Bound Tasks

// main.js
import { Worker, isMainThread, parentPort, workerData } from 'worker_threads';
import { cpus } from 'os';

function runWorker(data) {
  return new Promise((resolve, reject) => {
    const worker = new Worker('./worker.js', { workerData: data });
    worker.on('message', resolve);
    worker.on('error', reject);
    worker.on('exit', (code) => {
      if (code !== 0) reject(new Error(`Worker exited with code ${code}`));
    });
  });
}

// Parallel processing
const results = await Promise.all(
  chunks.map(chunk => runWorker({ data: chunk }))
);
// worker.js
import { parentPort, workerData } from 'worker_threads';

function heavyComputation(data) {
  // CPU-intensive work: image processing, crypto, parsing
  return data.reduce((acc, n) => acc + n * n, 0);
}

parentPort.postMessage(heavyComputation(workerData.data));

Worker Thread Pool

import { Worker } from 'worker_threads';
import { EventEmitter } from 'events';

class WorkerPool extends EventEmitter {
  constructor(workerPath, poolSize = cpus().length) {
    super();
    this.workers = [];
    this.queue = [];
    
    for (let i = 0; i < poolSize; i++) {
      this.addWorker(workerPath);
    }
  }
  
  addWorker(workerPath) {
    const worker = new Worker(workerPath);
    worker.on('message', (result) => {
      worker.busy = false;
      const { resolve } = worker.currentTask;
      resolve(result);
      this.processQueue(worker);
    });
    worker.busy = false;
    this.workers.push(worker);
  }
  
  run(data) {
    return new Promise((resolve, reject) => {
      const freeWorker = this.workers.find(w => !w.busy);
      const task = { data, resolve, reject };
      
      if (freeWorker) {
        this.runTask(freeWorker, task);
      } else {
        this.queue.push(task);
      }
    });
  }
  
  runTask(worker, task) {
    worker.busy = true;
    worker.currentTask = task;
    worker.postMessage(task.data);
  }
  
  processQueue(worker) {
    if (this.queue.length > 0) {
      this.runTask(worker, this.queue.shift());
    }
  }
}

Memory Leak Detection

# Use node --inspect + Chrome DevTools
node --inspect app.js

# Or use heapdump
npm install heapdump

# Take heap snapshot on demand
process.kill(process.pid, 'SIGUSR2');
// Detect memory leaks with memwatch-next
import memwatch from '@memwatch/node';

memwatch.on('leak', (info) => {
  console.error('Memory leak detected:', info);
});

memwatch.on('stats', (stats) => {
  console.log('GC stats:', stats);
});

Stream Processing for Large Data

import { Transform, pipeline } from 'stream';
import { promisify } from 'util';

const pipelineAsync = promisify(pipeline);

// Process large CSV without loading into memory
await pipelineAsync(
  fs.createReadStream('large-file.csv'),
  csv.parse({ headers: true }),
  new Transform({
    objectMode: true,
    transform(row, encoding, callback) {
      // Process each row
      const processed = transformRow(row);
      callback(null, processed);
    }
  }),
  new Transform({
    objectMode: true,
    transform(row, encoding, callback) {
      this.push(JSON.stringify(row) + '
');
      callback();
    }
  }),
  fs.createWriteStream('output.jsonl')
);

HTTP/2 and Keep-Alive

import http2 from 'http2';
import fs from 'fs';

const server = http2.createSecureServer({
  key: fs.readFileSync('key.pem'),
  cert: fs.readFileSync('cert.pem'),
});

// Express with http2
import spdy from 'spdy';
const server = spdy.createServer(
  { key, cert },
  app
);

Performance Checklist

  • Use --max-old-space-size to increase heap when needed
  • Avoid synchronous I/O (fs.readFileSync etc.) in hot paths
  • Use Buffer.allocUnsafe() for performance-critical buffer ops
  • Prefer Map over plain objects for frequent add/delete
  • Use Promise.all for parallel async operations
  • Enable keep-alive in HTTP agents
  • Use connection pooling for databases
  • Cache expensive computations with LRU cache