ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Rate Limiter (API 요청 제한) 설계: 시스템을 보호하는 방어막
    Tech Career/System designs 2025. 8. 10. 16:49
    반응형

    들어가며: 무분별한 요청으로부터 시스템을 지켜라

    대규모 서비스를 운영하다 보면, 예상치 못한 트래픽 폭증이나 악의적인 요청으로 인해 시스템이 마비되는 상황을 마주할 수 있습니다. 특정 사용자가 API를 과도하게 호출하거나, 봇이 무차별적으로 데이터를 긁어가는 경우 등이 대표적입니다. 이러한 상황은 시스템의 안정성을 위협하고, 모든 사용자에게 피해를 줄 수 있습니다.

    특히, 분산 서비스 거부(DDoS, Distributed Denial of Service) 공격은 수많은 IP 주소에서 대량의 요청을 보내 시스템을 마비시키는 것을 목표로 합니다. Rate Limiter(요청 제한기)는 이러한 문제를 해결하기 위한 필수적인 컴포넌트입니다. 이는 특정 단위 시간 동안 API에 접근할 수 있는 요청의 수를 제한하여, 시스템의 과부하를 막고, 서비스 품질을 안정적으로 유지하는 역할을 합니다.

    하지만 Rate Limiter는 모든 유형의 DDoS 공격에 대응할 수는 없습니다. 주로 HTTP 계층의 애플리케이션 계층 공격에 효과적이며, 전문적인 DDoS 방어 솔루션(WAF 등)과 함께 사용될 때 더욱 강력한 방어 체계를 구축할 수 있습니다. 따라서 Rate Limiter는 시스템을 보호하는 중요한 '1차 방어선' 정도로 이해하는 것이 좋습니다. 이번 포스팅에서는 Rate Limiter가 왜 필요하고, 어떤 알고리즘으로 구현할 수 있는지, 그리고 분산 환경에서의 구현 시 고려사항은 무엇인지 자세히 살펴보겠습니다.


    1. Rate Limiter가 필요한 이유

    Rate Limiter는 단순히 시스템을 보호하는 것을 넘어, 다음과 같은 중요한 역할을 수행합니다.

    • 시스템 안정성 보장: 트래픽 폭증 시, 시스템의 리소스(CPU, 메모리, 네트워크 등)가 고갈되는 것을 방지하여 안정적인 서비스 운영을 가능하게 합니다.
    • 비용 절감: 클라우드 환경에서 사용량 기반 과금(예: API 호출 횟수)을 사용하는 경우, 불필요하거나 과도한 요청을 차단하여 비용을 절감합니다.
    • 서비스 남용 방지: 악성 봇이나 스크래핑(Scraping) 등 서비스의 무분별한 사용을 막아, 서비스 품질을 보호합니다.
    • 공정한 사용 보장: 모든 사용자가 시스템 리소스를 공평하게 사용할 수 있도록 보장합니다.

    2. Rate Limiter의 핵심 알고리즘

    Rate Limiter를 구현하는 데에는 여러 가지 알고리즘이 있습니다. 각 알고리즘은 장단점이 명확하므로, 시스템의 요구사항에 따라 적절한 것을 선택해야 합니다.

    2.1. 고정 윈도우 카운터 (Fixed Window Counter)

    • 원리: 특정 시간(예: 1분)을 윈도우(Window)로 설정하고, 윈도우가 시작될 때마다 카운터를 0으로 초기화합니다. 윈도우 내에서 요청이 들어올 때마다 카운터를 증가시키고, 미리 정해둔 임계값에 도달하면 요청을 차단합니다.
    • 장점: 구현이 매우 간단하고 직관적입니다.
    • 단점: '경계 문제(Edge Case)'가 발생할 수 있습니다. 예를 들어, 1분당 100건 제한이 있을 때, 윈도우가 끝나는 시점(00:59)에 100건, 다음 윈도우가 시작하는 시점(01:00)에 100건의 요청이 몰리면, 사실상 1초 사이에 200건의 요청이 허용되어 시스템에 과부하를 줄 수 있습니다.

    2.2. 슬라이딩 윈도우 로그 (Sliding Window Log)

    • 원리: 고정 윈도우 카운터의 경계 문제를 해결하는 알고리즘입니다. 요청이 들어올 때마다 타임스탬프(Timestamp)를 로그로 기록합니다. 새로운 요청이 들어오면, 현재 시각을 기준으로 설정된 (가장 최근)윈도우 시간 내에 들어온 로그의 개수를 세어 임계값과 비교합니다.
    • 장점: 정확한 요청 수를 기록하므로, 고정 윈도우의 경계 문제를 해결하여 부하를 더 균일하게 제어할 수 있습니다.
    • 단점: 메모리 사용량이 많습니다. 슬라이딩 윈도우를 계속 추적하기 위해 모든 요청의 타임스탬프를 저장해야 하므로, 트래픽이 많을수록 리소스 소모가 커집니다.

    2.3. 토큰 버킷 (Token Bucket)

    • 원리: 미리 정해진 속도(예: 초당 5개)로 토큰이 생성되는 '버킷(Bucket)'을 사용합니다. 요청이 들어올 때마다 버킷에서 토큰을 하나씩 사용합니다. 버킷에 토큰이 없으면 요청은 차단됩니다. 버킷의 최대 크기는 버스트(Burst) 트래픽을 처리할 수 있는 임계값을 설정합니다. (Pooling을 생각하시면 비슷한 개념으로 이해할 수 있습니다.)
    • 장점:
      • 버스트 트래픽 처리: 버킷에 토큰이 쌓여있다면, 짧은 시간 동안 많은 요청을 허용할 수 있습니다.
      • 효율적인 리소스 사용: 토큰이 생성되는 속도가 일정하여 시스템에 균일한 부하를 줄 수 있습니다.
    • 단점: 토큰 생성이 유휴(Idle) 상태에서도 지속되어, 리소스가 낭비될 수 있습니다.
      • 주로 버스트 트래픽이 자주 발생하고, 유휴상태에 머무는 경우가 드문 시스템에 적합합니다.

    2.4. 리키 버킷 (Leaky Bucket)

    • 원리: 들어오는 요청을 버킷에 담고, 버킷에서 미리 정해진 속도(예: 초당 5개)로 요청을 처리합니다. 버킷이 가득 차면 추가 요청은 거부됩니다.
    • 장점: 처리 속도가 일정하므로, 시스템의 백엔드에 항상 균일한 부하를 전달하여 안정성을 높입니다.
    • 단점: 버스트 트래픽을 처리하지 못하고, 들어온 요청을 모두 버킷에 담아야 하므로, 트래픽이 많을 경우 버킷이 쉽게 가득 차게 됩니다.
      • 주로 트래픽이 대부분 균일하거나 증감 속도가 느릴때 적합합니다.

    3. 분산 환경에서의 Rate Limiter 설계

    단일 서버 환경에서는 위 알고리즘들을 메모리에 구현하는 것으로 충분하지만, 여러 서버로 구성된 분산 시스템에서는 동시성 문제가 발생합니다.

    • 문제점: 여러 서버에 들어온 요청이 동일한 카운터를 동시에 업데이트하려고 할 때, 데이터 불일치가 발생할 수 있습니다.
    • 해결책: 중앙 집중식 카운터 사용:
      • 모든 서버가 공유하는 중앙 집중식 카운터를 사용합니다. Redis와 같은 인메모리 데이터베이스를 활용하는 것이 일반적입니다.
      • Redis의 INCR 명령어는 원자적(Atomic) 연산을 보장하므로, 여러 서버에서 동시에 카운터를 증가시켜도 데이터 정합성 문제를 안전하게 해결할 수 있습니다.
    • 설계 과정:
      1. API 요청이 들어오면, 각 서버는 Redis에 저장된 해당 사용자의 카운터를 INCR 명령어로 증가시킵니다.
      2. INCR 명령어가 반환하는 값과 미리 설정된 제한(Limit)을 비교하여 요청 허용 여부를 결정합니다.
      3. 카운터의 만료 시간(Expiration Time)을 윈도우 시간으로 설정하여, 정해진 시간이 지나면 카운터가 자동으로 초기화되도록 합니다. (예: EXPIRE 명령어)
    • 트레이드오프:
      • 장점: 분산 환경에서도 정확하고 일관된 요청 제한을 구현할 수 있습니다.
      • 단점: 모든 API 요청이 Redis로 카운터를 업데이트해야 하므로, Redis 서버에 병목 현상이 발생할 수 있습니다.

    4. 분산 Rate Limiter의 고도화: 트레이드오프와 최적화

    중앙 집중식 카운터 방식의 한계를 극복하기 위해 다음과 같은 방법을 고려할 수 있습니다.

    • 분산 Redis 클러스터: Redis 서버를 클러스터로 구성하여 부하를 분산시키고, 확장성을 확보합니다.
    • 로컬 캐시 + 중앙 캐시: 각 서버에 로컬 캐시를 두어, 중앙 캐시에 대한 요청 수를 줄입니다. 하지만 이 경우 분산 환경에서 완벽한 동기화를 보장하기 어려워, 약간의 오버카운팅(Over-counting)을 감수해야 할 수 있습니다.
    • 이중 Rate Limiter: Edge Layer(API Gateway)에서 가벼운 Rate Limiter를 먼저 적용하고, 백엔드 서비스 단에서 더 엄격한 Rate Limiter를 적용하여 부하를 분산시키는 전략입니다.

    마치며: 시스템을 위한 필수 방어 전략

    Rate Limiter는 단순히 API 요청을 제한하는 기능을 넘어, 시스템의 안정성, 공정성, 그리고 비용 효율성을 확보하는 필수적인 방어 전략입니다. 단일 서버 환경에서는 간단한 구현으로 충분하지만, 분산 시스템에서는 동시성 문제와 성능 병목을 해결하기 위한 심도 있는 설계가 필요합니다. 무엇보다, 시스템 디자인 면접에서 질문이 들어올 경우에, rate limiting은 시스템의 자원, 성능, 서비스 트래픽의 성격에 따라 다양한 전략이 가능하므로, 유연하게 설계할 수 있는게 중요합니다. (하나의 정석이 모든 경우에 효과적이진 않다는 것이죠.)

    다음 포스팅에서는 대규모 서비스 설계의 단골 문제인 URL Shortener(URL 단축 서비스) 설계를 통해, 지금까지 배운 모든 개념들을 종합적으로 활용해 보는 시간을 갖겠습니다. 오늘 배운 Rate Limiter도 URL Shortener 시스템을 보호하는 중요한 컴포넌트로 활용될 수 있습니다.

    궁금한 점이 있으시다면 언제든지 질문해 주십시오!

    반응형

    댓글

Designed by naubull2.