TTFB 분해 — DNS / TCP / TLS / 서버 처리, 어디서 350ms가 새는가
"TTFB는 350ms인데 ping은 7ms입니다. 서버가 느린 건가요?" 답하려면 TTFB가 서버 처리 시간이 아니라는 사실부터 짚어야 합니다. 이 글은 요청부터 첫 바이트가 도착하기까지를 DNS 조회, TCP 핸드셰이크, TLS 협상, 서버 처리 네 단계로 분해하고, curl 한 줄로 각 구간 비용을 측정해 350ms가 실제로 어디서 새는지 찾아냅니다. ping 값에 따라 병목이 서버인지 네트워크인지 DNS 콜드 스타트인지 가려내는 진단법과 단계별 처방까지 정리했습니다.
목차(34개 항목)
- 0. 핵심 명제 — TTFB는 합산 측정값
2. TCP 핸드셰이크 — 연결 통로 만들기
3. TLS 핸드셰이크 — 암호화 협상
4. "첫 바이트 도착" — 스트림이라는 사실
5. ping vs TTFB — 시나리오별 진단
6. 실전 측정 — curl 한 줄로 모든 단계 분해
7. 줄이는 법 — 단계별 처방
- 8. 한 줄 결론 + 진단 흐름도
- 관련 글
- 참고 자료
"langco.app TTFB가 350ms인데 ping은 7ms입니다. 서버가 느린 건가요?". 답을 하려면 먼저 TTFB가 무엇을 측정한 값인지부터 정리해야 합니다. TTFB는 서버 처리 시간이 아닙니다. 그 안에는 서버가 손도 안 댄 시간이 잔뜩 들어있습니다.
이 글은 요청 → 첫 바이트 도착까지의 모든 단계를 분해하고, 각 단계가 무엇을 하는지·어떻게 측정하는지·어디서 줄일 수 있는지 정리합니다.
0. 핵심 명제 — TTFB는 합산 측정값
TTFB(Time to First Byte) = 요청 시작 ~ 첫 응답 바이트 도착까지의 모든 시간
| 잘못된 이해 | 정확한 이해 |
|---|---|
| 서버가 응답을 만드는 데 걸린 시간 | 클라이언트가 측정한 합산 경과 시간 |
TTFB 안에 들어있는 것:
핵심: TTFB가 350ms로 측정됐다고 해서 "서버가 350ms 동안 일했다"가 아니다. 그 안에서 1~4단계 비용을 빼야 진짜 서버 처리 시간이 보인다.
1. DNS 조회 — 5층 캐시 구조
브라우저는 langco.app이라는 글자로 서버에 연결할 수 없습니다. 인터넷은 IP(예: 76.76.21.21)로만 통신합니다. 도메인 → IP 변환이 첫 단계.
이 변환은 5층 캐시를 차례로 뒤져서 해결합니다.
5층 매트릭스
| 단계 | 위치 | 적중 시 지연 | 설명 |
|---|---|---|---|
| ① 브라우저 캐시 | 크롬/사파리 내부 | 0ms | 같은 브라우저 최근 방문 |
| ② OS 캐시 | mDNSResponder (mac), DNS Client (Win), systemd-resolved (Linux) | 0~1ms | OS가 RAM에 보유, 모든 앱 공유 |
| ③ 공유기 캐시 | 가정/사무실 라우터 | 1~5ms | 같은 LAN 안 모든 기기 공유 |
| ④ ISP DNS | KT/SKT/LGU+, 8.8.8.8, 1.1.1.1 | 5~30ms | 인기 도메인은 거의 항상 적중 |
| ⑤ 권한 DNS 추적 | root → TLD → authoritative NS | 30~100ms | 최악 콜드 스타트 — 첫 방문 |
같은 사이트 두 번째 방문이 빠른 이유
TTL(Time To Live) 값이 만료되면 다시 처음부터 시작. 일반적으로 5분 ~ 1시간.
⑤ 권한 DNS 추적 — 트리 구조
ISP DNS가 캐시에 답이 없으면 재귀 질의를 합니다. 위에서부터 차례로:
- ISP DNS → root: ".app은 누가 관리해?" → root: "이 IP에 .app TLD 서버"
- ISP DNS → .app TLD: "langco.app 알아?" → TLD: "이 NS에 가서 물어봐"
- ISP DNS → langco.app 권한 NS: "진짜 IP 줘" → "76.76.21.21"
각 단계가 30ms씩이라면 총 90ms. 콜드 스타트가 느린 이유.
시크릿 모드의 캐시?
브라우저 캐시(①)만 우회. OS 캐시 아래는 다 살아있음. 시크릿 모드는 프라이버시 기능이지 성능 리셋이 아닙니다.
진짜 비우려면:
DoH(DNS over HTTPS)가 바꾸는 것
최근 브라우저는 8.8.8.8이나 1.1.1.1 같은 공용 DNS에 HTTPS로 직접 질의합니다. 그러면:
- OS DNS resolver를 우회 → ② OS 캐시가 안 잡힐 수 있음
- 회사 보안장비/공유기가 DNS를 못 봄
- 측정 도구로 OS 캐시 상태를 봐도 브라우저 실제 동작과 다를 수 있음
시사점: dscacheutil 캐시를 비웠는데도 크롬이 빠르다면 DoH 캐시 의심.
2. TCP 핸드셰이크 — 연결 통로 만들기
DNS로 IP를 알아냈으면 이제 그 IP와 신뢰할 수 있는 연결을 만들어야 합니다. TCP는 패킷이 잘 도착했는지, 순서대로 도착했는지 보장하는 프로토콜.
3-way handshake
왕복 횟수의 정확한 의미
자주 "TCP는 1.5 RTT"라는 표현을 봅니다. 이건 메시지 개수(3개) ÷ 2라는 의미일 뿐, 연결 완료까지 소요 시간 = 1 RTT가 더 정확합니다.
- 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 | 2008년 표준(RFC 5246). 정정: TLS 1.2는 폐지된 게 아니라 여전히 널리 쓰임 — 폐지된 건 TLS 1.0/1.1(RFC 8996, 2021) |
| TLS 1.3 | 1 RTT | 2018년 표준, 현대 브라우저 기본 |
| TLS 1.3 + 0-RTT | 0 RTT | 재방문 시 (replay 공격 위험 있어 GET만 권장) |
TLS 1.3 흐름
이게 끝나면 그 다음부터 주고받는 모든 HTTP 데이터는 암호화됩니다. 중간에 KT나 카페 와이파이가 가로채도 내용을 못 읽음.
비유
- TCP: 전화선 연결. "여보세요? 여보세요. 잘 들려? 응 잘 들려."
- TLS: 통화하면서 "암호 책 페이지 맞추자. 47페이지 5번 단어부터?"
왜 분리되어 있나?
1990년대 인터넷 초기에는 암호화가 없었습니다(HTTP). 나중에 보안이 필요해지자 "TCP는 그대로, 위에 암호화 층을 얹자"는 방식으로 SSL → TLS가 추가됐습니다. 레이어 분리 덕분에 TCP는 그대로 두고 TLS만 발전시킬 수 있는 구조.
거리별 누적 핸드셰이크 비용
거리 멀어질수록 핸드셰이크 비용이 폭증. CDN/Edge 배포로 거리를 줄이는 게 가장 큰 효과.
4. "첫 바이트 도착" — 스트림이라는 사실
HTTP 응답은 한 덩어리로 한 번에 오지 않습니다. 스트림으로, 패킷이 줄줄이 옵니다.
| 측정값 | 의미 |
|---|---|
time_starttransfer (curl) | 첫 바이트 도착 = TTFB |
time_total (curl) | 마지막 바이트 도착 |
| 차이 | 본문 다운로드 시간 |
첫 바이트가 빠르면 왜 좋은가
브라우저는 HTML을 받자마자 파싱 시작합니다. 다 받을 때까지 기다리지 않음. 첫 바이트 도착 → 즉시 HTML 파싱 → <link rel="stylesheet"> 만나면 CSS 다운로드 시작 → <script> 만나면 JS 다운로드 시작 → 화면 그리기 시작.
첫 바이트가 빨리 와야 이 체인 전체가 빨리 시작됨. 그래서 TTFB는 페이지 로딩 체감 속도의 첫 도미노입니다.
5. ping vs TTFB — 시나리오별 진단
TTFB 숫자가 비슷해 보여도 ping 값에 따라 병목이 다른 곳입니다. 아래 세 시나리오로 나눠 봅니다.
시나리오 A — 서버가 병목 (langco.app 케이스)
진단 방향: 서버 코드, DB 쿼리, 외부 API 호출(Supabase auth 등) 의심. 네트워크 최적화 효과 거의 없음.
시나리오 B — 네트워크가 병목 (한국 → 미국 동부)
주의: 만약 같은 200ms ping 환경에서 TTFB가 이론 핸드셰이크 비용(400ms)보다 작게 측정된다면, 그건 keep-alive로 기존 연결을 재사용했거나 0-RTT 재개가 일어난 것이다(핸드셰이크를 새로 안 함). 핸드셰이크가 매번 일어난다는 전제에서는 TTFB가 400ms 아래로 내려갈 수 없다.
진단 방향: 서버 코드 만져봐야 1ms도 안 줄어듦. CDN/Edge 배포로 거리 자체를 줄여야 함.
시나리오 C — DNS 콜드 스타트
같은 사이트를 두 번 측정해서 차이가 70~100ms면 DNS 콜드 스타트. 매번 발생하는 건 아니지만 사용자 첫 인상에 영향.
6. 실전 측정 — curl 한 줄로 모든 단계 분해
출력 해석
구간별 비용 계산:
같은 명령 두 번 돌리기
캐시 효과 분리:
브라우저에서 보기
- Chrome DevTools → Network → 임의 요청 클릭 → Timing 탭
- "Stalled / DNS Lookup / Initial Connection / SSL / Waiting (TTFB) / Content Download" 6단계로 분해됨
chrome://net-internals/#events로 더 깊은 추적 가능chrome://net-internals/#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 단계
| 처방 | 효과 |
|---|---|
| Connection: keep-alive | 같은 호스트 재요청 시 핸드셰이크 재사용 (HTTP/1.1 기본) |
| HTTP/2 multiplexing | 한 연결로 여러 요청 병렬 |
| HTTP/3 (QUIC) | TCP+TLS를 1 RTT로 합침. 0-RTT 재개 가능 |
| TLS 1.3 강제 | 1 RTT (TLS 1.2의 2 RTT 대비 절반) |
| 0-RTT 재개 | 재방문 시 핸드셰이크 0ms (replay 위험 있어 GET 한정 권장) |
거리 단계
| 처방 | 효과 |
|---|---|
| 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는 합산값. 네트워크 비용을 빼야 진짜 서버 시간이 보인다. ping과 함께 측정하라.
관련 글
- #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로 합치는 트랜스포트
- MDN — Time to First Byte (TTFB) — TTFB 정의와 구성 단계
- MDN — rel=preconnect / dns-prefetch (Resource hints) — DNS/TCP/TLS 사전 연결 힌트
- curl --write-out 매뉴얼 —
time_namelookup/time_connect/time_appconnect/time_starttransfer변수 레퍼런스