
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회라는 제한은 시스템 자원을 보호하기 위한 최소한의 가이드라인입니다. 이 수치는 서비스의 성격에 따라 운영 상황에 맞게 동적으로 조절할 수 있도록 설계하면 과부하를 방지하는데 도움이 되는 기술로 쓰일 수 있을 것 같네요.! 저는 일단 외부의 요청 횟수를 우리쪽에서 제한할 수 있다는 것을 증명하기 위해 사용해보게 되었습니다 :)
'IT > Backend | API' 카테고리의 다른 글
| HTTP보다 빠르다! Named Pipe를 이용한 프로세스 간 통신(IPC) (1) | 2026.01.25 |
|---|---|
| HMAC-SHA256을 이용한 데이터 무결성 인증 (0) | 2026.01.24 |
| 데이터비교툴 개발하기 | 엑셀로 서로다른 DB의 데이터 비교 (0) | 2025.04.01 |
| GitLab Repository API 사용방법 (Upload, Download) (2) | 2024.12.18 |