대용량 데이터를 처리하는 지혜: Buffer & Stream
2GB짜리 영화 파일을 메모리 1GB 서버에서 전송하려면 어떻게 해야 할까요? 전체를 한 번에 올리면 메모리 초과(OOM)로 서버가 죽습니다. 이럴 때 데이터를 작게 잘라(Buffer) 흐르게(Stream) 만드는 기술이 필수적입니다.
Buffer (버퍼)란?
버퍼는 0과 1로 이루어진 바이너리(이진) 데이터를 임시로 담아두는 고정 크기의 메모리 덩어리입니다. V8 엔진(자바스크립트)이 아닌 C++ 레벨에서 할당된 메모리 공간을 나타내며, 유튜브 영상의 '버퍼링 중입니다...' 할 때 그 임시 공간을 의미합니다.
Stream (스트림)의 4가지 종류
| 종류 |
설명 |
예시 |
| Readable Stream |
데이터를 읽어들이기만 하는 스트림 (수도꼭지에서 물이 나오는 것) |
fs.createReadStream() |
| Writable Stream |
데이터를 쓰기만 하는 스트림 (하수구로 물이 빠져나가는 것) |
fs.createWriteStream() |
| Duplex Stream |
읽기와 쓰기가 동시에 가능한 양방향 스트림 |
TCP Socket |
| Transform Stream |
읽은 데이터를 쓰기 전에 변환(압축, 암호화)하는 특별한 스트림 |
zlib.createGzip() |
💡 최고의 궁합: Pipe (.pipe)
스트림끼리 연결할 때는 파이프를 연결하듯 readStream.pipe(writeStream) 형태로 작성합니다. 이렇게 하면 Node.js가 알아서 데이터를 읽고 쓰는 속도(Backpressure)를 조절해주어 메모리가 터지는 것을 방지해줍니다.
Stream 데이터 파이프라인 전송
거대한 1GB 동영상 파일을 메모리에 한 번에 다 올리지 않고, 조각(Chunk/Buffer) 단위로 쪼개어 물 흐르듯 전송하는 Stream의 강력함을 확인하세요.
const fs = require('fs');
// 메모리가 1GB밖에 없는 서버라도 스트림을 사용하면 대용량 파일도 거뜬합니다!
console.log('--- Stream 파이프라인 전송 시작 ---');
// 1. 읽기 스트림(수도꼭지) 생성 (기본 단위: 64KB)
const readStream = fs.createReadStream('big-video.mp4');
// 2. 쓰기 스트림(하수구) 생성
const writeStream = fs.createWriteStream('big-video-copy.mp4');
let chunkCount = 0;
// 데이터가 단위 크기(Chunk)만큼 쪼개져서 들어올 때마다 발생하는 이벤트
readStream.on('data', (chunk) => {
chunkCount++;
console.log(`> 데이터 청크(Chunk) 수신 완료! (진행: ${chunkCount}번째, 크기: ${chunk.length} bytes)`);
console.log(`> 파일에 쓰는 중... (메모리 점유율 최소 유지)`);
});
// 모든 데이터를 다 읽었을 때 발생하는 이벤트
readStream.on('end', () => {
console.log('\n✅ 전송 완료! (이벤트: readStream.on("end"))');
console.log('(설명: 파일 전체를 메모리에 올리지 않아 서버가 안전합니다)');
});
// 스트림을 잇는 가장 완벽한 방법 (파이프 연결)
// 위 동작을 내부적으로 자동 처리하며 읽고 쓰는 속도(Backpressure)를 알아서 조절합니다.
readStream.pipe(writeStream);
Terminal (Node.js Execution)
$ node stream.js
--- Stream 파이프라인 전송 시작 ---
> 데이터 청크(Chunk) 수신 완료! (진행: 1번째, 크기: 65536 bytes)
> 파일에 쓰는 중... (메모리 점유율 최소 유지)
> 데이터 청크(Chunk) 수신 완료! (진행: 2번째, 크기: 65536 bytes)
> 파일에 쓰는 중... (메모리 점유율 최소 유지)
> 데이터 청크(Chunk) 수신 완료! (진행: 3번째, 크기: 65536 bytes)
> 파일에 쓰는 중... (메모리 점유율 최소 유지)
... (반복) ...
✅ 전송 완료! (이벤트: readStream.on("end"))
(설명: 파일 전체를 메모리에 올리지 않아 서버가 안전합니다)