Rate Limiting

Rate Limiting은 MSA(Microservices Architecture) 환경에서 시스템 보안과 안정성을 유지하기 위한 핵심 기술로, 과도한 트래픽으로 인한 서비스 장애 방지악성 공격 차단을 목표로 한다.
클라이언트/서비스 간 요청 처리량을 제어하는 메커니즘으로, 특히 API 기반 마이크로서비스 통신에서 중요하다.

Rate Limiting은 단순 트래픽 제어를 넘어 마이크로서비스 생태계의 안전벨트 역할을 수행한다.
2025년 현재, 주요 클라우드 제공업체들은 AI 기반 예측 차단 기능을 표준으로 제공하며, 이는 시스템 보안 설계 시 필수 요소로 자리잡았다. 효과적 구현을 위해서는 서비스 특성에 맞는 알고리즘 선택과 지속적 모니터링 체계 수립이 관건이다.

Rate Limiting의 핵심 목적

  1. 서비스 안정성 보장:
    단일 서비스의 과부하로 인한 전체 시스템 장애 방지 (예: 초당 1,000회 이상 요청 시 차단)
  2. 악성 공격 차단:
    • DDoS 공격: 초당 수만 건 요청 탐지 및 차단
    • 무차별 대입 공격(Brute-force): IP당 분당 5회 로그인 시도 제한
  3. 리소스 공정 분배:
    API 사용자별 할당량 관리 (예: 유료/무료 계층별 차등 처리)

동작 원리와 주요 알고리즘

기본 작동 흐름

sequenceDiagram
    Client->>+서버: 요청 전송
    서버->>Rate Limiter: 요청 카운트 증가
    Rate Limiter-->>서버: 허용 여부 판단
    alt 허용
        서버->>+비즈니스 로직: 처리
    else 거부
        서버-->>-Client: 429 Too Many Requests
    end

주요 제한 알고리즘 비교

알고리즘특징장점단점사용 사례
고정 창(Fixed Window)지정 시간 내 요청 수 제한구현 간편창 경계 부근 트래픽 폭주 가능단순 API
슬라이딩 창(Sliding Window)이동 시간 창 기반 계산부하 분산 효과저장 공간 요구실시간 트래픽
토큰 버킷(Token Bucket)가상 토큰 소비 방식버스트 트래픽 허용복잡한 구현스트리밍 서비스
누수 버킷(Leaky Bucket)고정 처리율 유지예측 가능한 처리량지연 발생 가능금융 거래 시스템

MSA 환경 구현 전략

계층별 적용 방식

  1. API Gateway 레벨:
    • Kong, AWS API Gateway에서 전역 정책 설정
    • 클라이언트 IP/API 키별 할당량 관리
  2. 서비스 메시 레벨:
    Istio의 EnvoyFilter를 활용한 서비스 간 호출 제한
  3. 분산 추적 시스템 연동:
    Redis Cluster를 이용한 분산 카운터 관리

구현 예시

  1. API Gateway 레벨에서의 구현:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    
    @Configuration
    public class GatewayConfig {
        @Bean
        public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
            return builder.routes()
                .route("user_service_route", r -> r
                    .path("/api/users/**")
                    .filters(f -> f
                        .requestRateLimiter(config -> config
                            .setRateLimiter(redisRateLimiter())
                            .setKeyResolver(userKeyResolver())))
                    .uri("lb://user-service"))
                .build();
        }
    
        @Bean
        public RedisRateLimiter redisRateLimiter() {
            return new RedisRateLimiter(10, 20); // 초당 10개, 버스트 20개
        }
    
        @Bean
        KeyResolver userKeyResolver() {
            return exchange -> Mono.just(
                exchange.getRequest()
                    .getHeaders()
                    .getFirst("X-User-Id"));
        }
    }
    
  2. 분산 환경에서의 Rate Limiting:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    import redis
    
    class DistributedRateLimiter:
        def __init__(self, redis_client, key_prefix, limit, window):
            self.redis = redis_client
            self.key_prefix = key_prefix
            self.limit = limit
            self.window = window
    
        def is_allowed(self, user_id):
            key = f"{self.key_prefix}:{user_id}"
            pipeline = self.redis.pipeline()
    
            now = time.time()
            pipeline.zadd(key, {now: now})
            pipeline.zremrangebyscore(key, 0, now - self.window)
            pipeline.zcard(key)
            pipeline.expire(key, self.window)
            results = pipeline.execute()
    
            request_count = results[2]
            return request_count <= self.limit
    
  3. Nginx를 활용

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    http {
        # 공유 메모리 영역 정의: 각 클라이언트 IP당 초당 10개의 요청을 허용
        limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;
    
        server {
            location / {
                # 정의한 영역을 사용하여 요청 제한 적용
                limit_req zone=one burst=5 nodelay;
                proxy_pass http://backend_server;
            }
        }
    }
    
    • limit_req_zone: 클라이언트의 IP 주소를 기준으로 초당 10개의 요청을 허용하는 공유 메모리 영역을 정의한다.
    • limit_req: 정의된 영역을 사용하여 요청을 제한하며, burst는 추가로 허용할 수 있는 대기 요청 수를, nodelay는 지연 없이 즉시 응답을 반환하도록 설정한다.
  4. Spring Boot 설정

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    @Configuration
    public class RateLimitConfig {
    
        @Bean
        public RateLimiterRegistry rateLimiterRegistry() {
            return RateLimiterRegistry.custom()
                .addRateLimiterConfig("user-service", 
                    RateLimiterConfig.custom()
                        .limitForPeriod(100)
                        .limitRefreshPeriod(Duration.ofMinutes(1))
                        .build())
                .build();
        }
    }
    

보안 강화 기법

  1. 동적 조정(Dynamic Rate Limiting):

    • AI 기반 이상 트래픽 탐지 시 임계값 자동 조정
    • 계절성 트래픽(예: 블랙프라이데이) 대응 자동화
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    class AdaptiveRateLimiter:
        def __init__(self, initial_limit):
            self.current_limit = initial_limit
            self.error_rate = 0
    
        def adjust_limit(self, error_rate):
            if error_rate > 0.1:  # 에러율 10% 초과
                self.current_limit = max(1, self.current_limit * 0.8)
            elif error_rate < 0.05:  # 에러율 5% 미만
                self.current_limit *= 1.2
    
  2. 다중 계층 제한:

    1
    
    사용자 → [IP 제한(1000/일)] → [API 키별(500/시간)] → [엔드포인트별(10/초)]
    
  3. 예외 처리 전략:

    • 재시도 지침 포함 HTTP 429 응답
    • Retry-After 헤더를 활용한 유예 시간 안내

모니터링과 최적화

  1. 핵심 지표:
    • Request Rate(요청율)
    • Error Rate(429 발생 비율)
    • Throttle Time(제한 적용 시간)
  2. 튜닝 가이드라인:
    • 피크 트래픽 시험: 부하 테스트로 최대 한계치 파악
    • 점진적 조정: 20%씩 제한치 증가/감소 실험
  3. 보안 로그 분석:
    차단된 요청 패턴을 통한 신규 공격 유형 식별

구현 시 고려사항

  1. 식별자 선택:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    def get_rate_limit_key(request):
        # IP 기반
        ip = request.remote_addr
    
        # 사용자 인증 기반
        user_id = request.headers.get('X-User-Id')
    
        # API 키 기반
        api_key = request.headers.get('X-API-Key')
    
        return f"rate_limit:{ip}:{user_id}:{api_key}"
    
  2. 응답 헤더 설정:

    1
    2
    3
    4
    5
    
    def set_rate_limit_headers(response, limit, remaining, reset):
        response.headers['X-RateLimit-Limit'] = str(limit)
        response.headers['X-RateLimit-Remaining'] = str(remaining)
        response.headers['X-RateLimit-Reset'] = str(reset)
        return response
    
  3. 초과 요청 처리:

    1
    2
    3
    4
    5
    6
    
    def handle_rate_limit_exceeded():
        response = {
            'error': 'Rate limit exceeded',
            'retry_after': calculate_retry_after()
        }
        return jsonify(response), 429
    

도입 시 고려사항

  • 거짓 양성 문제: 합법적 사용자 차단 방지를 위한 화이트리스트 관리
  • 비용 효율성: 클라우드 API 게이트웨이 사용 시 과금 모델 검토
  • 법적 준수: GDPR 등 규정에 따른 지역별 차등 정책 (예: EU 사용자 20% 높은 할당량)

참고 및 출처