빠른 흐름
import { createReadStream, createWriteStream } from "node:fs";
import { createGzip } from "node:zlib";
import { pipeline } from "node:stream/promises";
// 스트림 기반 — 대용량 파일도 메모리 부담 없이 처리
await pipeline(
createReadStream("app.log"),
createGzip(), // 기본 압축 레벨 6
createWriteStream("app.log.gz"),
);압축 흐름
먼저 머릿속에 들어와야 하는 기본형은 아래입니다.
- 범용 HTTP 압축: gzip
- 선압축 정적 자산: brotli 검토
- 작은 버퍼 압축: promise 기반 API
- 큰 파일/응답 압축:
pipeline(...) - 압축 여부 결정:
Accept-Encoding확인
HTTP 응답이면 기본값은 gzip이다
호환성과 운영 단순성을 같이 보면 HTTP 응답 압축은 gzip이 가장 안전한 기본값입니다. brotli는 압축률이 더 좋지만 실시간 생성 비용을 같이 봐야 합니다.
정적 파일 선압축이면 brotli를 검토한다
빌드 시점에 미리 압축해 둘 수 있는 자산이라면 brotli가 더 좋은 선택이 될 수 있습니다. 요청 시점마다 CPU를 쓰는 게 아니라면 압축률 이점을 살리기 쉽습니다.
큰 데이터는 스트림으로 압축한다
파일, 로그, 대용량 응답처럼 메모리에 한 번에 올리기 싫은 경우는 pipeline()이 기본입니다. 압축 카드에서 사실 제일 실전적인 기준은 "작은 버퍼냐, 스트림이냐"입니다.
헤더와 실제 압축 본문은 같이 움직여야 한다
Content-Encoding: gzip만 보내고 본문을 실제로 압축하지 않으면 클라이언트가 바로 깨집니다. 반대로 압축을 했는데 헤더를 안 주면 클라이언트는 평문으로 읽으려다 실패합니다.
압축률보다 CPU 비용을 같이 본다
brotli가 더 잘 줄어드는 경우가 많아도, 요청마다 실시간 압축할 때는 CPU 비용이 먼저 문제일 수 있습니다. 그래서 응답 압축 카드는 알고리즘 카드이면서 동시에 "언제 선압축으로 넘길 것인가" 카드이기도 합니다.
언제 압축할까
체크포인트
- 범용 HTTP 응답: gzip
- 선압축 정적 파일: brotli 검토
- 작은 메모리 버퍼: promise 기반 gzip
- 큰 파일/스트림:
pipeline(...) - 실시간 압축 CPU 절약: 낮은 level
- 지원 안 하는 클라이언트: 압축 없이 응답
- 요청 시점 CPU가 아깝다: 선압축 정적 자산 검토
주의할 점
압축 헤더와 압축 본문이 어긋나면 응답 전체가 깨진다. Accept-Encoding 확인, Content-Encoding 설정, 실제 압축 스트림 연결을 한 묶음으로 생각해야 한다.
참고 링크
2 sources