대규모 트래픽 처리 가이드 – 로드밸런싱·CDN·캐싱·오토스케일링


설날 자정, 카카오 선물하기에 수백만 명이 동시에 접속합니다. 월드컵 결승전 시작과 동시에 넷플릭스에 트래픽이 폭발적으로 몰립니다. 이 상황에서 서비스가 단 1초도 멈추지 않는 것은 어떻게 가능할까요? 대규모 트래픽 처리는 단 하나의 기술이 아니라, 여러 레이어에서 동시에 작동하는 기술들의 정교한 합주입니다. 이 글에서는 서버 한 대로 운영하던 서비스가 수천만 사용자를 감당하는 인프라로 진화하는 과정을 로드밸런싱, CDN, 캐싱, 오토스케일링, 비동기 처리, 데이터베이스 분산 순서로 완벽하게 해부합니다.


목차

  1. 트래픽 폭증은 왜 위험한가 – 병목과 장애의 원리
  2. 로드밸런싱 – 요청을 나누는 첫 번째 방어선
  3. CDN과 캐싱 – 서버에 도달하기 전에 처리하기
  4. 오토스케일링 – 트래픽에 따라 서버가 스스로 늘어나는 방법
  5. 비동기 처리와 메시지 큐 – 터지지 않는 버퍼 만들기
  6. 데이터베이스 분산과 서킷 브레이커 – 장애 격리 전략

1. 트래픽 폭증은 왜 위험한가 – 병목과 장애의 원리

대규모 트래픽 처리 문제를 이해하려면 먼저 왜 트래픽이 증가하면 서비스가 죽는지를 알아야 합니다. 원인은 명확합니다. 모든 컴퓨터 자원에는 한계가 있고, 요청 수가 그 한계를 넘는 순간 시스템이 무너지기 때문입니다.

단일 서버의 한계 – 숫자로 보는 현실

서버 한 대가 처리할 수 있는 요청 수를 TPS(Transaction Per Second)라고 합니다. 일반적인 웹 애플리케이션 서버가 처리 가능한 TPS는 수백~수천 수준입니다.

[단일 서버 트래픽 한계 시뮬레이션]

서버 스펙: 4코어 CPU, 16GB RAM
평균 요청 처리 시간: 50ms
이론상 최대 TPS: 4 × (1000ms / 50ms) = 80 TPS

동시 접속 1,000명 → 초당 요청 500건 가정
→ 처리 가능 범위 초과 → 큐 적체 시작
→ 응답 지연 급증 → 타임아웃 발생
→ 서버 메모리 고갈 → OOM(Out of Memory) → 프로세스 종료 ❌

병목이 발생하는 3가지 레이어

대규모 트래픽 처리 실패는 항상 특정 레이어의 병목에서 시작됩니다.

병목 레이어원인증상
네트워크 레이어대역폭 포화, 연결 수 한계패킷 손실, 연결 지연
애플리케이션 레이어CPU·메모리 고갈, 스레드 고갈응답 지연, 500 에러
데이터베이스 레이어커넥션 풀 소진, 락 경합쿼리 타임아웃, 데드락

증권 거래 시스템처럼 장 시작과 마감 시점에 평상시 대비 수십 배의 트래픽이 순간적으로 집중되는 환경에서는, 온프레미스 환경의 고정 인프라로는 최대 트래픽 기준으로 상시 인프라를 확보해야 하는 막대한 비용 문제가 발생합니다. 대규모 트래픽 처리 기술은 바로 이 문제를 해결하기 위해 진화해왔습니다. Amazon Web Services


2. 로드밸런싱 – 요청을 나누는 첫 번째 방어선

**로드밸런싱(Load Balancing)**은 대규모 트래픽 처리의 가장 기본적인 전략입니다. 여러 대의 서버에 요청을 골고루 분산해, 어느 한 서버도 과부하가 걸리지 않도록 조율하는 기술입니다. 교통 정리를 하는 신호등처럼, 로드밸런서는 들어오는 요청을 가장 여유 있는 서버로 안내합니다.

로드밸런싱 알고리즘 4가지

알고리즘 1: 라운드 로빈(Round Robin) 요청을 서버에 순서대로 하나씩 배분합니다. 구현이 단순하고 서버 스펙이 동일할 때 효과적입니다.

요청 순서:   1    2    3    4    5    6
배정 서버:  A    B    C    A    B    C

알고리즘 2: 최소 연결(Least Connections) 현재 처리 중인 요청 수가 가장 적은 서버에 새 요청을 배분합니다. 처리 시간이 일정하지 않은 서비스(파일 업로드, 복잡한 API)에 적합합니다.

현재 상태:  서버A(10개 처리중) / 서버B(3개 처리중) / 서버C(7개 처리중)
새 요청 → 서버B 배정 (최소 연결)

알고리즘 3: IP 해시(IP Hash) 클라이언트 IP를 해시하여 항상 같은 서버로 연결합니다. 로그인 세션이 서버에 종속된 스티키 세션(Sticky Session) 방식에서 사용합니다.

알고리즘 4: 가중치 라운드 로빈(Weighted Round Robin) 서버 스펙이 다를 때 성능 비율에 따라 가중치를 부여합니다.

nginx

# Nginx 로드밸런서 설정 예시
upstream backend {
    # 가중치 라운드 로빈: 고사양 서버에 더 많은 요청 배분
    server 10.0.0.1:8080 weight=5;   # 5/8 비율
    server 10.0.0.2:8080 weight=2;   # 2/8 비율
    server 10.0.0.3:8080 weight=1;   # 1/8 비율

    # 헬스체크: 2회 실패 시 서버 제외, 30초마다 복구 확인
    keepalive 32;
}

server {
    listen 80;
    location / {
        proxy_pass http://backend;
        proxy_connect_timeout 3s;
        proxy_read_timeout 10s;
    }
}

L4 vs L7 로드밸런서

로드밸런서는 OSI 계층에 따라 두 종류로 나뉩니다.

구분L4 로드밸런서L7 로드밸런서
동작 계층전송 계층 (TCP/UDP)애플리케이션 계층 (HTTP)
라우팅 기준IP 주소 + 포트 번호URL 경로, 헤더, 쿠키
처리 속도매우 빠름상대적으로 느림
기능단순 분산콘텐츠 기반 라우팅
대표 제품AWS NLB, HAProxyNginx, AWS ALB
[L7 로드밸런서 콘텐츠 기반 라우팅]

/api/user/*    → 사용자 서비스 서버군
/api/order/*   → 주문 서비스 서버군
/api/payment/* → 결제 서비스 서버군 (별도 보안 구역)
/static/*      → CDN 또는 정적 파일 서버

로드밸런싱은 트래픽을 분산시켜 서버 과부하를 방지하고 전체적인 네트워크 성능을 최적화하며, 트래픽 급증 시에도 안정적인 서비스를 유지할 수 있게 합니다. Blazing CDN


3. CDN과 캐싱 – 서버에 도달하기 전에 처리하기

대규모 트래픽의 상당 부분은 “이미 만들어진 데이터를 반복적으로 요청하는 것”입니다. 매번 서버와 데이터베이스를 거쳐 같은 결과를 만드는 것은 엄청난 낭비입니다. **캐싱(Caching)**과 **CDN(Content Delivery Network)**은 이 낭비를 제거하는 전략입니다.

CDN – 전 세계 어디서든 가까운 서버에서

CDN은 전 세계 여러 지역에 **엣지 서버(Edge Server)**를 배치하고, 정적 콘텐츠(이미지, JS, CSS, 동영상)를 사용자와 가장 가까운 엣지 서버에서 직접 제공하는 인프라입니다.

[CDN 없는 경우: 서울 사용자가 미국 서버 접속]

사용자(서울) ─────────────────────► 원본 서버(미국)
                  왕복 지연: ~200ms

[CDN 있는 경우: 서울 사용자가 국내 엣지 서버 접속]

사용자(서울) ──► 엣지 서버(서울 CDN PoP) ──► 원본 서버(미국)
                  캐시 히트 시 왕복 지연: ~5ms      (최초 1회만)

여러 대의 PoP(Point of Presence) 서버에서 복사본을 저장하고 원본 서버에 로그와 정보를 제공하는 메커니즘을 통해 웹사이트 성능 및 보안 강화 효과를 얻을 수 있습니다. DAOU TECH

CDN 사용 시 만료 시한이 너무 길면 콘텐츠의 신선도가 떨어지고, 너무 짧으면 원본 서버에 빈번하게 접속하게 되므로 적절한 범위를 찾는 것이 중요합니다. 또한 CDN 자체 장애 시, 원본 서버로부터 직접 콘텐츠를 가져오도록 클라이언트를 구성하는 폴백 전략이 필요합니다. Medium

애플리케이션 캐싱 – Redis의 역할

CDN이 정적 콘텐츠를 처리한다면, Redis는 동적 데이터의 반복 계산을 방지합니다.

토스 라이브 쇼핑 서비스에서는 모든 유저에게 동일하게 보이는 Universal Data(방송 리스트, 방송 상세 정보)와 유저별로 다르게 사용되는 User-Specific Data(방송 시청 여부)로 캐싱 데이터를 분류하여 관리합니다. Toss

java

// Spring Boot + Redis 캐싱 전략 예시
@Service
public class ProductService {

    private final RedisTemplate<String, Product> redisTemplate;
    private final ProductRepository productRepository;

    // 캐시 우선 조회 패턴 (Cache-Aside / Lazy Loading)
    public Product getProduct(Long productId) {
        String cacheKey = "product:" + productId;

        // 1. Redis에서 먼저 조회
        Product cached = redisTemplate.opsForValue().get(cacheKey);
        if (cached != null) {
            return cached;  // 캐시 히트: DB 조회 없이 즉시 반환
        }

        // 2. 캐시 미스: DB에서 조회
        Product product = productRepository.findById(productId)
            .orElseThrow(() -> new ProductNotFoundException(productId));

        // 3. Redis에 저장 (TTL 10분)
        redisTemplate.opsForValue().set(cacheKey, product,
            Duration.ofMinutes(10));

        return product;
    }

    // 상품 수정 시 캐시 무효화
    @CacheEvict(value = "product", key = "#productId")
    public void updateProduct(Long productId, ProductUpdateRequest req) {
        productRepository.updateProduct(productId, req);
    }
}

캐싱 전략 3가지 비교

전략방식적합한 데이터주의점
Cache-Aside앱이 직접 캐시 관리읽기 빈도 높은 데이터캐시 콜드 스타트 주의
Write-Through쓰기 시 DB·캐시 동시 갱신일관성 중요 데이터쓰기 지연 증가
Write-Behind캐시 먼저 쓰고 DB는 나중에쓰기 빈도 높은 데이터장애 시 데이터 손실 위험

4. 오토스케일링 – 트래픽에 따라 서버가 스스로 늘어나는 방법

로드밸런서와 캐시만으로도 한계가 있습니다. 트래픽이 처리 용량 자체를 초과하면, 서버를 더 추가해야 합니다. **오토스케일링(Auto Scaling)**은 이 과정을 사람의 개입 없이 자동으로 처리합니다.

수직 확장 vs 수평 확장

서버 용량을 늘리는 방법은 두 가지입니다.

[수직 확장 (Scale Up)]
서버 1대:  2코어 → 8코어 → 32코어
           8GB  → 32GB → 256GB
장점: 구현 단순, 코드 변경 불필요
단점: 한계가 명확(물리적 최대치), 단일 장애 지점(SPOF)

[수평 확장 (Scale Out)]
서버 1대 → 서버 4대 → 서버 20대 (필요 시 즉시 추가)
장점: 이론상 무한 확장, 장애 격리
단점: 상태 공유 문제(세션), 분산 설계 필요

대규모 서비스는 **수평 확장(Scale Out)**을 기본으로 설계합니다. 서버 한 대가 죽어도 나머지가 계속 동작하고, 트래픽이 줄면 서버 수를 줄여 비용을 절감할 수 있기 때문입니다.

오토스케일링 작동 원리

오토스케일링은 CPU, 메모리, 디스크, 네트워크 트래픽과 같은 시스템 자원들의 메트릭(Metric) 값을 모니터링하여 서버 사이즈를 자동으로 조절합니다. 자동 조절 정책은 서버들의 묶음 단위인 오토스케일링 그룹(Auto-Scaling Group)에 연결되어 서비스가 유휴 상태일 때는 서버의 개수를 최소로 유지하고 부하가 발생하면 최대로 늘려 안정적이고 유연한 서비스를 구현합니다. S-core

[오토스케일링 동작 시퀀스]

① 메트릭 수집 (CloudWatch / Prometheus)
   CPU 사용률 85% 초과 → 경보 발생

② 스케일아웃 정책 트리거
   "CPU 80% 초과 5분 지속 → 인스턴스 2대 추가"

③ 신규 인스턴스 프로비저닝 (30초~2분)
   AMI 이미지 기반 인스턴스 생성
   → 헬스체크 통과
   → 로드밸런서 타깃 그룹에 자동 등록

④ 트래픽 분산 재조정
   로드밸런서가 신규 인스턴스 포함 분산 시작

⑤ 트래픽 감소 시 스케일인
   CPU 20% 미만 10분 지속 → 인스턴스 1대 제거
   (드레이닝: 기존 연결 처리 완료 후 제거)

Kubernetes HPA를 활용한 컨테이너 오토스케일링

현대 대규모 서비스는 Kubernetes의 **HPA(Horizontal Pod Autoscaler)**를 통해 컨테이너 단위로 오토스케일링합니다.

yaml

# HPA 설정 예시: CPU 기반 자동 파드 확장
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: order-service-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: order-service
  minReplicas: 3      # 최소 파드 수 (항상 3개 유지)
  maxReplicas: 50     # 최대 파드 수 (트래픽 폭증 대비)
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70   # CPU 70% 초과 시 스케일아웃
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80   # 메모리 80% 초과 시 스케일아웃

5. 비동기 처리와 메시지 큐 – 터지지 않는 버퍼 만들기

트래픽 폭증 시 모든 요청을 즉시 처리하려 하면 시스템이 무너집니다. 비동기 처리와 **메시지 큐(Message Queue)**는 요청을 일단 안전하게 받아두고, 시스템 여유에 따라 순차적으로 처리하는 완충재 역할을 합니다.

동기 vs 비동기 처리 비교

[동기 처리 – 한 줄로 선 기차]

요청 1 → 처리 → 응답
요청 2 →  (대기)  → 처리 → 응답
요청 3 →  (대기)       → 처리 → 응답
트래픽 급증 시: 대기열 폭증 → 타임아웃 → 서버 다운

[비동기 처리 – 메시지 큐 도입]

요청 1 → 큐에 저장 → 즉시 응답("접수완료") → 워커가 나중에 처리
요청 2 → 큐에 저장 → 즉시 응답
요청 3 → 큐에 저장 → 즉시 응답
트래픽 급증 시: 큐가 쌓이지만 서버는 여유 있게 순차 처리

Apache Kafka를 활용한 이벤트 스트리밍

대규모 서비스에서 가장 많이 사용하는 메시지 큐는 Apache Kafka입니다. 초당 수백만 건의 이벤트도 안정적으로 처리할 수 있습니다.

java

// Kafka Producer: 주문 이벤트 발행
@Service
public class OrderService {

    private final KafkaTemplate<String, OrderEvent> kafkaTemplate;

    public OrderResponse createOrder(OrderRequest request) {
        // 1. 주문 DB 저장 (빠른 처리)
        Order order = orderRepository.save(Order.from(request));

        // 2. 재고 차감·결제·알림은 Kafka로 비동기 처리
        OrderEvent event = OrderEvent.builder()
            .orderId(order.getId())
            .userId(request.getUserId())
            .items(request.getItems())
            .build();

        kafkaTemplate.send("order-created", event);  // 큐에 발행

        // 3. 즉시 응답 (처리 완료를 기다리지 않음)
        return OrderResponse.accepted(order.getId());
    }
}

// Kafka Consumer: 재고 서비스가 비동기로 재고 차감
@Component
public class InventoryConsumer {

    @KafkaListener(topics = "order-created", groupId = "inventory-group")
    public void handleOrderCreated(OrderEvent event) {
        inventoryService.decreaseStock(event.getItems());
        // 처리 완료 후 다음 메시지 처리 (백프레셔 자동 조절)
    }
}

메시지 큐의 핵심 이점

이점설명
트래픽 버퍼링순간 폭증을 흡수하여 다운스트림 서비스 보호
서비스 디커플링발행자와 소비자가 독립적으로 동작
재처리 가능성처리 실패 시 메시지 재소비로 데이터 유실 방지
순서 보장파티션 내 메시지 순서 보장(Kafka)

6. 데이터베이스 분산과 서킷 브레이커 – 장애 격리 전략

아무리 서버를 많이 늘려도 데이터베이스가 병목이 되면 의미가 없습니다. 대규모 트래픽 처리의 마지막 퍼즐은 데이터베이스 분산장애 전파 차단입니다.

읽기/쓰기 분리 (Read Replica)

가장 먼저 적용하는 DB 분산 전략은 읽기/쓰기 분리입니다. 데이터베이스 요청의 70~90%는 읽기(SELECT)입니다. 쓰기는 Master DB에, 읽기는 Replica DB에 분산하면 DB 부하가 크게 줄어듭니다.

[읽기/쓰기 분리 아키텍처]

        애플리케이션 서버
              │
    ┌─────────┴──────────┐
    ▼                    ▼
쓰기 요청             읽기 요청
(INSERT/UPDATE)       (SELECT)
    │                    │
    ▼                    ▼
Master DB  ──복제──► Replica DB 1
                  ──복제──► Replica DB 2
                  ──복제──► Replica DB 3

샤딩(Sharding) – 데이터를 물리적으로 나누기

샤딩은 데이터를 여러 데이터베이스에 분할하여 저장함으로써 데이터베이스의 부하를 분산시키고, 복제는 데이터의 안정성과 가용성을 보장합니다. F-Lab & Company

[사용자 ID 기반 해시 샤딩]

사용자 수: 1억 명 → 4개 샤드로 분산

userId % 4 == 0 → Shard A (DB 서버 1)  user_id: 0, 4, 8, 12...
userId % 4 == 1 → Shard B (DB 서버 2)  user_id: 1, 5, 9, 13...
userId % 4 == 2 → Shard C (DB 서버 3)  user_id: 2, 6, 10, 14...
userId % 4 == 3 → Shard D (DB 서버 4)  user_id: 3, 7, 11, 15...

각 DB: 2,500만 건씩 분산 → 단일 DB 대비 1/4 부하

샤딩의 단점은 여러 샤드에 걸친 JOIN이 어렵고, 샤드 수를 늘릴 때 데이터 재분배 비용이 크다는 점입니다.

서킷 브레이커 – 장애 전파를 차단하는 두꺼비집

분산 시스템에서 한 서비스의 응답이 느려지면, 그것을 호출하는 모든 서비스가 연쇄적으로 느려집니다. **서킷 브레이커(Circuit Breaker)**는 전기 두꺼비집처럼, 특정 서비스 장애가 전체로 퍼지기 전에 연결을 차단합니다.

java

// Resilience4j 서킷 브레이커 설정
@Component
public class PaymentServiceClient {

    @CircuitBreaker(
        name = "paymentService",
        fallbackMethod = "paymentFallback"
    )
    @TimeLimiter(name = "paymentService")  // 2초 타임아웃
    public CompletableFuture<PaymentResult> processPayment(PaymentRequest req) {
        return CompletableFuture.supplyAsync(
            () -> externalPaymentApi.process(req)
        );
    }

    // 서킷 열림 시 폴백: 사용자에게 대체 응답 제공
    private CompletableFuture<PaymentResult> paymentFallback(
            PaymentRequest req, Exception ex) {
        log.warn("결제 서비스 장애 - 폴백 실행: {}", ex.getMessage());
        return CompletableFuture.completedFuture(
            PaymentResult.pending("결제가 지연되고 있습니다. 잠시 후 확인해주세요.")
        );
    }
}
[서킷 브레이커 3가지 상태]

CLOSED (정상)
  → 요청 통과, 실패율 모니터링
  → 실패율 50% 초과, 5초 지속
       ↓
OPEN (차단)
  → 모든 요청 즉시 폴백 반환 (서비스 호출 안 함)
  → 10초 대기 후
       ↓
HALF-OPEN (탐색)
  → 요청 일부만 통과 (복구 테스트)
  → 성공 시 CLOSED 복귀 / 실패 시 OPEN 유지

레이트 리미팅 – 특정 클라이언트의 과도한 요청 차단

Amazon Route 53, ECS와 EKS 등의 매니지드 서비스를 활용해 매일 20억 건 이상의 이벤트 데이터를 처리하는 서비스에서도 악의적이거나 비정상적인 클라이언트의 대량 요청은 별도로 차단해야 합니다. 레이트 리미팅은 클라이언트당 초당 요청 수를 제한해 시스템을 보호합니다. Amazon Web Services

python

# Redis를 활용한 슬라이딩 윈도우 레이트 리미터
import redis
import time

class RateLimiter:
    def __init__(self, redis_client, limit=100, window=60):
        self.redis = redis_client
        self.limit = limit      # 60초당 최대 100회
        self.window = window

    def is_allowed(self, user_id: str) -> bool:
        key = f"rate_limit:{user_id}"
        now = time.time()
        window_start = now - self.window

        pipe = self.redis.pipeline()
        # 오래된 요청 기록 제거
        pipe.zremrangebyscore(key, 0, window_start)
        # 현재 요청 추가
        pipe.zadd(key, {str(now): now})
        # 윈도우 내 요청 수 조회
        pipe.zcard(key)
        # 키 만료 설정
        pipe.expire(key, self.window)
        results = pipe.execute()

        request_count = results[2]
        return request_count <= self.limit

결론

대규모 트래픽 처리는 단일 기술이 아니라 여러 레이어의 전략이 쌓인 아키텍처입니다. 로드밸런서가 첫 번째 방어선으로 요청을 나누고, CDN과 캐싱이 반복 요청을 흡수하며, 오토스케일링이 용량을 자동으로 조절하고, 메시지 큐가 순간 폭증을 버퍼링하며, 서킷 브레이커가 장애 전파를 차단합니다. 이 기술들은 각각 독립적이지만, 함께 동작할 때 비로소 수천만 사용자를 단 한 번의 다운타임 없이 감당하는 시스템이 완성됩니다. 지금 운영하는 서비스의 어느 레이어가 가장 먼저 병목이 될지 점검해보세요. 그것이 다음 아키텍처 개선의 출발점입니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다