블로그 목록
Backend18분 읽기

TTFB 분해 — DNS / TCP / TLS / 서버 처리, 어디서 350ms가 새는가

"TTFB는 350ms인데 ping은 7ms입니다. 서버가 느린 건가요?" 답하려면 TTFB가 서버 처리 시간이 아니라는 사실부터 짚어야 합니다. 이 글은 요청부터 첫 바이트가 도착하기까지를 DNS 조회, TCP 핸드셰이크, TLS 협상, 서버 처리 네 단계로 분해하고, curl 한 줄로 각 구간 비용을 측정해 350ms가 실제로 어디서 새는지 찾아냅니다. ping 값에 따라 병목이 서버인지 네트워크인지 DNS 콜드 스타트인지 가려내는 진단법과 단계별 처방까지 정리했습니다.

TTFBDNSTCPTLSHTTP/3QUIC성능curl디버깅

"langco.app TTFB가 350ms인데 ping은 7ms입니다. 서버가 느린 건가요?". 답을 하려면 먼저 TTFB가 무엇을 측정한 값인지부터 정리해야 합니다. TTFB는 서버 처리 시간이 아닙니다. 그 안에는 서버가 손도 안 댄 시간이 잔뜩 들어있습니다.

이 글은 요청 → 첫 바이트 도착까지의 모든 단계를 분해하고, 각 단계가 무엇을 하는지·어떻게 측정하는지·어디서 줄일 수 있는지 정리합니다.


0. 핵심 명제 — TTFB는 합산 측정값

TTFB(Time to First Byte) = 요청 시작 ~ 첫 응답 바이트 도착까지의 모든 시간

잘못된 이해정확한 이해
서버가 응답을 만드는 데 걸린 시간클라이언트가 측정한 합산 경과 시간

TTFB 안에 들어있는 것:

[브라우저]                                                     [서버]
  │                                                              │
  │── 1) DNS 조회       (도메인 → IP)            ────────►       │
  │── 2) TCP 핸드셰이크 (연결 통로 만들기)        ◄───SYN-ACK──── │
  │── 3) TLS 핸드셰이크 (암호화 협상)             ◄──ServerHello──│
  │── 4) HTTP 요청 전송                           ────GET─────►  │
  │                                                              │ ← 여기서 처음으로
  │                                                              │   서버 코드 실행
  │── 5) 첫 바이트 도착 (응답 헤더 시작)         ◄──HTTP/1.1 200─│
  └────────────────────────── 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~1msOS가 RAM에 보유, 모든 앱 공유
③ 공유기 캐시가정/사무실 라우터1~5ms같은 LAN 안 모든 기기 공유
④ ISP DNSKT/SKT/LGU+, 8.8.8.8, 1.1.1.15~30ms인기 도메인은 거의 항상 적중
⑤ 권한 DNS 추적root → TLD → authoritative NS30~100ms최악 콜드 스타트 — 첫 방문

같은 사이트 두 번째 방문이 빠른 이유

첫 방문:    [브라우저] → ❌ → [OS] → ❌ → [공유기] → ❌ → [ISP] → ❌ → [권한 NS] → "76.76.21.21"
            (5단계 전부 → 30~100ms)

두 번째 방문: [브라우저] → ✅ "캐시에 있음" → 즉시
            (0ms)

TTL(Time To Live) 값이 만료되면 다시 처음부터 시작. 일반적으로 5분 ~ 1시간.

⑤ 권한 DNS 추적 — 트리 구조

                       . (root)
                  ┌──────┼──────┐
                .com   .app   .kr
                       │
              ┌────────┼────────┐
          langco.app  google.app
              │
        Authoritative NS (Vercel/Cloudflare 운영)
              │
          76.76.21.21  ← 진짜 IP

ISP DNS가 캐시에 답이 없으면 재귀 질의를 합니다. 위에서부터 차례로:

  1. ISP DNS → root: ".app은 누가 관리해?" → root: "이 IP에 .app TLD 서버"
  2. ISP DNS → .app TLD: "langco.app 알아?" → TLD: "이 NS에 가서 물어봐"
  3. ISP DNS → langco.app 권한 NS: "진짜 IP 줘" → "76.76.21.21"

각 단계가 30ms씩이라면 총 90ms. 콜드 스타트가 느린 이유.

시크릿 모드의 캐시?

브라우저 캐시(①)만 우회. OS 캐시 아래는 다 살아있음. 시크릿 모드는 프라이버시 기능이지 성능 리셋이 아닙니다.

진짜 비우려면:

# macOS
sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder

# Windows
ipconfig /flushdns

# Chrome 안의 호스트 캐시 (브라우저 ① 단계만)
# chrome://net-internals/#dns → "Clear host cache"

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

[브라우저]                            [서버]
    │                                   │
    │── SYN ───────────────────────────►│   "연결할래?"
    │                                   │
    │◄────────────────── SYN-ACK ──────│   "ㅇㅋ 나도 연결할래"
    │                                   │
    │── ACK ───────────────────────────►│   "확인. 이제 시작"
    │                                   │
    └─ 평문 통로 완성 ─────────────────┘

왕복 횟수의 정확한 의미

자주 "TCP는 1.5 RTT"라는 표현을 봅니다. 이건 메시지 개수(3개) ÷ 2라는 의미일 뿐, 연결 완료까지 소요 시간 = 1 RTT가 더 정확합니다.

  • SYN(브라우저→서버) + SYN-ACK(서버→브라우저) = 1 RTT
  • 마지막 ACK는 클라이언트가 데이터와 함께 보낼 수 있음 (지연 누적 X)

거리별 비용:

경로RTTTCP 핸드셰이크 비용
한국 ↔ 한국 (같은 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.22 RTT2008년 표준(RFC 5246). 정정: TLS 1.2는 폐지된 게 아니라 여전히 널리 쓰임 — 폐지된 건 TLS 1.0/1.1(RFC 8996, 2021)
TLS 1.31 RTT2018년 표준, 현대 브라우저 기본
TLS 1.3 + 0-RTT0 RTT재방문 시 (replay 공격 위험 있어 GET만 권장)

TLS 1.3 흐름

[브라우저]                            [서버]
    │                                   │
    │── ClientHello ──────────────────►│   "이런 알고리즘 지원해, 임시 키 a"
    │                                   │
    │◄── ServerHello + 인증서 ─────────│   "이거 쓰자, 내 임시 키 b, 내 신분증"
    │   + Finished                      │
    │                                   │
    │── Finished ─────────────────────►│   "확인 완료, 키 합성, 암호화 시작"
    │                                   │
    └─ 암호화 보안 통로 완성 ─────────┘

이게 끝나면 그 다음부터 주고받는 모든 HTTP 데이터는 암호화됩니다. 중간에 KT나 카페 와이파이가 가로채도 내용을 못 읽음.

비유

  • TCP: 전화선 연결. "여보세요? 여보세요. 잘 들려? 응 잘 들려."
  • TLS: 통화하면서 "암호 책 페이지 맞추자. 47페이지 5번 단어부터?"

왜 분리되어 있나?

1990년대 인터넷 초기에는 암호화가 없었습니다(HTTP). 나중에 보안이 필요해지자 "TCP는 그대로, 위에 암호화 층을 얹자"는 방식으로 SSL → TLS가 추가됐습니다. 레이어 분리 덕분에 TCP는 그대로 두고 TLS만 발전시킬 수 있는 구조.

거리별 누적 핸드셰이크 비용

한국→한국 (RTT 7ms):
   TCP 1 RTT + TLS 1.3 1 RTT = 14ms     ← 거의 무시 가능

한국→일본 (RTT 40ms):
   TCP 1 RTT + TLS 1.3 1 RTT = 80ms     ← 살짝 느낌

한국→미국 동부 (RTT 200ms):
   TCP 1 RTT + TLS 1.3 1 RTT = 400ms    ← 매우 느림
   TCP 1 RTT + TLS 1.2 2 RTT = 600ms    ← TLS 1.2면 더 느림

한국→미국 동부 + HTTP/3 (QUIC, 0-RTT):
   0 RTT (재방문 시)                     ← 핸드셰이크 비용 0

거리 멀어질수록 핸드셰이크 비용이 폭증. CDN/Edge 배포로 거리를 줄이는 게 가장 큰 효과.


4. "첫 바이트 도착" — 스트림이라는 사실

HTTP 응답은 한 덩어리로 한 번에 오지 않습니다. 스트림으로, 패킷이 줄줄이 옵니다.

서버 내부 타임라인:
─────────────────────────────────────────────────▶ 시간

[DB 쿼리] [auth 호출] [HTML 렌더링] [응답 시작]──[…계속 전송…]──[응답 끝]
                                       ↑                          ↑
                              첫 바이트 출발                  마지막 바이트
                              ↓                                    ↓
                         이 시점이                          이 시점이
                       TTFB의 끝                          time_total의 끝
측정값의미
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 케이스)

ping:    7ms    ████
TTFB:    350ms  ████████████████████████████████

→ 네트워크는 7ms로 멀쩡
→ 350 - (DNS 0~1ms + TCP 7ms + TLS 7ms) ≈ 335ms 가 서버 안에서 발생

진단 방향: 서버 코드, DB 쿼리, 외부 API 호출(Supabase auth 등) 의심. 네트워크 최적화 효과 거의 없음.

시나리오 B — 네트워크가 병목 (한국 → 미국 동부)

ping:    200ms  ████████████████████
TTFB:    550ms  ████████████████████████████████████████████████████████

→ TCP 1 RTT + TLS 1.3 1 RTT = 400ms 어치 핸드셰이크 (이론치)
   550ms 중 400ms가 핸드셰이크 → handshake가 주범
→ 서버 자체 처리 시간은 약 150ms 수준 (550 - 400)

주의: 만약 같은 200ms ping 환경에서 TTFB가 이론 핸드셰이크 비용(400ms)보다 작게 측정된다면, 그건 keep-alive로 기존 연결을 재사용했거나 0-RTT 재개가 일어난 것이다(핸드셰이크를 새로 안 함). 핸드셰이크가 매번 일어난다는 전제에서는 TTFB가 400ms 아래로 내려갈 수 없다.

진단 방향: 서버 코드 만져봐야 1ms도 안 줄어듦. CDN/Edge 배포로 거리 자체를 줄여야 함.

시나리오 C — DNS 콜드 스타트

ping:    7ms
TTFB:    430ms  (첫 방문)
TTFB:    350ms  (두 번째 방문)
         ─────
        80ms 차이 ≈ DNS 5단계 추적 비용

같은 사이트를 두 번 측정해서 차이가 70~100ms면 DNS 콜드 스타트. 매번 발생하는 건 아니지만 사용자 첫 인상에 영향.


6. 실전 측정 — curl 한 줄로 모든 단계 분해

curl -w '
DNS 조회:        %{time_namelookup}s
TCP 연결:        %{time_connect}s
TLS 핸드셰이크:   %{time_appconnect}s
서버 처리 + 첫바이트: %{time_starttransfer}s
전체 다운로드:    %{time_total}s
' -o /dev/null -s https://langco.app/

출력 해석

DNS 조회:        0.045123s    ← DNS만의 시간
TCP 연결:        0.052456s    ← DNS + TCP까지 (누적)
TLS 핸드셰이크:   0.066789s    ← + TLS까지 (누적)
서버 처리 + 첫바이트: 0.350123s ← TTFB
전체 다운로드:    0.412345s    ← 응답 본문까지

구간별 비용 계산:

DNS:  0.045 - 0     = 45ms
TCP:  0.052 - 0.045 = 7ms       ← RTT와 거의 같음
TLS:  0.066 - 0.052 = 14ms      ← 1 RTT (TLS 1.3)
서버: 0.350 - 0.066 = 284ms     ← 서버 처리 + 응답 네트워크 1 RTT (순수 서버 시간은 여기서 ping/RTT를 더 빼야 함)
본문: 0.412 - 0.350 = 62ms

같은 명령 두 번 돌리기

캐시 효과 분리:

# 1회차 (콜드)
curl -w "DNS:%{time_namelookup}s TTFB:%{time_starttransfer}s\n" -o /dev/null -s https://langco.app/
# DNS:0.045s TTFB:0.430s

# 2회차 (DNS 캐시됨)
curl -w "DNS:%{time_namelookup}s TTFB:%{time_starttransfer}s\n" -o /dev/null -s https://langco.app/
# DNS:0.000s TTFB:0.350s
#       ↑           ↑
#    DNS 캐시      서버 처리는 그대로

브라우저에서 보기

  • 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과 함께 측정하라.

                  [TTFB가 느리다는 신고]
                         │
                         ▼
              [같은 위치에서 ping 측정]
                         │
            ┌────────────┼────────────┐
        ping 작음     ping 큼      ping은 OK인데
        (< 30ms)     (> 100ms)    같은 사이트 두 번째도 느림
            │            │              │
       서버가 병목    네트워크 병목    DNS·TCP는 빠른데
            │            │           서버가 일관되게 느림
            ▼            ▼              ▼
       [서버 점검]   [거리 줄이기]   [서버 코드/DB 점검]
       - DB 쿼리     - CDN/Edge       - 캐시 도입
       - N+1         - HTTP/3         - 외부 API 병렬화
       - 외부 API    - 가까운 region   - 응답 streaming

관련 글

참고 자료

© 2026 Frank Kim. All rights reserved.