← 목차4 / 7

04

스트리밍이 죽어도 티가 나지 않는다

출력도 TTFB도 정상인데 화면만 다른 경우

왜 죽여보는가

스트리밍이 동작하는 것은 확인했습니다. 그런데 동작하지 않을 때는 어떻게 알아챌 수 있을까요.

3장에서 스트림에 개입하는 구조를 만들었습니다. 이 구조를 일부러 잘못 쓰면 스트리밍이 죽습니다. 그러면 무엇이 그것을 알려주는지 확인해봤습니다.

한 줄만 바꿔본다

3장에서 만든 개입 구조는 이랬습니다.

while (true) {
  const { done, value } = await reader.read();

  if (done) break;

  controller.enqueue(value); // 읽는 즉시 내보낸다
}

여기서 enqueue를 배열에 모으는 것으로 바꿔봅니다.

const chunks: Uint8Array[] = [];

while (true) {
  const { done, value } = await reader.read();

  if (done) break;

  chunks.push(value); // 모은다
}

for (const chunk of chunks) {
  controller.enqueue(chunk); // 다 모인 뒤 한 번에
}

모았다가 한 번에 내보내면 스트리밍이 아니게 됩니다. 그러면 무엇이 달라지는지 확인해봤습니다.

예상했을 때는 TTFB가 2초로 튈 것으로 생각했습니다. 다 모이면 한 번에 처리하는 것이니, getTodoList가 완료되기 전까지는 화면에 아무것도 보이지 않겠다고 이해했기 때문입니다.

결과 1 — 응답 원문은 완전히 같다

curl -N localhost:3001

3장에서 본 응답과 한 글자도 다르지 않았습니다.

서버가 만드는 HTML은 달라진 것이 없습니다. 언제 내보내느냐만 바뀌었을 뿐이었습니다.

결과 2 — TTFB도 정상으로 보인다

TTFB: 0.002676s / Total: 2.007220s

TTFB가 2초로 튈 것이라고 예상했는데 그렇지 않았습니다.

이유는 코드에 있었습니다. header를 먼저 enqueue하고 그다음부터 모으기 시작합니다. 그러니 첫 바이트는 header가 즉시 내보낸 것입니다.

TTFB는 첫 바이트가 언제 나갔는지만 측정합니다. React 콘텐츠가 2초 뒤에 한꺼번에 나갔다는 사실은 잡히지 않습니다.

결과 3 — 화면에서만 드러난다

브라우저로 열어보면 다릅니다.

loading...이 보이지 않습니다.

브라우저는 HTML을 받는 즉시 파싱하고 그립니다. 다 받을 때까지 기다리지 않습니다.

그리고 그 2초 동안 빈 화면으로 보입니다.

빈 화면 loading... 목록
정상 ~6ms 6ms ~ 2000ms 2000ms ~
버퍼링 ~2000ms 스쳐감 2000ms ~

이 결과는 현재 회사에서 진행하는 프로젝트에서 처음 SSR 프로젝트를 맡았을 때 겪었던 현상과 동일했습니다.

무엇이 이 문제를 잡아내는가

관측 수단 스트리밍이 죽은 것을 잡았나
curl 응답 원문 아니오. 완전 동일
TTFB 아니오. 정상으로 보임
loading... 표시 여부 예
빈 화면 시간 예

응답도 같고 숫자도 같은데 사용자 경험만 다릅니다.

이것이 스트리밍의 본질이기도 합니다. 서버가 보내는 바이트는 결국 같습니다. 다른 것은 언제 보내느냐뿐이고, 그 차이는 사용자 화면에서만 드러납니다.

성능 지표로 말하면 FCP(First Contentful Paint)가 달라진 것입니다. 총 로딩 시간이 아니라 첫 픽셀이 언제 뜨느냐입니다.

그렇다면 스트리밍은 항상 좋은 것일까요.