Hiệu năng Node.js: Event Loop, Clustering và Tối ưu hóa trong năm 2026

Phân tích chuyên sâu cơ chế Event Loop, Clustering và các chiến lược tối ưu hiệu năng Node.js. Bao gồm mã mẫu thực tế, giám sát production và hướng dẫn phỏng vấn kỹ thuật.

Minh họa hiệu năng Node.js với Event Loop, Clustering và các chiến lược tối ưu hóa

Hiệu năng luôn là yếu tố quyết định đối với bất kỳ ứng dụng Node.js nào hoạt động trong môi trường sản xuất. Tuy nhiên, phần lớn các vấn đề về hiệu năng không xuất phát từ bản thân runtime, mà từ cách lập trình viên sử dụng nó. Một API xử lý 50 request/giây có thể tăng lên 5.000 request/giây chỉ bằng việc hiểu đúng cách Event Loop hoạt động, áp dụng Clustering hợp lý và loại bỏ các anti-pattern phổ biến.

Node.js 24, được phát hành vào tháng 4 năm 2025 và trở thành phiên bản LTS vào tháng 10, đã mang đến những cải tiến đáng kể về hiệu năng V8, hỗ trợ tốt hơn cho Worker Threads và các công cụ giám sát tích hợp mạnh mẽ hơn. Đối với các đội ngũ phát triển đang chuẩn bị cho phỏng vấn kỹ thuật hoặc tối ưu hóa hệ thống production, việc nắm vững ba trụ cột — Event Loop, Clustering và Memory Management — là điều kiện tiên quyết không thể bỏ qua.

Bài viết này phân tích từng cơ chế cốt lõi với mã nguồn minh họa thực tế, từ việc đo lường độ trễ Event Loop cho đến thiết lập Worker Pool và giám sát bằng OpenTelemetry. Mỗi đoạn mã đều có thể chạy trực tiếp trong dự án thực tế.

Điểm mấu chốt cần nhớ

Event Loop không phải là một thread riêng biệt — nó là một vòng lặp chạy trên main thread duy nhất của Node.js. Mọi thao tác đồng bộ kéo dài đều chặn toàn bộ ứng dụng. Hiểu rõ các phase của Event Loop là nền tảng để viết code Node.js hiệu năng cao.

Event Loop: Cơ chế hoạt động chi tiết

Event Loop là trái tim của Node.js. Khác với mô hình multi-thread truyền thống, Node.js sử dụng một single thread kết hợp với non-blocking I/O để xử lý hàng nghìn kết nối đồng thời. Tuy nhiên, "single-threaded" không có nghĩa là Node.js chỉ sử dụng một CPU core — libuv duy trì một thread pool mặc định gồm 4 worker cho các tác vụ I/O nặng như đọc file, DNS lookup và mã hóa.

Event Loop hoạt động theo 6 phase tuần tự, mỗi phase có một hàng đợi callback riêng biệt:

  1. Timers — Thực thi các callback của setTimeoutsetInterval đã hết hạn
  2. Pending Callbacks — Xử lý các callback I/O bị trì hoãn từ vòng lặp trước
  3. Idle/Prepare — Phase nội bộ của libuv
  4. Poll — Nhận các sự kiện I/O mới và thực thi callback tương ứng
  5. Check — Thực thi các callback của setImmediate
  6. Close Callbacks — Xử lý sự kiện close (ví dụ: socket.on('close'))

Điểm quan trọng là microtask queue (bao gồm Promiseprocess.nextTick) được xử lý giữa mỗi lần chuyển phase. Điều này giải thích tại sao process.nextTick luôn chạy trước Promise.then, và cả hai đều chạy trước bất kỳ callback nào trong phase tiếp theo.

event-loop-phases.jsjavascript
// Demonstrating phase execution order

const fs = require('fs');

// Phase 1: Timers — executes setTimeout/setInterval callbacks
setTimeout(() => console.log('1. Timer phase'), 0);

// Phase 4: Poll — executes I/O callbacks
fs.readFile(__filename, () => {
  console.log('2. Poll phase (I/O callback)');

  // Phase 5: Check — executes setImmediate callbacks
  setImmediate(() => console.log('3. Check phase (setImmediate)'));

  // Phase 1 again: Timer scheduled from within I/O
  setTimeout(() => console.log('4. Timer phase (from I/O)'), 0);
});

// Microtask — runs between every phase transition
Promise.resolve().then(() => console.log('Microtask: Promise'));
process.nextTick(() => console.log('Microtask: nextTick'));

Kết quả khi chạy đoạn mã trên sẽ luôn theo thứ tự: nextTick chạy đầu tiên (microtask ưu tiên cao nhất), sau đó là Promise, rồi Timer phase, tiếp đến là Poll phase khi I/O hoàn thành, và cuối cùng setImmediate luôn chạy trước setTimeout(0) khi được gọi từ bên trong I/O callback. Thứ tự này là kiến thức bắt buộc trong các buổi phỏng vấn kỹ thuật Node.js.

Giám sát Event Loop trong môi trường Production

Việc biết Event Loop hoạt động như thế nào trên lý thuyết là chưa đủ. Trong production, cần có khả năng đo lường và cảnh báo khi Event Loop bị quá tải. Node.js cung cấp module perf_hooks với hai công cụ chính: monitorEventLoopDelay để đo độ trễ và eventLoopUtilization để đo tỷ lệ sử dụng.

event-loop-monitor.jsjavascript
// Production-grade event loop monitoring

const { performance, monitorEventLoopDelay } = require('perf_hooks');

// High-resolution event loop delay histogram
const histogram = monitorEventLoopDelay({ resolution: 20 });
histogram.enable();

// Track utilization over intervals
let previous = performance.eventLoopUtilization();

setInterval(() => {
  const current = performance.eventLoopUtilization(previous);
  previous = performance.eventLoopUtilization();

  const metrics = {
    // Ratio of time the loop spent active vs idle (0-1)
    utilization: current.utilization.toFixed(3),
    // Delay percentiles in milliseconds
    p50: (histogram.percentile(50) / 1e6).toFixed(2),
    p99: (histogram.percentile(99) / 1e6).toFixed(2),
    max: (histogram.max / 1e6).toFixed(2),
  };

  // Alert when utilization exceeds 70% or p99 > 100ms
  if (current.utilization > 0.7 || histogram.percentile(99) > 100e6) {
    console.warn('EVENT_LOOP_SATURATED', metrics);
  }

  histogram.reset();
}, 5000);

Hai chỉ số quan trọng cần theo dõi:

  • Event Loop Utilization (ELU): Tỷ lệ thời gian Event Loop đang xử lý callback so với thời gian rảnh rỗi. Giá trị từ 0 đến 1, với ngưỡng cảnh báo thường đặt tại 0.7 (70%). Khi vượt ngưỡng này, ứng dụng bắt đầu xuất hiện tình trạng tăng độ trễ phản hồi.
  • Event Loop Delay p99: Độ trễ tại phân vị thứ 99 cho biết thời gian lâu nhất mà một callback phải đợi trước khi được thực thi. Ngưỡng p99 > 100ms trong API server là dấu hiệu rõ ràng của blocking code.

Nhận diện và loại bỏ Blocking Pattern

Nguyên nhân phổ biến nhất khiến Event Loop bị chặn là việc sử dụng các API đồng bộ trong đường dẫn xử lý request. Hai anti-pattern điển hình là JSON.parse trên payload lớn và các hàm crypto đồng bộ.

blocking-patterns.jsjavascript
// Anti-patterns and their solutions

// PROBLEM: JSON.parse blocks on large payloads
const largePayload = Buffer.alloc(50 * 1024 * 1024); // 50MB
// JSON.parse(largePayload.toString()); // Blocks event loop 200-500ms

// SOLUTION: Stream-parse large JSON with a streaming parser
const { Transform } = require('stream');
const JSONStream = require('jsonstream2');

function processLargeJSON(readableStream) {
  return new Promise((resolve, reject) => {
    const results = [];
    readableStream
      .pipe(JSONStream.parse('items.*'))  // Stream-parse array items
      .on('data', (item) => results.push(item))
      .on('end', () => resolve(results))
      .on('error', reject);
  });
}

// PROBLEM: Synchronous crypto in request path
// const hash = crypto.pbkdf2Sync(password, salt, 100000, 64, 'sha512');

// SOLUTION: Use async variant
const crypto = require('crypto');
async function hashPassword(password, salt) {
  return new Promise((resolve, reject) => {
    crypto.pbkdf2(password, salt, 100000, 64, 'sha512', (err, key) => {
      if (err) reject(err);
      else resolve(key.toString('hex'));
    });
  });
}

Ngoài hai ví dụ trên, còn một số blocking pattern khác cần tránh trong production: fs.readFileSync trong route handler, các vòng lặp xử lý mảng lớn (>100.000 phần tử) đồng bộ, và regex phức tạp có khả năng gây ra catastrophic backtracking. Nguyên tắc chung là: bất kỳ thao tác nào mất hơn 10ms trên main thread đều nên được chuyển sang async hoặc Worker Thread.

Sẵn sàng chinh phục phỏng vấn Node.js / NestJS?

Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.

Clustering: Tận dụng toàn bộ CPU Core

Một process Node.js mặc định chỉ sử dụng một CPU core. Trên một máy chủ 8 core, điều này có nghĩa là 87.5% tài nguyên CPU bị lãng phí. Module cluster giải quyết vấn đề này bằng cách fork nhiều worker process, mỗi worker chạy trên một core riêng biệt và chia sẻ cùng một port lắng nghe.

Kiến trúc cluster hoạt động theo mô hình primary-worker: process chính (primary) không xử lý request mà chỉ quản lý vòng đời của các worker. Khi một request đến, hệ điều hành sẽ phân phối nó cho một trong các worker process đang lắng nghe trên cùng port.

cluster-setup.jsjavascript
// Production clustering with graceful shutdown

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

const WORKER_COUNT = parseInt(process.env.WORKERS) || os.cpus().length;

if (cluster.isPrimary) {
  console.log(`Primary ${process.pid} starting ${WORKER_COUNT} workers`);

  // Fork workers for each CPU core
  for (let i = 0; i < WORKER_COUNT; i++) {
    cluster.fork();
  }

  // Restart crashed workers automatically
  cluster.on('exit', (worker, code, signal) => {
    if (!worker.exitedAfterDisconnect) {
      console.error(`Worker ${worker.process.pid} died (${signal || code}). Restarting...`);
      cluster.fork();
    }
  });

  // Graceful shutdown on SIGTERM
  process.on('SIGTERM', () => {
    console.log('Primary received SIGTERM. Shutting down workers...');
    for (const id in cluster.workers) {
      cluster.workers[id].disconnect();
    }
  });
} else {
  // Worker process — start the actual HTTP server
  const http = require('http');
  const server = http.createServer((req, res) => {
    res.writeHead(200);
    res.end(`Handled by worker ${process.pid}\n`);
  });

  server.listen(3000, () => {
    console.log(`Worker ${process.pid} listening on port 3000`);
  });

  // Graceful shutdown for individual worker
  process.on('SIGTERM', () => {
    server.close(() => process.exit(0));
  });
}

Một số lưu ý quan trọng khi triển khai cluster:

  • Graceful shutdown: Mỗi worker cần xử lý tín hiệu SIGTERM để hoàn thành các request đang xử lý trước khi tắt. Đoạn mã trên minh họa cách thực hiện điều này bằng server.close().
  • Auto-restart: Khi một worker crash bất ngờ (không phải do disconnect có chủ đích), primary process tự động fork một worker mới để thay thế.
  • Số lượng worker: Thông thường bằng số CPU core. Tuy nhiên, đối với ứng dụng I/O-intensive, có thể tăng lên 1.5x số core. Đối với CPU-intensive, nên giữ bằng hoặc thấp hơn số core.

Worker Threads: Xử lý tác vụ nặng CPU

Cluster phù hợp cho việc nhân bản toàn bộ HTTP server, nhưng không hiệu quả cho việc offload một tác vụ CPU đơn lẻ (như nén ảnh hay xử lý video). Worker Threads giải quyết nhu cầu này bằng cách cho phép chạy JavaScript trên một thread riêng biệt mà không cần fork toàn bộ process.

Điểm khác biệt chính giữa Cluster và Worker Threads:

  • Cluster: Fork toàn bộ process, mỗi worker có bộ nhớ riêng. Phù hợp cho horizontal scaling HTTP server.
  • Worker Threads: Tạo thread mới trong cùng process, có thể chia sẻ bộ nhớ qua SharedArrayBuffer. Phù hợp cho offload tác vụ CPU-intensive.

Để sử dụng Worker Threads hiệu quả trong production, cần triển khai một Worker Pool thay vì tạo thread mới cho mỗi request:

worker-pool.jsjavascript
// Reusable worker thread pool for CPU tasks

const { Worker } = require('worker_threads');
const os = require('os');

class WorkerPool {
  constructor(workerScript, poolSize = os.cpus().length) {
    this.workers = [];
    this.queue = [];

    for (let i = 0; i < poolSize; i++) {
      this.workers.push({ busy: false, worker: new Worker(workerScript) });
    }
  }

  execute(taskData) {
    return new Promise((resolve, reject) => {
      const available = this.workers.find(w => !w.busy);

      if (available) {
        this._runTask(available, taskData, resolve, reject);
      } else {
        // Queue task until a worker is free
        this.queue.push({ taskData, resolve, reject });
      }
    });
  }

  _runTask(entry, taskData, resolve, reject) {
    entry.busy = true;
    entry.worker.postMessage(taskData);

    const onMessage = (result) => {
      entry.busy = false;
      cleanup();
      resolve(result);
      this._processQueue();
    };

    const onError = (err) => {
      entry.busy = false;
      cleanup();
      reject(err);
      this._processQueue();
    };

    const cleanup = () => {
      entry.worker.removeListener('message', onMessage);
      entry.worker.removeListener('error', onError);
    };

    entry.worker.on('message', onMessage);
    entry.worker.on('error', onError);
  }

  _processQueue() {
    if (this.queue.length === 0) return;
    const available = this.workers.find(w => !w.busy);
    if (available) {
      const { taskData, resolve, reject } = this.queue.shift();
      this._runTask(available, taskData, resolve, reject);
    }
  }
}

module.exports = { WorkerPool };

Ví dụ thực tế: sử dụng Worker Pool để xử lý ảnh bằng Sharp mà không chặn Event Loop:

image-worker.jsjavascript
// Worker thread for CPU-intensive image processing

const { parentPort } = require('worker_threads');
const sharp = require('sharp');

parentPort.on('message', async ({ inputPath, width, height }) => {
  const result = await sharp(inputPath)
    .resize(width, height)
    .webp({ quality: 80 })
    .toBuffer();

  parentPort.postMessage({ size: result.length, buffer: result });
});

Với Worker Pool, main thread chỉ cần gọi pool.execute({ inputPath, width: 800, height: 600 }) và nhận kết quả qua Promise mà không bao giờ bị chặn. Khi tất cả worker đang bận, các tác vụ mới sẽ được xếp hàng đợi tự động.

Sai lầm phổ biến với Cluster và Worker Threads

Không nên sử dụng cả Cluster lẫn Worker Threads cùng lúc một cách bừa bãi. Nếu ứng dụng đã chạy 8 cluster worker trên máy 8 core, việc thêm Worker Threads trong mỗi worker sẽ dẫn đến over-subscription — tổng số thread vượt quá số core vật lý, gây context switching overhead và giảm hiệu năng thay vì tăng. Công thức tối ưu: số cluster worker + số worker thread trên mỗi cluster PHẢI nhỏ hơn hoặc bằng tổng số CPU core.

Tối ưu bộ nhớ và giảm áp lực Garbage Collector

Garbage Collector (GC) của V8 hoạt động trên main thread, có nghĩa là mỗi chu kỳ GC sẽ tạm dừng Event Loop. Các ứng dụng tạo ra nhiều object tạm thời trong vòng lặp nóng (hot loop) sẽ gây ra GC pause thường xuyên, làm tăng độ trễ p99 một cách đáng kể.

memory-optimization.jsjavascript
// Patterns that reduce GC pressure

// ANTI-PATTERN: Creating objects in hot loops
function processItemsBad(items) {
  return items.map(item => ({
    id: item.id,
    name: item.name.trim(),
    score: calculateScore(item),  // New object per iteration
    metadata: { processed: true, timestamp: Date.now() }
  }));
}

// OPTIMIZED: Reuse buffers and minimize allocations
const reusableBuffer = Buffer.alloc(4096);

function processItemsGood(items, output) {
  // Reuse the output array instead of creating new one
  output.length = 0;
  for (let i = 0; i < items.length; i++) {
    // Mutate in place when safe to do so
    output.push(items[i].id);
  }
  return output;
}

// Monitor heap usage for leak detection
function checkMemory() {
  const used = process.memoryUsage();
  return {
    heapUsedMB: Math.round(used.heapUsed / 1024 / 1024),
    heapTotalMB: Math.round(used.heapTotal / 1024 / 1024),
    externalMB: Math.round(used.external / 1024 / 1024),
    rsssMB: Math.round(used.rss / 1024 / 1024),
  };
}

// V8 flags for production GC tuning
// node --max-old-space-size=4096 --max-semi-space-size=128 app.js
// --max-old-space-size: Set old generation limit (default ~1.7GB)
// --max-semi-space-size: Increase young generation (default 16MB)

Các chiến lược giảm áp lực GC trong production:

  • Tái sử dụng Buffer: Thay vì tạo Buffer mới cho mỗi request, sử dụng Buffer pool với kích thước cố định.
  • Tránh closure không cần thiết: Mỗi closure tạo ra một object trên heap. Trong hot path, sử dụng plain function thay vì closure.
  • Object pooling: Đối với các object được tạo và hủy thường xuyên (như connection object hay parser state), sử dụng pool để tái sử dụng.
  • Cấu hình V8 flags: --max-old-space-size để tăng giới hạn heap cho ứng dụng cần nhiều bộ nhớ, --max-semi-space-size để tăng kích thước young generation giúp giảm tần suất minor GC.

Ma trận quyết định: Khi nào dùng giải pháp nào

Việc chọn đúng chiến lược tối ưu phụ thuộc vào đặc điểm cụ thể của ứng dụng. Bảng dưới đây tổng hợp các tình huống phổ biến:

| Tình huống | Giải pháp | Lý do | |---|---|---| | API server cần xử lý nhiều request đồng thời | Cluster (N worker = N core) | Nhân bản toàn bộ server để tận dụng multi-core | | Xử lý ảnh/video trong request path | Worker Threads + Pool | Offload CPU-intensive task không chặn Event Loop | | JSON.parse payload > 10MB | Stream parsing (JSONStream) | Tránh blocking main thread với thao tác đồng bộ | | Crypto operations (hash, encrypt) | Async API (crypto.pbkdf2) | Phiên bản async chạy trên libuv thread pool | | Event Loop utilization > 70% | Profiling + Code splitting | Tìm và loại bỏ blocking code, chia nhỏ tác vụ | | Memory leak nghi ngờ | Heap snapshot + --inspect | Phân tích heap để tìm object không được giải phóng | | Microservice với traffic cao | Cluster + Horizontal scaling | Kết hợp vertical (cluster) và horizontal (nhiều instance) | | Background job nặng (ETL, report) | Dedicated Worker Thread | Tách hẳn khỏi HTTP server thread |

Cải tiến hiệu năng trong Node.js 24

Node.js 24 (LTS từ tháng 10/2025) mang đến V8 engine 13.6 với Maglev JIT compiler được cải tiến, giúp tăng throughput lên 10-15% cho các workload tính toán. Require scope được cải thiện giúp giảm thời gian khởi động cold start. Module node:diagnostics_channel được mở rộng hỗ trợ tracing tốt hơn cho async context. Đặc biệt, API navigator.hardwareConcurrency giúp xác định chính xác số CPU core khả dụng trong container environment.

Giám sát hiệu năng với OpenTelemetry

OpenTelemetry đã trở thành tiêu chuẩn de facto cho observability trong hệ sinh thái Node.js. Việc tích hợp OpenTelemetry cho phép thu thập metrics, traces và logs một cách thống nhất, xuất dữ liệu sang Prometheus, Grafana, Jaeger hoặc bất kỳ backend nào hỗ trợ OTLP.

otel-setup.jsjavascript
// OpenTelemetry setup for Node.js performance monitoring

const { NodeSDK } = require('@opentelemetry/sdk-node');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const { PrometheusExporter } = require('@opentelemetry/exporter-prometheus');
const { PeriodicExportingMetricReader } = require('@opentelemetry/sdk-metrics');

const sdk = new NodeSDK({
  metricReader: new PrometheusExporter({ port: 9464 }),
  instrumentations: [
    getNodeAutoInstrumentations({
      // Instrument HTTP, Express, DNS, fs, and more
      '@opentelemetry/instrumentation-fs': { enabled: false }, // Too noisy
    }),
  ],
});

sdk.start();

// Custom event loop lag metric
const { metrics } = require('@opentelemetry/api');
const meter = metrics.getMeter('app');

const eventLoopLag = meter.createHistogram('nodejs.event_loop.lag', {
  description: 'Event loop lag in milliseconds',
  unit: 'ms',
});

// Report event loop lag every second
const { monitorEventLoopDelay } = require('perf_hooks');
const h = monitorEventLoopDelay({ resolution: 10 });
h.enable();

setInterval(() => {
  eventLoopLag.record(h.percentile(99) / 1e6);
  h.reset();
}, 1000);

Đoạn mã trên thiết lập auto-instrumentation cho HTTP, Express, DNS và nhiều module khác, đồng thời tạo một custom metric để theo dõi Event Loop lag. Metrics được expose qua endpoint Prometheus trên port 9464, sẵn sàng để Grafana scrape và hiển thị dashboard.

Các metric quan trọng cần giám sát trong production Node.js:

  • nodejs.event_loop.lag (p99): Phải dưới 100ms cho API server
  • nodejs.event_loop.utilization: Phải dưới 0.7 trong điều kiện bình thường
  • nodejs.gc.duration: Tổng thời gian GC pause, nên dưới 50ms/giây
  • process.heap.used: Xu hướng tăng liên tục là dấu hiệu memory leak
  • http.server.duration (p99): Thời gian phản hồi HTTP, mục tiêu phụ thuộc SLA

Danh sách kiểm tra tối ưu hiệu năng Node.js

Dưới đây là danh sách kiểm tra thực tế cho việc tối ưu một ứng dụng Node.js trong production:

Event Loop:

  • Không sử dụng API đồng bộ (*Sync) trong request path
  • Stream parsing cho JSON payload > 5MB
  • Async crypto cho mọi thao tác mã hóa trong hot path
  • Giám sát ELU và Event Loop delay với ngưỡng cảnh báo rõ ràng
  • Sử dụng setImmediate để chia nhỏ vòng lặp xử lý mảng lớn

Clustering và Scaling:

  • Sử dụng Cluster module hoặc PM2 cluster mode trên multi-core server
  • Triển khai graceful shutdown cho mọi worker process
  • Auto-restart worker khi crash không mong muốn
  • Đảm bảo tổng thread (cluster + worker threads) không vượt quá số CPU core

Memory:

  • Cấu hình --max-old-space-size phù hợp với RAM khả dụng
  • Giám sát heap usage và thiết lập cảnh báo khi heap vượt 80% giới hạn
  • Tránh tạo object tạm trong hot loop — sử dụng object pooling
  • Định kỳ chạy heap snapshot để phát hiện memory leak sớm

Observability:

  • Tích hợp OpenTelemetry cho metrics, traces và logs
  • Dashboard Grafana với các metric Node.js core (ELU, GC, heap, HTTP latency)
  • Alerting cho Event Loop saturation và memory threshold
  • Distributed tracing cho microservice architecture

Bắt đầu luyện tập!

Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.

Kết luận

Hiệu năng Node.js không phải là phép thuật — nó là kết quả của việc hiểu rõ cơ chế hoạt động nội bộ và áp dụng đúng công cụ cho đúng tình huống. Event Loop là nền tảng cần nắm vững trước tiên: hiểu các phase, biết phân biệt microtask và macrotask, và nhận diện được blocking pattern. Clustering giúp tận dụng toàn bộ tài nguyên phần cứng, trong khi Worker Threads cho phép offload tác vụ CPU-intensive mà không ảnh hưởng đến khả năng phản hồi của server. Cuối cùng, observability với OpenTelemetry đảm bảo rằng mọi vấn đề hiệu năng đều được phát hiện và xử lý kịp thời.

Để tìm hiểu sâu hơn về Node.js và chuẩn bị cho phỏng vấn kỹ thuật, tham khảo lộ trình Node.js & NestJS trên SharpSkill. Module middleware và interceptors cung cấp kiến thức nâng cao về kiến trúc NestJS. Ngoài ra, hướng dẫn phỏng vấn backend Node.js tổng hợp các câu hỏi thường gặp nhất trong các buổi phỏng vấn kỹ thuật năm 2026.

Thẻ

#node.js
#performance
#event-loop
#clustering
#optimization
#worker-threads

Chia sẻ

Bài viết liên quan