LabHub
배우기 러닝패스 코스

요청 하나가 아니라 전부가 느려졌다 · 배압과 스트림 · 실습

쌓이는 바이트를 직접 잰다

LabHub 에서 이어서 보기

목표

빠른 생산자와 느린 소비자 사이에서 메모리가 어떻게 늘어나는지를 직접 재고,
write() 의 반환값과 drain 이벤트가 무엇을 막아 주는지 확인합니다.

왜 중요한가

"메모리 누수" 로 신고되는 것의 상당수는 새는 것이 아니라 줄이 서 있는
것입니다. 초당 10MB 를 만들어 내는 쪽과 초당 2MB 를 받아 가는 쪽을 그냥 이어
두면, 남는 8MB 는 어딘가에 쌓여야 하고 그 어딘가가 이 프로세스의 힙입니다.

Node 의 스트림은 이것을 막을 손잡이를 하나 줍니다 — write() 의 반환값입니다.
그런데 그 값은 그냥 두면 아무 일도 하지 않습니다. **읽고 멈춰 주는 것은 쓰는
쪽의 몫입니다.** 그 한 줄이 빠지면 코드는 멀쩡히 돌고, 시험도 통과하고, 운영에
나가서 큰 파일 하나에 프로세스가 죽습니다.

그리고 배압을 지키면 느려질 것 같지만, 재 보면 그렇지 않습니다. 소비자가 받는
속도는 어차피 같기 때문입니다. 잃는 것은 거의 없고 막는 것은 큽니다.

단계

1. /root/work/backpressure/flow.mjsmakeSink(options) 를 만들고,
이 판의 기본 highWaterMark 를 재어 report.jsonhwm 에 적습니다.
2. fillUntilFalse(stream, chunk) 로 버퍼가 차는 지점을 셉니다.
3. writeAll(stream, chunks) 로 배압을 지키며 씁니다.
4. floodNoWait(stream, chunks) 로 반환값을 무시하고 부어 runs.flood 에 적습니다.
5. 같은 양을 writeAll 로 보내 runs.paced 에 적고 backpressure 에 비율을 적습니다.
6. pipeThrough(readable, writable) 로 같은 일을 하고 runs.pipeline 에 적습니다.
7. estimateQueueBytessecondsUntil 로 쌓이는 속도를 계산합니다.

참고

단계 7개

  1. 이 판의 기본값을 직접 잰다
  2. write() 가 false 를 내는 지점
  3. 멈추고, drain 을 기다린다
  4. 반환값을 무시하면 어디까지 쌓이는가
  5. 같은 양을 배압을 지켜 보낸다
  6. 실패했을 때 누가 치우는가
  7. 몇 초 뒤에 터지는가