작성일 :

애플리케이션에서 네트워크 성능을 어떻게 개선하는가

Part 1에서 지연의 구성 요소를,

Part 2에서 TCP 혼잡 제어를 살펴보았습니다.


이제 애플리케이션 레벨에서 할 수 있는 최적화를 알아봅니다.


HTTP 최적화

Keep-Alive (Persistent Connections)

HTTP/1.0은 요청마다 새 TCP 연결을 만들었습니다.

1
2
3
4
HTTP/1.0 (연결 재사용 안 함):
요청 1: TCP 핸드셰이크 + 요청/응답 + 연결 종료
요청 2: TCP 핸드셰이크 + 요청/응답 + 연결 종료
요청 3: TCP 핸드셰이크 + 요청/응답 + 연결 종료


Keep-Alive는 연결을 재사용하며, HTTP/1.1에서 기본으로 활성화되어 있습니다.


1
2
3
4
5
6
HTTP/1.1 (Keep-Alive):
TCP 핸드셰이크 (1회)
요청 1 → 응답 1
요청 2 → 응답 2
요청 3 → 응답 3
연결 종료


핸드셰이크 비용 절감:

  • TCP 3-way 핸드셰이크: 1 RTT
  • TLS 핸드셰이크: 1-2 RTT
  • 연결당 2-3 RTT 절약


압축

응답 본문을 압축하여 전송량을 줄입니다.


1
2
3
4
5
# 요청 헤더
Accept-Encoding: gzip, deflate, br

# 응답 헤더
Content-Encoding: gzip


주요 압축 방식:

  • gzip: 가장 널리 지원
  • deflate: gzip과 유사
  • br (Brotli): 더 높은 압축률, 현대 브라우저 지원


텍스트 콘텐츠(HTML, CSS, JS, JSON)에 효과적:

  • 50-80% 크기 감소 가능


HTTP/2 다중화

HTTP/1.1의 문제: Head-of-Line Blocking


1
2
3
4
5
6
7
8
HTTP/1.1:
연결 1: 요청A ──────────► 응답A (느림)
         요청B는 응답A를 기다림

해결책: 병렬 연결 (브라우저는 도메인당 6개)
연결 1: 요청A ──────────► 응답A
연결 2: 요청B ──► 응답B
연결 3: 요청C ──────► 응답C


HTTP/2는 하나의 연결에서 여러 스트림을 다중화합니다.


1
2
3
4
5
6
7
HTTP/2:
하나의 연결:
  스트림 1: 요청A ──────────────► 응답A
  스트림 2: 요청B ──► 응답B
  스트림 3: 요청C ──────► 응답C

프레임 단위로 인터리빙


HTTP/2의 추가 기능:

  • 헤더 압축 (HPACK): 중복 헤더 제거
  • 서버 푸시: 요청 전에 리소스 전송
  • 우선순위: 중요한 리소스 먼저


HTTP/3과 QUIC

HTTP/2도 TCP의 Head-of-Line Blocking 문제가 있어, 패킷 손실 시 모든 스트림이 대기해야 합니다.


QUIC은 UDP 위에 구현된 전송 프로토콜입니다.


1
2
3
4
5
6
7
8
9
10
┌─────────────────────────────────────────┐
│              HTTP/3                     │
├─────────────────────────────────────────┤
│              QUIC                       │
│   - 다중 스트림 (독립적)                │
│   - 내장 TLS 1.3                        │
│   - 0-RTT 재연결                        │
├─────────────────────────────────────────┤
│              UDP                        │
└─────────────────────────────────────────┘


QUIC의 장점:

  • 스트림 독립성: 한 스트림 손실이 다른 스트림에 영향 없음
  • 빠른 연결 설정: 1-RTT (TLS 포함), 0-RTT 재연결
  • 연결 마이그레이션: IP 변경 시에도 연결 유지

연결 최적화

Connection Pooling

애플리케이션 레벨에서 연결을 재사용합니다.


1
2
3
4
5
6
7
8
9
연결 풀:
┌─────────────────────────────────────────┐
│  연결 1: 유휴 (사용 가능)              │
│  연결 2: 사용 중                        │
│  연결 3: 유휴 (사용 가능)              │
│  연결 4: 사용 중                        │
└─────────────────────────────────────────┘

요청 → 풀에서 유휴 연결 획득 → 사용 → 반환


데이터베이스, HTTP 클라이언트에서 흔히 사용합니다.


TCP Fast Open (TFO)

첫 번째 SYN 패킷에 데이터를 포함합니다.


1
2
3
4
5
6
7
8
9
일반 TCP:
SYN ────────────►
◄──────── SYN-ACK
ACK + 데이터 ───►  (3번째 패킷에서야 데이터)

TCP Fast Open:
SYN + 쿠키 + 데이터 ──►  (첫 패킷에 데이터!)
◄───────── SYN-ACK + 응답
ACK ────────────►


이미 쿠키가 있는 경우 1 RTT를 절약할 수 있습니다.


0-RTT 재연결

TLS 1.3과 QUIC에서 지원합니다.


이전 연결의 키를 사용하여 첫 패킷부터 암호화된 데이터를 전송합니다.


주의: 재전송 공격 가능성

0-RTT 데이터는 멱등성 있는 요청에만 사용해야 합니다.


캐싱 전략

브라우저 캐시

1
2
3
4
5
6
7
# 응답 헤더
Cache-Control: max-age=3600  # 1시간 캐시
Cache-Control: no-cache      # 매번 검증
Cache-Control: no-store      # 캐시 안 함

ETag: "abc123"               # 리소스 식별자
Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT


조건부 요청:

1
2
3
4
5
6
7
# 클라이언트 요청
If-None-Match: "abc123"
If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT

# 서버 응답 (변경 없음)
304 Not Modified
(본문 없음, 대역폭 절약)


CDN 캐시

전 세계 에지 서버에 콘텐츠를 캐시합니다.


1
2
3
4
5
사용자 (서울) ──► CDN 에지 (서울) ──► 원본 서버 (미국)
                     │
                캐시 있으면
                여기서 응답
                (RTT 감소)


캐시 무효화 전략

버전/해시 기반 URL:

1
2
3
# 파일 변경 시 URL 변경
/static/app.abc123.js  (max-age=1년)
/static/app.def456.js  (새 버전)


긴 캐시 시간 + URL 변경 = 즉시 갱신 가능


프리페칭과 프리커넥트

DNS Prefetch

다음에 필요할 도메인의 DNS를 미리 조회합니다.


1
<link rel="dns-prefetch" href="//api.example.com">


Preconnect

TCP + TLS 연결을 미리 수립합니다.


1
<link rel="preconnect" href="https://fonts.googleapis.com">


Prefetch

다음 페이지에 필요한 리소스를 미리 가져옵니다.


1
<link rel="prefetch" href="/next-page.html">


Preload

현재 페이지에 필요한 리소스를 높은 우선순위로 가져옵니다.


1
2
<link rel="preload" href="/critical.css" as="style">
<link rel="preload" href="/hero.jpg" as="image">

프로토콜 선택

TCP vs UDP

특성 TCP UDP
신뢰성 보장 없음
순서 보장 없음
연결 필요 불필요
오버헤드 높음 낮음
사용 사례 웹, 이메일 스트리밍, 게임, DNS


QUIC의 장점

  • TCP의 신뢰성 + UDP의 유연성
  • 빠른 연결 설정
  • 모바일 로밍에 유리
  • HTTP/3의 기반


gRPC

HTTP/2 기반의 RPC 프레임워크로, 다음과 같은 특징이 있습니다.


  • Protocol Buffers (효율적인 직렬화)
  • 양방향 스트리밍
  • 강타입 인터페이스


이러한 특징으로 마이크로서비스 간 통신에 적합합니다.


WebSocket

HTTP 연결을 업그레이드하여 양방향 통신을 제공합니다.


1
2
3
4
5
6
7
8
클라이언트                 서버
    │                        │
    │ ── HTTP Upgrade ─────► │
    │ ◄── 101 Switching ─── │
    │                        │
    │ ◄────── 데이터 ──────► │
    │ ◄────── 데이터 ──────► │
    │   (양방향, 실시간)     │


채팅이나 알림 같은 실시간 애플리케이션에 적합합니다.


측정과 모니터링

핵심 지표

TTFB (Time To First Byte):

  • 요청 시작부터 첫 바이트 수신까지
  • 서버 응답 시간 지표


LCP (Largest Contentful Paint):

  • 가장 큰 콘텐츠가 렌더링된 시간
  • 사용자 인지 로딩 완료


FCP (First Contentful Paint):

  • 첫 번째 콘텐츠 렌더링 시간


CLS (Cumulative Layout Shift):

  • 레이아웃 이동 정도
  • 시각적 안정성


실제 사용자 모니터링 (RUM)

실제 사용자 환경에서의 성능 데이터를 수집합니다.


1
2
3
4
// Performance API
const timing = performance.getEntriesByType('navigation')[0];
console.log('TTFB:', timing.responseStart - timing.requestStart);
console.log('DOM Content Loaded:', timing.domContentLoadedEventEnd);


합성 모니터링

시뮬레이션된 사용자를 통해 정기적으로 성능을 측정합니다.


대표적인 도구로 Lighthouse, WebPageTest, Pingdom 등이 있습니다.


정리: 측정 없이 최적화 없다

애플리케이션 레벨 최적화 체크리스트:


연결 최적화:

  • HTTP/2 또는 HTTP/3 사용
  • Keep-Alive 활성화
  • Connection Pooling


전송 최적화:

  • 압축 (gzip, Brotli)
  • 캐싱 전략 수립
  • CDN 활용


로딩 최적화:

  • Critical CSS 인라인
  • JavaScript 지연 로딩
  • 이미지 최적화/지연 로딩
  • Preconnect/Prefetch


측정:

  • Core Web Vitals 모니터링
  • RUM 설정
  • 목표 설정 및 추적


1
2
"측정하지 않으면 개선할 수 없다."
                - Peter Drucker

관련 글

Tags: HTTP, QUIC, 네트워크, 성능, 최적화, 캐싱

Categories: ,