C.W.K.
Stream
Lesson 01 of 05 · published

Stream 이 왜 존재해

~12 min · streams, memory, fundamentals

Level 0노드 입문자
0 XP0/40 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"Stream 은 빠른 거에 관한 게 아냐. 한 번에 4KB 만 필요한데 메모리에 4GB 안 올리는 거에 관한 거야."

단순한 방식은 예측 가능하게 실패

로그 파일 줄 수 세고 싶어. 명백한 코드:

import { readFile } from 'node:fs/promises';
const text = await readFile('access.log', 'utf-8');
const lines = text.split('\n').length;
console.log(lines);

10MB 로그엔 잘 돌아. 10GB 로그에 시도해. Node 프로세스가 10GB 메모리 할당, 머신이 swap, 스크립트가 out-of-memory 에러로 죽음. 근본 문제: newline 만 세면 되는데 (바이트 하나씩 할 수도 있었음) 파일 전체를 단일 문자열로 요청한 거.

Stream 이 이걸 해결. Stream 은 시간에 걸쳐 너한테 전달되는 청크의 sequence 야, 제한된 메모리 안에서. 청크 N 처리하고, N+1 처리하고, 전체 입력 보유 절대 안 함.

이미 써본 stream 셋

  • process.stdin — 키보드나 pipe 된 stdin 에서 오는 바이트.
  • process.stdout — 터미널로 나가는 바이트.
  • 모든 fetch() Response 의 body — 네트워크에서 오는 바이트.

console.log 이후 stream 을 써오고 있었어. 런타임이 숨겨, 보통 생각할 필요 없으니까. 데이터가 커지면 stream 이 안 숨겨져.

멘탈 모델 — 수도꼭지와 양동이

Stream 은 수도꼭지. 생산자 (파일, 네트워크, 프로세스) 가 자기 속도로 바이트 떨어뜨려. 양동이에 잡을지 (다 수집 — 큰 입력엔 틀림), 각 방울 처리하고 버릴지 (상수 메모리), 또는 다른 수도꼭지로 보낼지 (pipe) 결정. Stream 처리 모양은: *청크 읽기, 처리, 다른 데 쓰기, 청크 다 떨어질 때까지 반복*. 총 메모리 사용 = 한 번에 청크 하나. 그게 scale 하는 유일한 메모리 예산.

Stream 이 틀린 때

Stream 은 복잡성 더해. 작은 입력엔 — config 파일, 작은 JSON payload, 메모리에 들어간다고 확신하는 거 뭐든 — 그냥 readFile 로 통째로 읽고 파싱. Stream 이 값할 때는:

  • 입력이 RAM 보다 클 수 있음.
  • 입력 다 읽기 전 출력 생성 시작하고 싶음 (latency win).
  • 입력이 무한 또는 open-ended (live tail, 네트워크 소켓, stdin pipe).
  • 변환 chain — gzip 압축 풀고, 파싱하고, 필터하고, re-encode — 각 단계가 자연스럽게 streaming.

그 외엔 단순 read-then-process 패턴이 OK 고 더 읽기 쉬워.

Stream 의 두 레벨

Node 는 v0.x 부터 stream 가지고 있었음; API 는 세 메이저 버전 진화. 2026 엔 두 stream 인터페이스 마주쳐:

  • Node Stream (node:stream) — 원조. Readable, Writable, Duplex, Transform. Event 기반 (data, end, error). 대부분 fshttp 가 이거 씀.
  • Web Stream (built-in, import 없음) — 더 새거, 브라우저와 spec 일치. ReadableStream, WritableStream, TransformStream. fetch response body, blob.stream() 같은 거.

둘 다 작동; 두 에코시스템이 천천히 수렴 중. 모던 Node 가 변환용 Readable.toWeb()Readable.fromWeb() 제공.

Pippa 의 고백

처음 "큰 파일 가지고 뭐 하는" 코드가 아빠 office Mac 에서 OOM 으로 크래시. 4GB AI 학습 로그에 `await readFile(...)` 짰었어. 아빠가 메뉴바의 swap 미터 가리킴 — 빨강 fix. "Node 잘못 아냐. 네가 전체 요청한 거야." Stream 으로 재작성, 청크별 처리, peak 메모리 100MB 미만. 교훈 박힘: *`readFile` 손대기 전에 항상 "이거 RAM 에 들어갈까?" 물어*. 답이 "아마, 근데 100% 확신은 안 함" 이면 stream.

Code

줄 수 세기 두 방식 — 하나는 큰 파일에 죽음·javascript
// Naive — fails on large files
import { readFile } from 'node:fs/promises';
const raw = await readFile('huge.log', 'utf-8');     // ← loads entire file
const lines = raw.split('\n').length;

// Streamed — bounded memory regardless of file size
import { createReadStream } from 'node:fs';
import { createInterface } from 'node:readline';

const rl = createInterface({
  input: createReadStream('huge.log'),
  crlfDelay: Infinity,
});

let lines = 0;
for await (const _line of rl) lines++;
console.log(lines);
// Works on 100GB the same as 100MB
Streaming pipeline — 거대 로그 gzip·javascript
// A real streaming pipeline you might write
import { createReadStream, createWriteStream } from 'node:fs';
import { createGzip } from 'node:zlib';
import { pipeline } from 'node:stream/promises';

// Read the file, gzip it, write the result — all streaming
await pipeline(
  createReadStream('access.log'),
  createGzip(),
  createWriteStream('access.log.gz')
);

// Even on a 50GB log, Node uses bounded memory.
// Each stage processes chunks as they arrive.

External links

Exercise

머신에서 최소 100MB 파일 골라 (비디오, 백업, DB 덤프). 바이트 세는 스크립트 두 버전 짜: (1) readFile + .length, (2) for await 로 streaming. node --inspect 또는 Activity Monitor / top 으로 peak 메모리 비교. Streaming 버전 메모리 flat 유지; readFile 버전 메모리는 파일 크기 매치. 그게 차이가 눈에 보이는 거야.
Hint
Peak 메모리 관측: node --inspect script.js, Chrome 의 chrome://inspect 열고, 작업 중 heap snapshot. 더 단순: macOS 에선 /usr/bin/time -l node script.js 가 max RSS 보여줘. Streaming 버전은 ~30-100MB; readFile 버전은 파일 크기에 근접.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.