Skip to main content
요청이 2초 걸렸다고 해서 서버가 2초 동안 일한 건 아니에요. 첫 바이트를 기다린 시간과, 그 뒤 본문을 받은 시간은 다른 신호예요.
End-to-End Request Debugging에서는 느린 요청 하나를 DNS, 연결, TLS, 프록시, 캐시, 오리진으로 나눠 읽는 큰 그림을 봤어요. 그리고 브라우저 waterfall에서는 Waiting for server responseContent Download가 서로 다른 구간이라는 것도 봤죠. 근데요, 실제 운영 화면에서는 이런 식으로 한 줄만 보일 때가 많아요.
이걸 보고 바로 이렇게 말하고 싶어져요.
“API가 1.24초나 걸렸으니 서버가 느리네요.”
그럴 수도 있어요. 하지만 아직은 몰라요.
  • 첫 바이트가 늦게 왔나요?
  • 첫 바이트는 빨랐는데 본문 다운로드가 길었나요?
  • 압축된 크기는 작지만 압축 해제 뒤 본문이 컸나요?
  • CDN은 바로 응답했는데 브라우저 쪽 네트워크가 느렸나요?
  • 서버가 일부러 스트리밍하느라 오래 열린 요청인가요?
오늘은 이 질문을 하나로 줄여볼게요.
“이 요청은 첫 조각이 늦은 걸까요, 아니면 전체를 가져오는 데 오래 걸린 걸까요?”
여기서는 TTFB를 성능 점수처럼 외우기보다, 디버깅할 때 첫 바이트 대기본문 다운로드를 나눠 읽는 감각에 집중해요. 도구마다 정확한 측정 시작점은 조금 다를 수 있으니, 같은 도구 안에서 비교하는 습관이 중요해요.

식당에서 첫 음식이 늦은 것과 코스가 긴 것은 달라요

식당에 앉아서 주문했다고 해볼게요. 한 식당은 주문하고 20분 동안 아무것도 안 나와요. 그러다 첫 접시가 나오고, 나머지는 금방 끝나요. 다른 식당은 첫 접시는 1분 만에 나와요. 그런데 코스 요리가 계속 이어져서 전체 식사는 1시간이 걸려요. 둘 다 “오래 걸렸다”고 말할 수 있지만, 읽는 방식은 달라요. 그래서 느린 요청을 볼 때는 전체 시간 하나보다 아래 두 구간을 먼저 나눠요. 이 그림에서 D까지 오래 걸리면 첫 바이트 대기 문제에 가까워요. D에서 E까지 오래 걸리면 본문 다운로드 문제에 가까워요.

브라우저에서는 Waiting과 Content Download를 나눠 봐요

Chrome DevTools Network 패널에서 요청 하나를 누르고 Timing을 보면 이런 식의 구간이 보여요.
이 요청은 전체로 보면 약 845 ms예요. 하지만 핵심은 Waiting for server response820 ms이고, Content Download18 ms라는 점이에요. 처음에는 이렇게 읽으면 돼요. 여기서 Waiting for server response는 브라우저가 첫 바이트를 기다린 시간이에요. 흔히 TTFB 감각과 연결해서 봐요. 다만 이 안에는 앱 코드 실행만 들어 있는 게 아니에요. 브라우저 밖에서는 하나의 긴 Waiting으로 보이지만, 안쪽에서는 여러 일이 섞였을 수 있어요. 그래서 Waiting이 길다는 건 좋은 출발점이지, 곧바로 “DB가 느리다”는 결론은 아니에요.

curl에서는 누적 시간을 빼서 봐요

터미널에서는 curl timing으로 비슷한 구간을 볼 수 있어요.
예를 들어 이런 값이 나왔다고 해볼게요.
time_starttransfer는 시작부터 첫 바이트까지의 누적 시간이에요. 그래서 서버 응답 대기 감각을 보려면 time_starttransfer - time_pretransfer처럼 빼서 봐요. 이 예시는 첫 바이트 쪽이 길고, 다운로드는 짧아요. 그래서 다음에 볼 곳은 파일 크기보다 캐시 miss, 프록시 대기, 오리진 처리, DB, 외부 API 쪽이에요. 반대로 이런 값이면 해석이 달라져요.
첫 바이트는 약 92 ms 만에 왔어요. 그런데 전체 완료까지는 3.46 s예요. 이때는 서버가 첫 응답을 늦게 만든 문제가 아니라, 본문이 크거나 다운로드 경로가 느린 문제 쪽으로 의심이 옮겨가요.
브라우저는 쿠키, 캐시, Service Worker, 연결 재사용, HTTP/2/3 멀티플렉싱, 우선순위의 영향을 받아요. curl은 더 단순한 조건으로 요청할 수 있어요. 같은 URL이라도 두 도구의 조건을 맞추지 않으면 숫자가 달라질 수 있어요.

TTFB가 길 때는 첫 바이트 앞쪽을 봐요

이런 Timing을 만났다고 해볼게요.
이 요청은 본문을 받는 데 오래 걸린 게 아니에요. 첫 바이트가 늦게 온 거예요. 먼저 볼 신호는 이런 것들이에요. 예를 들어 브라우저에서 Waiting1850 ms인데 앱 로그에는 처리 시간이 40 ms라면, 앱 함수 자체보다 앞단 queue, 오리진 연결, CDN과 오리진 사이, 로그에 찍히기 전 구간을 의심해야 해요. 반대로 앱 로그도 1800 ms라면 이제 DB, 외부 API, 렌더링, lock, cold start 같은 안쪽 원인을 볼 차례예요.

Content Download가 길 때는 첫 바이트 뒤쪽을 봐요

이번에는 이런 Timing이에요.
첫 바이트는 빨리 왔어요. 그런데 본문을 다 받는 데 오래 걸렸어요. 이때는 질문이 달라져요. 예를 들어 큰 이미지나 JSON 응답이라면 Content Download가 긴 게 자연스러울 수 있어요.
여기서는 오리진이 첫 바이트를 늦게 만든 것보다 응답 자체가 크고, 사용자의 네트워크로 그 크기를 옮기는 데 시간이 걸린 것에 가까워요. 다음 행동도 달라져야 해요. DB 쿼리를 먼저 뒤지기보다 페이지네이션, 필드 축소, 이미지 포맷, 압축, CDN 위치, lazy loading을 봐야 할 수 있어요.

둘 다 길면 경계부터 다시 잡아요

가끔은 둘 다 길어요.
이때는 “서버도 느리고 다운로드도 느리다”가 맞을 수 있어요. 하지만 그래도 순서를 잡아야 해요. 핵심은 한 번에 모든 걸 “느림”으로 묶지 않는 거예요. 첫 바이트 전과 후는 담당 팀, 로그, 해결책이 달라질 수 있어요. 마지막 줄도 중요해요. 네트워크 요청이 빨리 끝났는데 화면이 늦게 보이면, 더 이상 Network waterfall만 볼 문제가 아닐 수 있어요.

스트리밍 응답은 일부러 다운로드가 길 수 있어요

Content Download가 길다고 항상 나쁜 건 아니에요. 예를 들어 Server-Sent Events, 긴 파일 다운로드, 동영상, 큰 로그 export, AI 응답 스트리밍처럼 오래 열려 있는 게 의도인 응답이 있어요.
이런 요청은 첫 바이트가 빨리 오고, 그 뒤 연결이 오래 유지되는 게 정상일 수 있어요. 그래서 Content Download가 길면 먼저 응답의 성격을 봐야 해요. 문서 HTML이 8초 다운로드되는 것과, 이벤트 스트림이 8분 열려 있는 것은 전혀 다른 장면이에요.

잘못 읽기 쉬운 함정

TTFB를 서버 코드 실행 시간으로 보기

TTFB 감각에는 요청이 서버 코드에 도착하기 전의 CDN, 프록시, queue, 오리진 연결, 캐시 miss가 섞일 수 있어요. 서버 코드 실행 시간과 같다고 단정하면 안 돼요.

Content Download가 길면 서버가 늦게 만든다고 보기

첫 바이트는 이미 왔어요. 이 구간은 본문 크기, 압축, 네트워크, 스트리밍 방식과 더 가까울 수 있어요.

TTFB가 빠르면 페이지가 빠르다고 보기

첫 바이트가 빠른 건 좋은 신호지만, 큰 JavaScript 다운로드, 느린 렌더링, 뒤늦은 API 요청 때문에 사용자는 여전히 느리게 느낄 수 있어요.

압축 전 크기와 전송 크기를 섞어 보기

브라우저는 전송된 크기와 리소스 크기를 다르게 보여줄 수 있어요. Content-Encoding이 있으면 네트워크로 받은 크기와 브라우저가 다루는 크기가 달라질 수 있어요.

스트리밍 요청을 일반 API처럼 평가하기

SSE, 파일 다운로드, 동영상, export, chunked 응답은 오래 열려 있는 것이 의도일 수 있어요. 이런 요청은 “언제 첫 바이트가 왔는지”와 “왜 계속 열려 있는지”를 따로 봐야 해요.

예시로 같이 읽어볼게요

1. API 첫 바이트가 늦은 경우

이건 다운로드보다 첫 바이트 대기가 길어요. 응답 헤더에서 cache status, request id, Server-Timing을 보고, 같은 request id로 오리진 로그를 찾는 쪽이 먼저예요.

2. 이미지 다운로드가 긴 경우

첫 바이트는 빠른 편이에요. 여기서는 이미지 크기, 포맷, 압축, CDN 위치, lazy loading, responsive image 설정을 봐야 해요.

3. HTML이 늦고 크기도 큰 경우

이건 두 문제가 같이 있을 수 있어요. 서버가 HTML을 늦게 만들고, 만들어진 HTML도 큽니다. 이때는 오리진 렌더링 시간과 HTML 크기 개선을 분리해서 봐야 해요.

4. 스트리밍 API가 오래 열린 경우

일반 API라면 이상하지만, 스트리밍 응답이라면 정상일 수 있어요. 이때는 전체 시간이 길다는 사실보다 첫 이벤트가 빨리 왔는지, 연결이 의도대로 유지되는지, 중간에 끊기지 않는지를 봐야 해요.

자, 정리해볼까요?

전체 요청 시간이 길어도, 첫 바이트 전이 긴지 첫 바이트 뒤가 긴지에 따라 의미가 달라져요.브라우저에서는 Waiting for server responseContent Download를 먼저 나눠 읽어요.curl에서는 time_starttransfer - time_pretransfertime_total - time_starttransfer처럼 누적 시간을 빼서 구간을 봐요.Waiting이 길면 캐시 miss, 앞단 대기, 오리진 처리, DB, 외부 API 같은 첫 바이트 전 원인을 좁혀요.Content Download가 길면 응답 크기, 압축, 네트워크, 이미지/영상, 스트리밍 여부를 먼저 봐요.TTFB는 서버 코드 실행 시간과 같지 않고, 다운로드 시간은 서버 처리 시간과 같지 않아요.
느린 요청을 볼 때 가장 먼저 할 일은 전체 시간을 탓하는 게 아니에요. 첫 바이트 전과 후를 갈라서, 다음에 볼 화면을 정확히 고르는 것이에요.

이어서 보면 좋은 글