메인 콘텐츠로 건너뛰기
압축은 네트워크 성능을 크게 개선하지만, 잘못 사용하면 오히려 성능이 저하될 수 있습니다. 이 문서는 압축을 효과적으로 사용하는 최적화 전략을 설명합니다.

압축의 트레이드오프

압축은 CPU 시간네트워크 시간을 교환합니다.

손익분기점

// 압축 없이
 시간 = 네트워크 시간
       = 100KB ÷ 1MB/s = 100ms

// 압축 사용
 시간 = 압축 시간 + 네트워크 시간 + 압축 해제 시간
       = 5ms + (10KB ÷ 1MB/s) + 2ms
       = 5ms + 10ms + 2ms = 17ms

이득 = 100ms - 17ms = 83ms (83% 개선)
결론: 네트워크가 느릴수록, 데이터가 클수록 압축 효과 큼

최적의 Threshold 찾기

Threshold는 압축할 최소 크기입니다.

Threshold별 효과

크기Threshold: 256Threshold: 1024Threshold: 4096
100B압축 ❌ (오버헤드)압축 ❌압축 ❌
500B압축 ⚠️ (효과 작음)압축 ❌압축 ❌
2KB압축 ✅압축 ✅압축 ❌
10KB압축 ✅압축 ✅압축 ✅

권장 Threshold

compress: {
  threshold: 256,
  encodings: ["br", "gzip", "deflate"],
}
적합한 경우:
  • 모바일 네트워크 (느린 속도)
  • 대역폭 비용이 비쌈
  • 대부분 응답이 작음 (< 10KB)
단점: CPU 사용량 증가

실험으로 최적값 찾기

// 테스트 설정
const thresholds = [256, 512, 1024, 2048, 4096];

for (const threshold of thresholds) {
  console.time(`threshold-${threshold}`);
  
  // 100번 요청
  for (let i = 0; i < 100; i++) {
    await fetch('/api/products', {
      headers: { 'Accept-Encoding': 'gzip' }
    });
  }
  
  console.timeEnd(`threshold-${threshold}`);
}

// 결과 분석: 가장 빠른 threshold 선택

알고리즘 선택 전략

Brotli vs Gzip

기준Brotli (br)Gzip
압축률최고 (15~20% 더 압축)중간
압축 속도느림 (CPU 사용 ↑)빠름
압축 해제 속도빠름빠름
브라우저 지원모던 브라우저모든 브라우저

사용 전략

사전 압축 → Brotli 사용
# 빌드 시 brotli로 사전 압축
brotli -q 11 bundle.js  # 최고 품질
# → bundle.js.br 생성

# 서버에서 사전 압축된 파일 제공
장점:
  • 압축 시간 걱정 없음 (미리 압축)
  • 최고 압축률 (quality 11)
  • 런타임 CPU 부하 없음

환경별 전략

const isProduction = process.env.NODE_ENV === 'production';
const isFastNetwork = process.env.NETWORK_SPEED === 'fast';

export const config: SonamuConfig = {
  server: {
    plugins: {
      compress: isProduction
        ? isFastNetwork
          ? {
              // 빠른 네트워크: 보수적
              threshold: 4096,
              encodings: ["gzip"],
            }
          : {
              // 느린 네트워크: 적극적
              threshold: 256,
              encodings: ["br", "gzip", "deflate"],
            }
        : false  // 개발: 압축 없음
    }
  }
};

캐싱과 압축 조합

압축과 캐싱을 함께 사용하면 최고의 성능을 얻습니다.
@cache({ ttl: "10m" })  // 서버 캐시
@api({
  httpMethod: 'GET',
  cacheControl: { maxAge: 60 },  // HTTP 캐시
  compress: CompressPresets.aggressive,  // 압축
})
async getProducts() {
  return this.findMany({});
}

효과

레이어별 최적화:
  1. 브라우저 캐시: 0ms (가장 빠름)
  2. CDN 캐시: 10ms (네트워크만)
  3. 서버 캐시: 50ms (압축만, DB 없음)
  4. DB 조회: 500ms (압축 + DB)

CDN과 압축

CloudFront 최적화

@api({
  httpMethod: 'GET',
  cacheControl: {
    visibility: 'public',
    maxAge: 60,        // 브라우저: 1분
    sMaxAge: 300,      // CDN: 5분
  },
  compress: {
    threshold: 1024,
    encodings: ["br", "gzip"],  // CloudFront는 brotli 지원
  }
})
async getData() {
  return this.findMany({});
}
CloudFront 설정:
  • Origin에서 압축된 응답 수신
  • CloudFront가 그대로 캐싱
  • 엣지에서 빠르게 제공

Vary 헤더 주의

압축 사용 시 Accept-Encoding에 따라 다른 응답이므로:
@api({
  cacheControl: {
    vary: ['Accept-Encoding'],  // 인코딩별 캐시 분리
  },
  compress: true,
})
async getData() {
  return this.findMany({});
}

사전 압축 (Pre-compression)

빌드 시 정적 파일을 미리 압축하면 런타임 CPU 부하가 없습니다.

빌드 스크립트

// scripts/compress-assets.ts
import { brotliCompressSync, gzipSync } from "zlib";
import { readdirSync, readFileSync, writeFileSync } from "fs";

const assetsDir = "./dist/assets";
const files = readdirSync(assetsDir);

for (const file of files) {
  if (/\.(js|css|html|json|svg)$/.test(file)) {
    const content = readFileSync(`${assetsDir}/${file}`);
    
    // Brotli (최고 압축률)
    const br = brotliCompressSync(content, {
      params: {
        [zlib.constants.BROTLI_PARAM_QUALITY]: 11  // 최고 품질
      }
    });
    writeFileSync(`${assetsDir}/${file}.br`, br);
    
    // Gzip (호환성)
    const gz = gzipSync(content, { level: 9 });
    writeFileSync(`${assetsDir}/${file}.gz`, gz);
    
    console.log(`${file}: ${content.length} → br: ${br.length}, gz: ${gz.length}`);
  }
}

서버 설정

// 사전 압축된 파일 제공
server.get('/assets/:filename', async (req, reply) => {
  const { filename } = req.params;
  const acceptEncoding = req.headers['accept-encoding'] || '';
  
  // Brotli 지원
  if (acceptEncoding.includes('br')) {
    const brFile = `./dist/assets/${filename}.br`;
    if (fs.existsSync(brFile)) {
      reply.header('Content-Encoding', 'br');
      return reply.sendFile(`${filename}.br`, { root: './dist/assets' });
    }
  }
  
  // Gzip 지원
  if (acceptEncoding.includes('gzip')) {
    const gzFile = `./dist/assets/${filename}.gz`;
    if (fs.existsSync(gzFile)) {
      reply.header('Content-Encoding', 'gzip');
      return reply.sendFile(`${filename}.gz`, { root: './dist/assets' });
    }
  }
  
  // 압축 없음
  return reply.sendFile(filename, { root: './dist/assets' });
});
장점:
  • 런타임 CPU 부하 없음
  • 최고 압축률 (quality 11)
  • 즉시 응답

성능 측정

1. 브라우저 개발자 도구

Network 탭 → 요청 선택

Size:
  1.2 KB (transferred)  ← 압축된 크기
  10.5 KB (size)        ← 원본 크기

Time:
  Waiting (TTFB): 50ms  ← 서버 처리 (압축 포함)
  Content Download: 10ms ← 네트워크 전송

2. 서버 로깅

import { performance } from "perf_hooks";

@api({
  httpMethod: 'GET',
  compress: CompressPresets.aggressive,
})
async getData() {
  const start = performance.now();
  const data = await this.findMany({});
  const dbTime = performance.now() - start;
  
  console.log(`DB: ${dbTime.toFixed(2)}ms`);
  // 압축 시간은 Fastify가 자동 처리
  
  return data;
}

3. 부하 테스트

# Apache Bench로 부하 테스트
ab -n 1000 -c 10 -H "Accept-Encoding: gzip" http://localhost:3000/api/products

# 압축 없이
ab -n 1000 -c 10 http://localhost:3000/api/products

# 결과 비교:
# - Time per request (평균 응답 시간)
# - Transfer rate (전송 속도)

실전 최적화 체크리스트

✅ 전역 압축 활성화
plugins: {
  compress: CompressPresets.default
}
✅ Threshold 설정
  • 일반: 1024 (1KB)
  • 모바일: 256 (256B)
  • 고성능: 4096 (4KB)
✅ 알고리즘 선택
  • 정적: brotli (사전 압축)
  • 동적: gzip (실시간)

일반적인 실수

피해야 할 실수:
  1. 모든 응답을 압축: 작은 응답은 비효율적
    // ❌ 잘못된 예
    threshold: 0  // 모든 응답 압축
    
    // ✅ 올바른 예
    threshold: 1024  // 1KB 이상만
    
  2. 이미 압축된 파일 재압축: 효과 없음
    // ❌ JPEG, PNG, MP4 압축
    @api({ compress: true })
    async getImage() { ... }
    
    // ✅ 압축 제외
    @api({ compress: false })
    async getImage() { ... }
    
  3. 스트리밍 압축: 지연 발생
    // ❌ SSE 압축
    @stream({ type: 'sse' })
    @api({ compress: true })
    async *streamData() { ... }
    
    // ✅ 압축 비활성화
    @api({ compress: false })
    async *streamData() { ... }
    
  4. 개발 환경에서 압축: 디버깅 어려움
    // ✅ 환경별 설정
    compress: process.env.NODE_ENV === 'production'
      ? CompressPresets.default
      : false
    
  5. Brotli를 실시간 압축: CPU 부하 높음
    // ⚠️ 주의: API 응답에 brotli
    compress: {
      encodings: ["br"]  // 느림
    }
    
    // ✅ gzip 사용
    compress: {
      encodings: ["gzip"]  // 빠름
    }
    

성능 벤치마크

실제 100KB JSON 응답 기준:
설정압축 시간전송 크기네트워크 시간총 시간
압축 없음0ms100KB1000ms1000ms
Gzip2ms14KB140ms142ms
Brotli5ms12KB120ms125ms
Pre-compressed0ms12KB120ms120ms
결론:
  • Gzip: 85% 시간 절약
  • Brotli: 87% 시간 절약
  • Pre-compressed: 88% 시간 절약 (최고)

다음 단계