우리는 HTTP 타이밍을 어떻게 측정하는가: DNS, TCP, TLS, TTFB 분석

발행: (2026년 1월 2일 오전 01:55 GMT+9)
6 min read
원문: Dev.to

Source: Dev.to

How We Measure HTTP Timing: DNS, TCP, TLS, TTFB Breakdown 커버 이미지

웹사이트의 응답 시간을 확인할 때 “847 ms”라고만 봐서는 별다른 정보를 얻을 수 없습니다. DNS 조회가 느린 건가요? 네트워크 지연인가요? 아니면 서버 자체 때문인가요? 세부적인 분석이 필요합니다.

**upsonar.io**에서는 시간이 정확히 어디에 소요되는지 보여줍니다. Go의 net/http/httptrace 패키지를 사용해 이를 구현하는 방법은 다음과 같습니다.

문제

start := time.Now()
resp, _ := http.Get("https://example.com")
fmt.Println(time.Since(start)) // 847ms

847 ms – 하지만 왜일까요? 요청 라이프사이클의 어느 부분일 수도 있습니다.

해결책

httptrace는 HTTP 요청의 모든 단계에 훅을 연결합니다. 콜백을 연결하면 각 단계가 완료될 때마다 실행됩니다:

trace := &httptrace.ClientTrace{
    DNSStart: func(_ httptrace.DNSStartInfo) {
        dnsStart = time.Now()
    },
    DNSDone: func(_ httptrace.DNSDoneInfo) {
        fmt.Printf("DNS: %v\n", time.Since(dnsStart))
    },
    // same pattern for TCP, TLS, etc.
}

req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))

각 지표가 의미하는 바

  • DNS Lookup – 도메인을 IP 주소로 해석합니다. > 100 ms는 DNS 서버 문제나 캐시가 비어 있는 경우를 나타냅니다.
  • TCP Connect – 3‑way 핸드셰이크. 주로 서버와의 물리적 거리 영향을 받습니다.
  • TLS Handshake – 암호화를 협상합니다. 일반적인 값은 50–150 ms이며, > 300 ms는 서버 또는 인증서 체인 문제를 시사합니다.
  • TTFB (Time To First Byte) – DNS, TCP, TLS 및 서버 처리 시간을 포함합니다. 응답 속도를 측정하는 표준 지표입니다.
  • Transfer – 응답 본문을 다운로드합니다.

작동 예제

package main

import (
    "crypto/tls"
    "fmt"
    "io"
    "net/http"
    "net/http/httptrace"
    "os"
    "time"
)

func main() {
    url := "https://httpbin.org/delay/2"
    if len(os.Args) > 1 {
        url = os.Args[1]
    }

    var dnsStart, tcpStart, tlsStart time.Time
    totalStart := time.Now()

    trace := &httptrace.ClientTrace{
        DNSStart: func(_ httptrace.DNSStartInfo) { dnsStart = time.Now() },
        DNSDone:  func(_ httptrace.DNSDoneInfo) { fmt.Printf("DNS:  %v\n", time.Since(dnsStart)) },

        ConnectStart: func(_, _ string) { tcpStart = time.Now() },
        ConnectDone:  func(_, _ string, _ error) { fmt.Printf("TCP:  %v\n", time.Since(tcpStart)) },

        TLSHandshakeStart: func() { tlsStart = time.Now() },
        TLSHandshakeDone:  func(_ tls.ConnectionState, _ error) { fmt.Printf("TLS:  %v\n", time.Since(tlsStart)) },

        GotFirstResponseByte: func() { fmt.Printf("TTFB: %v\n", time.Since(totalStart)) },
    }

    req, _ := http.NewRequest("GET", url, nil)
    req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))

    client := &http.Client{Transport: &http.Transport{DisableKeepAlives: true}}
    resp, err := client.Do(req)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    defer resp.Body.Close()

    transferStart := time.Now()
    io.ReadAll(resp.Body)
    fmt.Printf("Transfer: %v\n", time.Since(transferStart))
    fmt.Printf("Total: %v\n", time.Since(totalStart))
}

프로그램 실행:

go run main.go https://httpbin.org/delay/2

샘플 출력

DNS:  148.00525ms
TCP:  142.56925ms
TLS:  289.685ms
TTFB: 2.956339583s
Transfer: 72.208µs
Total: 2.95670575s

TTFB는 첫 번째 바이트가 도착하기까지 걸린 전체 시간이며, Transfer는 본문을 다운로드하는 데 걸린 시간입니다—여기서는 본문이 작아 전체 시간은 거의 TTFB와 같습니다.

더 큰 응답 예시

go run main.go https://proof.ovh.net/files/10Mb.dat

샘플 출력

DNS:  3.621333ms
TCP:  54.364208ms
TLS:  116.879041ms
TTFB: 286.073291ms
Transfer: 14.221007833s
Total: 14.507351083s

10 MB 파일의 경우 Transfer가 전체 지연 시간의 대부분을 차지합니다.

연결 재사용에 주의하세요

Go의 http.Client는 기본적으로 연결을 재사용합니다. 동일한 호스트에 대한 이후 요청은 DNS, TCP 및 TLS에 대해 0 ms가 표시되는데, 이는 연결이 이미 설정되어 있기 때문입니다.

정확한 측정을 위해 keep‑alives를 비활성화하세요:

client := &http.Client{
    Transport: &http.Transport{
        DisableKeepAlives: true,
    },
}

느린 수치가 알려주는 것

  • DNS > 100 ms → 더 빠른 DNS 제공업체를 사용해 보세요(예: Cloudflare, Google).
  • TCP > 100 ms → 서버가 사용자와 거리가 멉니다; CDN 도입을 고려하세요.
  • TLS > 200 ms → 인증서 체인을 확인하고 세션 재개를 활성화하세요.
  • TTFB > 500 ms → 백엔드 병목 현상: 느린 DB, 콜드 스타트, 무거운 처리.
  • Transfer high → 큰 페이로드; gzip/Brotli 압축을 활성화하세요.

200 ms 규칙

사용자는 약 200 ms를 초과하는 지연을 감지합니다. TTFB만으로도 이 임계값을 초과하면 프런트엔드 최적화와 관계없이 페이지가 느리게 느껴집니다.

무료이며 회원가입이 필요 없는 도구로 직접 사이트에서 시도해 보세요: upsonar.io/tools/diagnose.

Back to Blog

관련 글

더 보기 »

TCP는 메시지가 무엇인지 모른다

HTTP를 다룰 때 나는 마음 한구석에 조용한 가정을 가지고 있었다: > 내가 하나를 보내면, 반대쪽도 하나를 받는다. 그것은 당연해 보였다. Al...