Cute Light Pink Flying Butterfly API 과호출을 막는 방패 | Rate Limiter 설계와 구현 :: 놀면서 돈벌기
본문 바로가기
IT/Backend | API

API 과호출을 막는 방패 | Rate Limiter 설계와 구현

by esclife_ 2026. 1. 25.
반응형

 

 

Rate Limiter란 무엇인가?

Rate Limiter는 정해진 시간 동안 클라이언트가 보낼 수 있는 요청의 수를 제한하는 기술입니다.

DoS/DDoS 공격 방어를 위한 기술로 특정 사용자의 무차별적인 요청을 차단하고. 서버 CPU 및 DB 커넥션을 유지해서 자원을 보호합니다. 공정성 보장으로 특정 사용자가 API 자원을 독점하지 못하도록 방지할 수도 있는데요, 사용자별 api 호출 횟수나 패턴을 감시할 수 있습니다.

구현 전략: ConcurrentDictionary와 타임 윈도우

멀티스레드 환경인 서버에서 여러 클라이언트의 요청 횟수를 안전하게 관리하기 위해 ConcurrentDictionary를 사용했습니다.

// 클라이언트별 (요청 횟수, 마지막 요청 시간) 저장
private readonly ConcurrentDictionary<string, (int Count, DateTime Timestamp)> _clientRequests;
  • Thread-Safe: 자바의 ConcurrentHashMap과 유사하게, 여러 스레드에서 동시에 접근해도 데이터가 꼬이지 않습니다.
  • Sliding Window 방식: 설정된 _timeWindow(예: 1분)가 지나면 카운트를 리셋하여 새로운 요청을 받을 준비를 합니다.

코드 분석: IsRequestAllowed 로직

가장 핵심이 되는 '허용 여부' 판단 로직입니다.

public bool IsRequestAllowed(string clientId)
{
    var now = DateTime.UtcNow;
    // 1. 기존 기록이 없으면 새로 추가, 있으면 가져오기
    var entry = _clientRequests.GetOrAdd(clientId, (Count: 0, Timestamp: now));

    // 2. 시간 창(Time Window)이 지났는지 확인
    if (now - entry.Timestamp > _timeWindow)
    {
        entry = (Count: 1, Timestamp: now); // 리셋
    }
    else
    {
        // 3. 시간 내라면 카운트 증가 및 한도 체크
        if (entry.Count >= _maxRequests) return false; 
        entry = (Count: entry.Count + 1, Timestamp: entry.Timestamp);
    }

    _clientRequests[clientId] = entry; // 업데이트
    return true;
}

단순히 횟수를 제한하는 것보다 중요한 것은 어떤 기준으로 제한할 것인가입니다. 프로젝트 코드 내에서 이 수치가 어떻게 적용되고 동작하는지 상세히 분석해 보겠습니다.

 

1. 인스턴스 생성 및 파라미터 설정

RateLimiterService를 초기화하는 부분을 코드로 보여드릴께요. 여기서 시스템의 정책이 결정됩니다.

// Program.cs
var rateLimiter = new RateLimiterService(5, 1.0); // (최대 5회, 1.0분 동안)
  • 첫 번째 인자 (5): 허용할 최대 요청 횟수 (_maxRequests)
  • 두 번째 인자 (1.0): 제한이 적용되는 시간대 (_timeWindowInMinutes)

2. 시간 창(Time Window) 판별 로직

사용자가 요청을 보낼 때마다 서버는 이 사용자가 마지막으로 요청을 보낸 지 1분이 지났는가?를 먼저 확인합니다.

// RateLimiterService.cs 내부 로직
var now = DateTime.UtcNow;
var entry = _clientRequests.GetOrAdd(clientId, (Count: 0, Timestamp: now));

// [핵심] 현재 시간과 기록된 시간의 차이가 1분(Window)을 넘었는지 체크
if (now - entry.Timestamp > _timeWindow) 
{
    // 1분이 지났다면? 카운트를 1로 초기화하고 기준 시간을 현재로 갱신 (새로운 1분 시작)
    entry = (Count: 1, Timestamp: now);
}
else 
{
    // 1분 이내라면? 기존 카운트만 1 증가
    entry = (Count: entry.Count + 1, Timestamp: entry.Timestamp);
}

3. 차단(Deny) 조건의 실행

카운트가 올라간 후, 설정된 _maxRequests(5회)와 대조합니다.

if (entry.Count > _maxRequests) 
{
    return false; // 6번째 요청부터는 무조건 거부!
}

 


전체코드 예시

using System;
using System.Collections.Concurrent;

namespace SecurityDemoProject.AppGateway.Security.Services
{
    /// <summary>
    /// 특정 시간 창(Time Window) 동안의 API 호출 횟수를 제한합니다.
    /// (Token Bucket 알고리즘의 간소화된 버전)
    /// </summary>
    public class RateLimiterService
    {
        private readonly ConcurrentDictionary<string, (int Count, DateTime Timestamp)> _clientRequests;
        private readonly int _maxRequests;
        private readonly TimeSpan _timeWindow;

        public RateLimiterService(int maxRequests = 100, double timeWindowInMinutes = 1.0)
        {
            _clientRequests = new ConcurrentDictionary<string, (int, DateTime)>();
            _maxRequests = maxRequests;
            _timeWindow = TimeSpan.FromMinutes(timeWindowInMinutes);
        }

        /// <summary>
        /// 특정 클라이언트의 요청이 허용 한도 내에 있는지 확인합니다.
        /// </summary>
        /// <param name="clientId">요청을 보낸 클라이언트의 고유 식별자 (예: User ID 또는 IP 주소)</param>
        /// <returns>요청이 허용되면 true, 거부되면 false</returns>
        public bool IsRequestAllowed(string clientId)
        {
            var now = DateTime.UtcNow;
            var entry = _clientRequests.GetOrAdd(clientId, (Count: 0, Timestamp: now));

            // 현재 시간 창이 이전 기록의 시간 창을 벗어났으면, 카운트를 리셋합니다.
            if (now - entry.Timestamp > _timeWindow)
            {
                entry = (Count: 1, Timestamp: now);
            }
            else
            {
                // 시간 창 내에 있다면, 카운트를 증가시킵니다.
                entry = (Count: entry.Count + 1, Timestaㅁmp: entry.Timestamp);
            }

            // 새로운 값으로 업데이트 (또는 추가)
            _clientRequests[clientId] = entry;

            // 최대 요청 횟수를 초과했는지 확인합니다.
            return entry.Count <= _maxRequests;
        }
    }
}

결국 1분간 5회라는 제한은 시스템 자원을 보호하기 위한 최소한의 가이드라인입니다. 이 수치는 서비스의 성격에 따라 운영 상황에 맞게 동적으로 조절할 수 있도록 설계하면 과부하를 방지하는데 도움이 되는 기술로 쓰일 수 있을 것 같네요.! 저는 일단 외부의 요청 횟수를 우리쪽에서 제한할 수 있다는 것을 증명하기 위해 사용해보게 되었습니다 :)

반응형