문제 발생
큰 파일을 전송할 때 CPU는 높지 않은데 RSS가 계속 증가합니다. 생산자가 디스크나 socket 소비 속도보다 빠르게 chunk를 만들고 write()가 false를 반환해도 계속 쓰면 내부 buffer가 쌓입니다.
Writable이 false를 반환하면 producer는 drain을 기다려야 하며 pipeline은 error propagation과 cleanup을 함께 처리합니다.
원인 분석
highWaterMark는 전체 메모리 상한이 아니라 backpressure가 필요하다는 신호를 내기 위한 buffer 기준입니다. writable.write()가 false를 반환하면 'drain' 전까지 추가 쓰기를 멈춰야 합니다. 한 번의 heap snapshot보다 전송량을 늘릴 때 writableLength, RSS와 GC가 어떻게 변하는지 봅니다.
해결 방안
읽기와 쓰기를 연결할 수 있다면 pipeline()이 backpressure와 오류 전파를 함께 다루므로 직접 event를 조합하는 것보다 단순합니다.
import { createReadStream, createWriteStream } from "node:fs";
import { pipeline } from "node:stream/promises";
await pipeline(
createReadStream("large-input.csv"),
createWriteStream("copy.csv"),
);
직접 write()해야 한다면 반환값이 false일 때 'drain'을 기다립니다. 정상 완료, 대상 쓰기 오류와 중간 취소에서 file descriptor가 닫히는지 확인하고, 파일 크기를 키워도 RSS가 입력 전체 크기에 비례해 증가하지 않는지 검증합니다.
공식 문서
현재 Node.js version의 stream API와 실제 source·destination 속도에서 확인합니다.