TTFB 분해 — DNS / TCP / TLS / 서버 처리 병목 찾기
TTFB를 DNS 조회, connection, TLS handshake, request 전송, server 처리, first-byte 전송 구간으로 나눕니다. curl timing 값과 browser Network panel을 함께 읽고 connection reuse·HTTP version·cache 상태를 기록해 병목을 구분합니다. ICMP ping만으로 HTTP 지연 원인을 판단할 수 없는 이유도 설명합니다.
목차(34개 항목)
- 0. 핵심 명제 — TTFB는 합산 측정값
1. DNS 조회 — 여러 캐시와 재귀 resolver
2. TCP 핸드셰이크 — 연결 통로 만들기
3. TLS 핸드셰이크 — 암호화 협상
4. "첫 바이트 도착" — 스트림이라는 사실
5. ping vs TTFB — 시나리오별 진단
6. 실전 측정 — curl 한 줄로 모든 단계 분해
7. 줄이는 법 — 단계별 처방
- 8. 결론 + 진단 흐름도
- 관련 글
- 참고 자료
가상의 서비스에서 "TTFB가 350ms이고 ping은 7ms로 측정됐습니다. 서버가 느린 건가요?"라는 질문을 받았다고 가정해 봅니다. 이 수치는 진단 절차를 설명하기 위한 예시이며 실제 서비스의 측정 결과가 아닙니다. 답을 하려면 먼저 TTFB가 무엇을 측정한 값인지부터 정리해야 합니다. TTFB는 서버 처리 시간만을 뜻하지 않습니다. 서버 코드가 실행되기 전후의 여러 구간이 함께 포함됩니다.
이 글은 요청 → 첫 바이트 도착까지의 모든 단계를 분해하고, 각 단계가 무엇을 하는지·어떻게 측정하는지·어디서 줄일 수 있는지 정리합니다.
0. 핵심 명제 — TTFB는 합산 측정값
TTFB는 측정 도구가 정한 시작점부터 첫 응답 바이트까지의 경과 시간입니다.
| 잘못된 이해 | 정확한 이해 |
|---|---|
| 서버가 응답을 만드는 데 걸린 시간 | 클라이언트가 측정한 합산 경과 시간 |
TTFB 안에 들어있는 것:
핵심: curl의 time_starttransfer는 전송 시작부터 첫 바이트까지의 누적값이라 이름 해석·연결·TLS·프로토콜 협상과 서버 처리 시간을 포함합니다. 브라우저 Navigation Timing의 responseStart - requestStart는 요청 시작 이후만 계산하므로 DNS·연결 시간을 포함하지 않습니다. 먼저 어떤 지표를 말하는지 고정해야 합니다.
1. DNS 조회 — 여러 캐시와 재귀 resolver
브라우저는 service.example 같은 도메인 이름만으로 서버에 연결할 수 없습니다. DNS로 접속할 IP 주소를 확인하는 과정이 먼저 필요합니다. 아래 도메인과 IP는 흐름을 설명하기 위한 문서용 예시입니다.
이 변환에는 브라우저·OS·로컬 네트워크·재귀 resolver의 캐시가 관여할 수 있습니다. 구현과 네트워크 정책에 따라 일부 계층은 없거나 순서가 다릅니다.
흔히 관여하는 계층
| 위치 | 설명 |
|---|---|
| 브라우저 | 자체 호스트 캐시와 연결 정보를 재사용할 수 있음 |
| OS resolver | 운영체제와 로컬 resolver 정책에 따라 캐시 |
| 공유기·사내 DNS | DNS proxy/cache로 동작하는 구성도 있음 |
| 재귀 resolver | ISP 또는 사용자가 선택한 resolver가 권한 서버 질의를 대신 수행 |
| 권한 DNS | root·TLD 위임을 따라 도메인의 권한 서버가 최종 레코드를 제공 |
같은 사이트 두 번째 방문이 빠른 이유
TTL(Time To Live) 이 만료되면 캐시가 새 질의를 해야 합니다. TTL은 레코드 운영자가 설정하므로 고정 범위로 일반화할 수 없습니다.
⑤ 권한 DNS 추적 — 트리 구조
ISP DNS가 캐시에 답이 없으면 재귀 질의를 합니다. 위에서부터 차례로:
- ISP DNS → root: ".app은 누가 관리해?" → root: "이 IP에 .app TLD 서버"
- ISP DNS → 권한 서버 위임 정보 확인: "service.example은 어느 서버가 담당하나?"
- ISP DNS → 권한 NS: "접속할 주소를 알려줘" → 문서용 IP 응답
실제 지연은 resolver 캐시, 권한 서버 위치, 재시도와 네트워크 경로에 따라 측정해야 합니다.
시크릿 모드의 캐시?
시크릿 모드는 일반 프로필과 저장 상태를 분리하지만 OS·네트워크 계층까지 완전히 초기화하는 DNS 벤치마크 모드는 아닙니다. 브라우저 버전과 Secure DNS 설정도 함께 기록하세요.
진짜 비우려면:
DoH(DNS over HTTPS)가 바꾸는 것
브라우저의 Secure DNS(DoH)는 현재 DNS 제공자를 보안 방식으로 업그레이드하거나 사용자가 선택한 제공자를 사용할 수 있습니다. 활성화되면:
- OS DNS resolver를 우회 → ② OS 캐시가 안 잡힐 수 있음
- 회사 보안장비/공유기가 DNS를 못 봄
- 측정 도구로 OS 캐시 상태를 봐도 브라우저 실제 동작과 다를 수 있음
시사점: OS 캐시만 비운 결과로 브라우저 DNS 경로 전체가 초기화됐다고 단정하지 않습니다.
2. TCP 핸드셰이크 — 연결 통로 만들기
DNS로 IP를 알아냈으면 이제 그 IP와 신뢰할 수 있는 연결을 만들어야 합니다. TCP는 패킷이 잘 도착했는지, 순서대로 도착했는지 보장하는 프로토콜.
3-way handshake
왕복 횟수의 정확한 의미
클라이언트는 SYN을 보낸 뒤 약 1 RTT에 SYN-ACK을 받습니다. 서버가 마지막 ACK(또는 함께 실린 데이터)를 받는 시점은 그보다 한쪽 방향 전파 시간만큼 뒤입니다.
- SYN(브라우저→서버) + SYN-ACK(서버→브라우저) = 1 RTT
- 마지막 ACK는 클라이언트가 데이터와 함께 보낼 수 있음 (지연 누적 X)
거리별 비용:
| 경로 | RTT | TCP 핸드셰이크 비용 |
|---|---|---|
| 한국 ↔ 한국 (같은 IDC) | 5~10ms | ~10ms |
| 한국 ↔ 일본 | 30~50ms | ~50ms |
| 한국 ↔ 미국 서부 | 130~150ms | ~150ms |
| 한국 ↔ 미국 동부 | 180~220ms | ~220ms |
이 비용은 서버 코드와 무관. 거리 자체가 만든다.
3. TLS 핸드셰이크 — 암호화 협상
TCP로 만든 통로는 평문입니다. 가운데서 누가 엿들으면 다 보이죠. HTTPS의 보안성은 TCP 위에 한 층 더 얹는 TLS 핸드셰이크에서 옵니다.
TLS 1.2 vs TLS 1.3
| 버전 | 핸드셰이크 RTT | 비고 |
|---|---|---|
| TLS 1.2 | 보통 2 RTT | 신규 full handshake 기준. TLS 1.0/1.1은 RFC 8996으로 폐지 |
| TLS 1.3 | 1 RTT | 신규 full handshake 기준 |
| TLS 1.3 early data | 0-RTT 전송 가능 | PSK 재개에서만 가능하며 replay 위험과 서버 정책 제약이 있음 |
TLS 1.3 흐름
TLS 1.3에서는 클라이언트가 Finished와 애플리케이션 데이터를 같은 flight에 보낼 수 있습니다. 이후 HTTP 데이터는 협상된 키로 보호됩니다.
비유
- TCP: 전화선 연결. "여보세요? 여보세요. 잘 들려? 응 잘 들려."
- TLS: 통화하면서 "암호 책 페이지 맞추자. 47페이지 5번 단어부터?"
왜 분리되어 있나?
1990년대 인터넷 초기에는 암호화가 없었습니다(HTTP). 나중에 보안이 필요해지자 "TCP는 그대로, 위에 암호화 층을 얹자"는 방식으로 SSL → TLS가 추가됐습니다. 레이어 분리 덕분에 TCP는 그대로 두고 TLS만 발전시킬 수 있는 구조.
거리별 누적 핸드셰이크 비용
아래 RTT와 계산값은 연결 재사용이 없는 흐름을 설명하기 위한 가상 예시입니다. 실제 값은 사용자 ISP, 라우팅, 프록시·CDN, TLS 재개 여부에 따라 달라집니다.
RTT가 커지면 새 연결의 핸드셰이크 비용도 함께 늘어납니다. 정적 자원이나 일부 동적 처리를 사용자 가까이 배치하면 이 구간을 줄일 수 있지만, 실제 개선 폭은 연결 재사용률과 서버 처리 시간을 함께 측정해야 판단할 수 있습니다.
4. "첫 바이트 도착" — 스트림이라는 사실
HTTP 응답은 한 덩어리로 한 번에 오지 않습니다. 스트림으로, 패킷이 줄줄이 옵니다.
| 측정값 | 의미 |
|---|---|
time_starttransfer (curl) | 첫 바이트 도착 = TTFB |
time_total (curl) | 마지막 바이트 도착 |
| 차이 | 본문 다운로드 시간 |
첫 바이트가 빠르면 왜 좋은가
브라우저는 HTML을 받자마자 파싱 시작합니다. 다 받을 때까지 기다리지 않음. 첫 바이트 도착 → 즉시 HTML 파싱 → <link rel="stylesheet"> 만나면 CSS 다운로드 시작 → <script> 만나면 JS 다운로드 시작 → 화면 그리기 시작.
첫 바이트가 빨리 와야 이 체인 전체가 빨리 시작됨. 그래서 TTFB는 페이지 로딩 체감 속도의 첫 도미노입니다.
5. ping vs TTFB — 시나리오별 진단
TTFB 숫자가 비슷해 보여도 연결·서버·캐시 상태에 따라 원인이 달라질 수 있습니다. 아래 가상 시나리오는 어떤 추가 계측이 필요한지 보여 줍니다.
시나리오 A — 서버 처리 구간을 의심하는 가상 사례
진단 방향: 이 두 값만으로 서버 병목을 확정하지 않습니다. curl 구간별 시간과 서버 APM을 먼저 대조한 뒤 서버 코드, DB, 외부 API를 확인합니다.
시나리오 B — 네트워크 구간을 의심하는 가상 사례
주의: ping은 ICMP 경로이고 HTTP 연결은 프록시·CDN·Anycast와 연결 재사용 상태의 영향을 받습니다. ping 값만으로 HTTPS TTFB의 하한을 계산하지 마세요.
진단 방향: 연결 설정과 전송 구간의 비중이 크다면 서버 코드 최적화만으로는 개선 폭이 작을 수 있습니다. CDN/Edge 배치, 연결 재사용과 대상 리전 변경을 각각 측정합니다.
시나리오 C — DNS 콜드 스타트를 의심하는 가상 사례
두 실행의 차이만으로 DNS 원인을 확정할 수 없습니다. time_namelookup을 직접 비교하고, CDN·애플리케이션 캐시와 서버 부하도 함께 기록하세요.
6. 실전 측정 — curl 한 줄로 모든 단계 분해
다음 URL은 자리표시자입니다. 실제 측정할 서비스 주소로 바꿔 실행합니다.
출력 해석
아래 출력 역시 계산 방법을 보여 주는 가상 결과입니다.
구간별 비용 계산:
같은 명령 두 번 돌리기
캐시 효과 분리:
브라우저에서 보기
- Chrome DevTools → Network → 임의 요청 클릭 → Timing 탭
- "Stalled / DNS Lookup / Initial Connection / SSL / Waiting (TTFB) / Content Download" 6단계로 분해됨
chrome://net-export/로 NetLog를 수집해 연결과 DNS 이벤트를 확인
7. 줄이는 법 — 단계별 처방
DNS 단계
| 처방 | 효과 |
|---|---|
<link rel="dns-prefetch" href="//api.example.com"> | 다른 도메인 DNS 미리 조회 |
<link rel="preconnect" href="https://api.example.com"> | DNS + TCP + TLS까지 미리 |
| TTL 적절히 설정 | 너무 짧으면 매번 콜드, 너무 길면 IP 변경 시 지연 |
| Anycast DNS (Cloudflare, Vercel) | 글로벌 어디서든 가까운 노드 |
TCP/TLS 단계
| 처방 | 효과 |
|---|---|
| 연결 재사용 | 같은 호스트 재요청 시 핸드셰이크를 생략할 수 있음. HTTP/1.1은 persistent connection이 기본이지만 서버·프록시 정책에 좌우됨 |
| HTTP/2 multiplexing | 한 연결로 여러 요청 병렬 |
| HTTP/3 (QUIC) | TCP+TLS를 1 RTT로 합침. 0-RTT 재개 가능 |
| TLS 1.3 지원 | full handshake를 1 RTT로 줄일 수 있음 |
| 0-RTT 재개 | replay 가능한 요청에만 서버 정책과 애플리케이션 안전성을 검토해 사용 |
거리 단계
| 처방 | 효과 |
|---|---|
| CDN / Edge 배포 | 정적 자원은 사용자 가까운 PoP에서 응답 |
| Edge Functions (Vercel Edge, Cloudflare Workers) | 동적 응답도 Edge에서 처리 |
| 읽기 전용 DB 복제본 | 글로벌 사용자에 가까운 read replica |
서버 단계 (TTFB - 네트워크 비용)
| 처방 | 효과 |
|---|---|
| DB 쿼리 인덱스 / N+1 제거 | 서버 처리 구간이 클 때 우선 확인할 후보 |
| 외부 API 병렬 호출 | 직렬 → 병렬로 RTT 합치기 |
| 캐시 (Redis, in-memory) | DB hit 자체 줄이기 |
| Streaming 응답 | 헤더 먼저 보내고 body는 점진적 (TTFB 단축) |
| Server Components / SSR 최적화 | Next.js 등 프레임워크 패턴 적용 |
8. 결론 + 진단 흐름도
TTFB는 도구별 시작점이 다른 합산값입니다. curl 구간값과 서버 측 계측을 함께 봐야 병목을 분리할 수 있습니다.
관련 글
- #0 WebRTC란? ICE/STUN/NAT/TURN 기초 — ICE 후보 수집도 결국 또 다른 종류의 연결 협상(핸드셰이크)
- #8 왜 P2P가 막히는가 — NAT/STUN/TURN — 연결 수립 단계의 지연/실패가 어디서 오는지 같은 관점
- #9 Agora 기업 방화벽 우회 — Cloud Proxy — TLS over TCP 443 포트를 빌려 쓰는 우회, TLS 핸드셰이크 절과 연결
- #32 시그널링과 미디어 분리 — RTSP/SIP vs RTP — TCP/TLS와 데이터 평면을 나누는 레이어 분리 사고방식
- #33 Cloud Recording 점프 재생 — moov/ENDLIST/DISCONTINUITY — 또 다른 "합산 측정값/타임라인 분해" 트러블슈팅 사례
참고 자료
- RFC 8446 — The Transport Layer Security (TLS) Protocol Version 1.3 — TLS 1.3 1-RTT/0-RTT 핸드셰이크 정의
- RFC 8996 — Deprecating TLS 1.0 and TLS 1.1 — 폐지된 건 1.0/1.1이지 1.2가 아님(본문 정정 근거)
- RFC 9000 — QUIC: A UDP-Based Multiplexed and Secure Transport — HTTP/3가 TCP+TLS를 1-RTT로 합치는 트랜스포트
- W3C Navigation Timing Level 2 —
requestStart,responseStart, DNS·연결 타임스탬프 정의 - W3C Server Timing — 클라이언트 지표만으로 보이지 않는 서버 처리 구간을 응답에 노출하는 표준
- WHATWG HTML — link type preconnect — 사전 연결 힌트의 표준 정의
- curl --write-out 매뉴얼 —
time_namelookup/time_connect/time_appconnect/time_starttransfer변수 레퍼런스