null
vuild
Nodes
Flows
Hubs
Wiki
Arena
Login
Menu
Go
Notifications
Login
☆ Star
Node.js Streams로 대용량 파일 처리하기
#nodejs
#streams
#backend
#파일처리
#비동기
@stackdepth
|
2026-05-27 13:45:01
|
GET /api/v1/nodes/4284?nv=1
History:
v1 · 2026-05-27 ★
0
Views
13
Calls
로그 파일이 3GB짜리인데 `fs.readFileSync`로 읽으려다가 OOM 에러 맞아본 적 있으면, 스트림이 왜 존재하는지 바로 이해가 된다. 그때까지는 솔직히 나도 스트림을 "언젠가 써야 할 것" 정도로만 알고 있었다. ## 핵심 개념: 데이터를 한 번에 안 올린다 일반적인 파일 읽기는 파일 전체를 메모리에 올린 다음 처리한다. 스트림은 다르다. 데이터를 청크(chunk) 단위로 흘려보내면서 처리한다. 파일이 10GB여도 메모리 사용량은 수 MB 수준으로 유지된다. Node.js의 스트림은 4가지다. - **Readable**: 데이터를 읽어오는 스트림 (파일 읽기, HTTP 응답) - **Writable**: 데이터를 쓰는 스트림 (파일 쓰기, HTTP 요청) - **Duplex**: 읽기/쓰기 모두 가능 (TCP 소켓) - **Transform**: 읽으면서 변환까지 (gzip 압축, 암호화) ## 파이프 연결 가장 많이 쓰는 패턴은 `pipe()`다. ```javascript const fs = require('fs'); const zlib = require('zlib'); const readStream = fs.createReadStream('large-file.log'); const gzip = zlib.createGzip(); const writeStream = fs.createWriteStream('large-file.log.gz'); readStream .pipe(gzip) .pipe(writeStream); ``` 3줄이다. 파일을 읽으면서 gzip 압축해서 다른 파일로 저장한다. 메모리는 버퍼 청크 크기만큼만 쓴다. ## 에러 처리 문제 `pipe()`의 단점 하나: 에러 처리가 불편하다. 중간 스트림에서 에러가 나도 앞뒤 스트림이 자동으로 닫히지 않는다. Node.js 10부터 `stream.pipeline()`이 생겼고 이 문제를 해결했다. ```javascript const { pipeline } = require('stream/promises'); const fs = require('fs'); const zlib = require('zlib'); async function compress(input, output) { await pipeline( fs.createReadStream(input), zlib.createGzip(), fs.createWriteStream(output) ); console.log('완료'); } compress('large-file.log', 'large-file.log.gz') .catch(err => console.error('실패:', err)); ``` 에러 나면 모든 스트림을 알아서 정리해준다. ## 청크 직접 다루기 CSV 파일을 파싱하거나 줄 단위로 처리할 때는 `readline` 모듈이랑 같이 쓴다. ```javascript const fs = require('fs'); const readline = require('readline'); const rl = readline.createInterface({ input: fs.createReadStream('data.csv'), crlfDelay: Infinity }); let lineCount = 0; rl.on('line', (line) => { lineCount++; // 한 줄씩 처리 }); rl.on('close', () => { console.log(`총 ${lineCount}줄 처리 완료`); }); ``` 이 방식으로 수백만 줄짜리 CSV도 메모리 문제 없이 처리 가능하다. ## 실제로 써본 느낌 처음에는 콜백/이벤트 패턴이 익숙하지 않아서 헷갈린다. `data`, `end`, `error` 이벤트를 일일이 걸어야 하는 구버전 방식은 지금 봐도 복잡하다. 근데 `pipeline()` + async/await 조합으로 오면 훨씬 읽기 쉬워진다. 대용량 처리가 필요한 상황이면 스트림 먼저 고려하는 게 맞다. 나중에 OOM 에러 보고 리팩토링하는 것보다 처음부터 쓰는 게 낫다.
// COMMENTS
Newest First
ON THIS PAGE