Node.js stream.pipeline으로 대용량 파일 안전하게 처리하기
문제
수 GB짜리 로그 파일을 fs.readFile로 한 번에 읽으려다 메모리가 터졌다. 당연한 결과인데 매번 까먹는다.
// 이러면 파일 전체가 메모리에 올라간다
const data = await fs.promises.readFile('huge.log', 'utf-8');
해결
stream.pipeline을 쓰면 청크 단위로 읽고-변환하고-쓰는 파이프라인을 만들 수 있다. 에러 처리와 스트림 정리도 자동이다.
const { pipeline } = require('stream/promises');
const fs = require('fs');
const zlib = require('zlib');
// 대용량 파일을 gzip 압축하면서 복사
await pipeline(
fs.createReadStream('huge.log'),
zlib.createGzip(),
fs.createWriteStream('huge.log.gz')
);
줄 단위 처리가 필요하면 Transform 스트림을 끼워 넣으면 된다.
const { Transform } = require('stream');
const lineFilter = new Transform({
transform(chunk, encoding, callback) {
const lines = chunk.toString().split('\n');
const errors = lines
.filter(line => line.includes('ERROR'))
.join('\n');
callback(null, errors ? errors + '\n' : '');
}
});
await pipeline(
fs.createReadStream('huge.log'),
lineFilter,
fs.createWriteStream('errors-only.log')
);
핵심 포인트
stream/promises의pipeline은 async/await와 자연스럽게 쓸 수 있다- 중간에 에러가 나면 모든 스트림을 자동으로 destroy 해준다.
.pipe()체이닝과의 가장 큰 차이점이다 - 메모리 사용량이 파일 크기와 무관하게 일정하다. 10GB 파일이든 100GB든 상관없다