LEVEL 55 QUEST
API Rate Limiting & 쿼터 관리
Rate Limiting 알고리즘, PHP 미들웨어 구현, API 키 관리, 쿼터 대시보드를 구축합니다.
Rate Limiting 알고리즘
왜 Rate Limiting이 필요한가?
API Rate Limiting은 세 가지 목적을 가집니다:
1. 서버 보호 - 과도한 요청으로부터 인프라 보호
2. 공정한 사용 - 한 테넌트가 리소스를 독점하지 않도록 방지
3. 수익화 - 요금제별 차등 한도로 업그레이드 유도
1. 서버 보호 - 과도한 요청으로부터 인프라 보호
2. 공정한 사용 - 한 테넌트가 리소스를 독점하지 않도록 방지
3. 수익화 - 요금제별 차등 한도로 업그레이드 유도
Token Bucket 알고리즘
버킷에 일정 속도로 토큰이 채워지고, 요청마다 토큰을 소비합니다. 버킷이 비면 요청이 거부됩니다. 순간적인 버스트를 허용하는 장점이 있습니다.
Sliding Window 알고리즘
현재 시점으로부터 일정 시간 창(예: 1분) 내의 요청 수를 계산합니다. 고정 윈도우보다 정확하고, Token Bucket보다 구현이 직관적입니다.
| 특성 | Token Bucket | Sliding Window |
|---|---|---|
| 버스트 허용 | 허용 (버킷 크기만큼) | 제한적 |
| 메모리 사용 | 낮음 (카운터 2개) | 보통 (타임스탬프 저장) |
| 정확도 | 보통 | 높음 |
| 구현 복잡도 | 보통 | 낮음 |
| DB 기반 구현 | 적합 | 매우 적합 |
PHP 미들웨어로 Rate Limiter 구현
-- Rate Limiting 테이블 CREATE TABLE rate_limits ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, identifier VARCHAR(255) NOT NULL, -- API 키 또는 tenant_id endpoint VARCHAR(100) NOT NULL DEFAULT '*', -- 엔드포인트별 제한 tokens DECIMAL(10,2) NOT NULL, -- 남은 토큰 max_tokens INT UNSIGNED NOT NULL, -- 최대 토큰 (버킷 크기) refill_rate DECIMAL(10,4) NOT NULL, -- 초당 토큰 충전량 last_refill DATETIME(3) NOT NULL, -- 마지막 충전 시각 (밀리초) UNIQUE KEY uq_identifier_endpoint (identifier, endpoint), INDEX idx_identifier (identifier) );
// RateLimiter.php - Token Bucket 기반 Rate Limiter class RateLimiter { private PDO $pdo; // 요금제별 Rate Limit 설정 private const PLAN_LIMITS = [ 'free' => ['max_tokens' => 10, 'refill_rate' => 0.1667], // 10req/분, 버스트 10 'basic' => ['max_tokens' => 60, 'refill_rate' => 1.0], // 60req/분, 버스트 60 'pro' => ['max_tokens' => 300, 'refill_rate' => 5.0], // 300req/분, 버스트 300 ]; public function __construct(PDO $pdo) { $this->pdo = $pdo; } /** * 요청 허용 여부 확인 (Token Bucket) * 반환값: ['allowed' => bool, 'remaining' => int, ...] */ public function attempt(string $identifier, string $planName, string $endpoint = '*'): array { $limits = self::PLAN_LIMITS[$planName] ?? self::PLAN_LIMITS['free']; $now = microtime(true); $this->pdo->beginTransaction(); try { // 현재 버킷 상태 조회 (행 잠금) $stmt = $this->pdo->prepare(" SELECT tokens, max_tokens, refill_rate, last_refill FROM rate_limits WHERE identifier = :id AND endpoint = :ep FOR UPDATE "); $stmt->execute([':id' => $identifier, ':ep' => $endpoint]); $bucket = $stmt->fetch(PDO::FETCH_ASSOC); if (!$bucket) { // 첫 요청: 버킷 생성 $stmt = $this->pdo->prepare(" INSERT INTO rate_limits (identifier, endpoint, tokens, max_tokens, refill_rate, last_refill) VALUES (:id, :ep, :tokens, :max, :rate, NOW(3)) "); $stmt->execute([ ':id' => $identifier, ':ep' => $endpoint, ':tokens' => $limits['max_tokens'] - 1, // 현재 요청분 차감 ':max' => $limits['max_tokens'], ':rate' => $limits['refill_rate'], ]); $this->pdo->commit(); return [ 'allowed' => true, 'remaining' => $limits['max_tokens'] - 1, 'limit' => $limits['max_tokens'], 'reset' => 60, ]; } // 경과 시간에 따른 토큰 충전 $lastRefill = strtotime($bucket['last_refill']); $elapsed = $now - $lastRefill; $newTokens = $elapsed * (float)$bucket['refill_rate']; $tokens = min( (float)$bucket['max_tokens'], (float)$bucket['tokens'] + $newTokens ); if ($tokens 1) { // 토큰 부족: 요청 거부 $this->pdo->commit(); $retryAfter = (int)ceil((1 - $tokens) / (float)$bucket['refill_rate']); return [ 'allowed' => false, 'remaining' => 0, 'limit' => (int)$bucket['max_tokens'], 'retry_after' => $retryAfter, ]; } // 토큰 소비 $tokens -= 1; $stmt = $this->pdo->prepare(" UPDATE rate_limits SET tokens = :tokens, last_refill = NOW(3) WHERE identifier = :id AND endpoint = :ep "); $stmt->execute([ ':tokens' => $tokens, ':id' => $identifier, ':ep' => $endpoint, ]); $this->pdo->commit(); return [ 'allowed' => true, 'remaining' => (int)floor($tokens), 'limit' => (int)$bucket['max_tokens'], 'reset' => 60, ]; } catch (Exception $e) { $this->pdo->rollBack(); throw $e; } } }
🔒
여기까지는 미리보기입니다
API Rate Limiting & 쿼터 관리
무료 가입하면 이어서 볼 수 있고, 강의를 완료할 때마다 XP와 레벨이 쌓입니다.
Google로 3초 만에 시작 →🧵 Threads로 시작무료 공개 강의 둘러보기 (Lv.1~3)무료 가입하면 이어서 볼 수 있고, 강의를 완료할 때마다 XP와 레벨이 쌓입니다.