개인의 기록
  • 소개
  • 프로젝트
  • 글
  • 링크

© 2026 newgirok

← 글 목록

Express 성능 최적화 — 압축, 캐시, 클러스터링

2025년 2월 13일
Express성능 최적화CompressionCacheCluster

Node.js 단일 스레드 특성 때문에 트래픽이 몰리면 Express 서버는 금방 병목을 드러낸다. 응답 크기를 줄이고, 중복 연산을 캐시하고, CPU 코어를 전부 쓰는 세 가지 축만 잡아도 처리량이 수 배 뛴다.

Compression — 응답 크기 줄이기

compression 미들웨어는 HTTP 응답을 gzip / Brotli로 압축해 전송 바이트를 줄인다. 설정 한 줄로 JSON·HTML 응답을 평균 60~80% 압축할 수 있다.

import express from "express";
import compression from "compression";

const app = express();

app.use(
  compression({
    level: 6,          // 1(빠름)~9(고압축), 기본 6
    threshold: 1024,   // 1KB 미만은 압축 생략
  })
);

gzip — DEFLATE 알고리즘 기반 압축 포맷. 대부분의 브라우저와 HTTP 클라이언트가 Accept-Encoding: gzip을 보내므로 별도 협상 없이 동작한다.

프로덕션에서는 Express 앞단의 Reverse Proxy(Nginx)에서 압축을 처리하는 편이 CPU 부하를 분리할 수 있어 더 효율적이다.

Client ──► Nginx (gzip) ──► Express ──► DB
               ↑
         압축은 여기서

Cache-Control 헤더 — 중복 요청 차단

정적 파일은 Cache-Control 헤더로 브라우저와 CDN이 캐시하게 만든다.

app.use(
  express.static("public", {
    maxAge: "7d",          // 브라우저 캐시 7일
    etag: true,            // 조건부 요청용 ETag 자동 생성
    lastModified: true,
  })
);

API 응답처럼 동적 데이터는 ETag 기반 조건부 요청으로 304를 돌려줘 바디 전송을 생략한다.

app.get("/api/products", (req, res) => {
  const data = getProducts();
  const etag = generateETag(data);

  if (req.headers["if-none-match"] === etag) {
    return res.status(304).end();
  }

  res.setHeader("ETag", etag);
  res.setHeader("Cache-Control", "public, max-age=60");
  res.json(data);
});

ETag — 리소스 버전을 나타내는 불투명 토큰. 서버가 값을 바꾸지 않으면 클라이언트는 캐시된 데이터를 재사용한다.

헤더용도예시 값
Cache-Control캐시 정책 선언public, max-age=3600
ETag리소스 버전 식별"abc123"
Last-Modified수정 시각 기준 캐시Wed, 23 Aug 2026 00:00:00 GMT

Cluster 모드 — CPU 코어 전부 활용

Node.js는 기본적으로 단일 코어만 쓴다. cluster 모듈로 Worker 프로세스를 CPU 수만큼 띄우면 병렬 처리가 가능하다.

import cluster from "cluster";
import os from "os";

if (cluster.isPrimary) {
  const cpus = os.cpus().length;
  for (let i = 0; i < cpus; i++) cluster.fork();
  cluster.on("exit", () => cluster.fork()); // 재시작 보장
} else {
  // 각 Worker가 독립적으로 Express 실행
  const { app } = await import("./app.js");
  app.listen(3000);
}

PM2를 쓰면 코드 수정 없이 같은 효과를 얻는다.

# ecosystem.config.yaml
apps:
  - name: api
    script: dist/server.js
    instances: max      # CPU 수만큼 자동
    exec_mode: cluster
    watch: false
    env:
      NODE_ENV: production
pm2 start ecosystem.config.yaml
pm2 monit   # 실시간 CPU/메모리 확인

PM2 — Node.js 프로세스 매니저. Cluster 모드, 무중단 재시작(graceful reload), 로그 수집을 한 번에 제공한다.

성능 측정

최적화 전후를 수치로 확인해야 방향이 보인다. autocannon으로 로컬에서 빠르게 부하 테스트를 돌린다.

npx autocannon -c 100 -d 10 http://localhost:3000/api/products
# -c: 동시 연결 수, -d: 테스트 시간(초)

압축 → 캐시 → 클러스터 순으로 하나씩 적용하고 RPS(Requests Per Second)와 레이턴시 p99를 기록하면 각 기법의 실제 기여도를 분리해서 볼 수 있다.

← 이전 글Express 보안 — XSS, CSRF, SQL Injection 방어
다음 글 →NestJS란? — 구조화된 Node.js 백엔드 프레임워크