책 보며 구현하려다, AI가 다 짜줬습니다: Redis 기반 RateLimiter 3종 비교와 개발자의 역할

코딩은 많은 시행착오·2025년 6월 29일
0

back-end

목록 보기
18/18
post-thumbnail

AI는 코드를, 나는 판단을

들어가며

'가상면접사례로 배우는 대규모 시스템 설계 기초' 책에서 Rate Limiter 챕터를 읽고 직접 구현해보고 싶어서 Cursor를 활용해 실습해보았습니다.

처음에는 이런 식으로 요청했습니다:

"ratelimiter를 구현하려고 하는데,
Redis와 다양한 알고리즘을 활용해 애플리케이션 레벨에서 Rate Limiter를 구현하는 프로젝트 입니다.
사용 기술 스택 (예정)

  • Kotlin
  • Spring Boot
  • Spring Data Redis
  • Spring Annotation 기반 AOP

이런식으로 구현하려고 해, 근데 ratelimiter에도 다양한 알고리즘이 있잖아 한 두~세개정도 짜려고 하는데 (토큰 버킷은 무조건 포함) 구현해줘"

결과적으로 AI가 5분 만에 구현과 테스트 코드를 모두 완성해줬을 뿐만 아니라, 중간에 발생한 트러블슈팅까지 모두 해결해주었습니다.

이 글에서는 Redis + Lua 기반 3가지 Rate Limiting 알고리즘을 정리하고, AI 시대에 개발자가 집중해야 할 핵심 영역에 대해 공유하겠습니다.

Rate Limiter 알고리즘 비교

알고리즘특징장점단점
Token Bucket토큰을 일정 주기로 충전, 요청 시 소비유연한 트래픽 제어, 버스트 허용구현 복잡도 상승
Fixed Window고정 시간 윈도우 내 요청 수 제한구현 간단, 메모리 효율적윈도우 경계에서 스파이크 발생 가능
Sliding Window각 요청 시간을 로그로 저장 후 계산정확한 제어 가능메모리 사용량 증가

구현 상세

Token Bucket Algorithm

local bucket_key = KEYS[1]
local now = tonumber(ARGV[1])
local capacity = tonumber(ARGV[2])
local refill_rate = tonumber(ARGV[3])

local bucket_data = redis.call('HMGET', bucket_key, 'tokens', 'last_refill')
local current_tokens = tonumber(bucket_data[1]) or capacity
local last_refill = tonumber(bucket_data[2]) or now

-- 토큰 충전 계산
local time_passed = now - last_refill
local tokens_to_add = time_passed * refill_rate
current_tokens = math.min(capacity, current_tokens + tokens_to_add)

-- 요청 허용/거부 판단
if current_tokens >= 1 then
    current_tokens = current_tokens - 1
    redis.call('HMSET', bucket_key, 'tokens', current_tokens, 'last_refill', now)
    redis.call('EXPIRE', bucket_key, 3600)
    return 1
else
    return 0
end

핵심 로직:

  • 초당 refill_rate만큼 토큰 충전
  • 요청 1건당 토큰 1개 소모
  • 버스트 트래픽을 자연스럽게 처리

Fixed Window Counter

override fun isAllowed(key: String, limit: Int, windowSeconds: Long): Boolean {
    val windowKey = "fixed_window:$key"
    val now = Instant.now().epochSecond
    val windowStart = (now / windowSeconds) * windowSeconds
    
    val script = """
        local window_key = KEYS[1]
        local limit = tonumber(ARGV[1])
        local window_seconds = tonumber(ARGV[2])
        
        local current_count = tonumber(redis.call('GET', window_key)) or 0
        
        if current_count < limit then
            redis.call('INCR', window_key)
            redis.call('EXPIRE', window_key, window_seconds)
            return 1
        else
            return 0
        end
    """.trimIndent()
    
    return executeScript(script, windowKey, limit, windowSeconds) == 1L
}

특징:

  • 가장 직관적이고 구현이 간단
  • Redis 키 하나로 간단한 카운터 관리
  • 윈도우 경계에서 일시적 트래픽 집중 가능성

Sliding Window Log

local log_key = KEYS[1]
local now = tonumber(ARGV[1])
local window_start = tonumber(ARGV[2])
local limit = tonumber(ARGV[3])
local window_seconds = tonumber(ARGV[4])

-- 윈도우 밖 요청 로그 제거
redis.call('ZREMRANGEBYSCORE', log_key, 0, window_start)

-- 현재 윈도우 내 요청 수 확인
local current_count = redis.call('ZCARD', log_key)

if current_count < limit then
    redis.call('ZADD', log_key, now, now .. ':' .. math.random())
    redis.call('EXPIRE', log_key, window_seconds * 2)
    return 1
else
    return 0
end

특징:

  • ZADD로 각 요청 타임스탬프 기록
  • ZREMRANGEBYSCORE로 만료된 요청 제거
  • 가장 정확한 시간 기반 제어

성능 테스트 결과

Sliding Window Log 부하 테스트 (10초간 동시 요청)

총 요청 수: 100
성공 요청 수: 10 (제한: 10req/min)
실패 요청 수: 90
성공률: 10%
평균 응답 시간: 48ms

알고리즘별 특성 비교:

  • Token Bucket: 버스트 허용으로 사용자 경험 ↑
  • Fixed Window: 최고 성능, 단순한 API 제한용
  • Sliding Window: 정확한 제어, 정밀한 트래픽 관리 필요시

AI 시대 개발자의 역할 분담

AI가 담당한 영역개발자가 집중해야 할 영역
3가지 알고리즘 코드 구현어떤 알고리즘이 우리 서비스에 적합한가?
Lua 스크립트 자동 생성Redis 장애 시 대응 정책은?
단위/통합 테스트 코드 작성트래픽 패턴에 따른 임계값 설정
어노테이션 기반 인터페이스팀 내 도입을 위한 설득과 가이드

실제 운영 고려사항

Redis 운영 전략

# redis.conf 핵심 설정
maxmemory-policy: allkeys-lru
timeout: 300
tcp-keepalive: 60

장애 대응 패턴

// Circuit Breaker와 결합
@CircuitBreaker(name = "rate-limiter", fallbackMethod = "fallbackAllow")
fun checkRateLimit(key: String): Boolean {
    return rateLimiter.isAllowed(key, limit, window)
}

// Redis 장애 시 기본 허용
fun fallbackAllow(key: String, ex: Exception): Boolean = true

핵심 인사이트

알고리즘 선택 기준

  • API 게이트웨이: Fixed Window (성능 우선)
  • 결제/인증: Sliding Window (정확성 우선)
  • 일반 서비스: Token Bucket (사용성 우선)

운영 모니터링 포인트

  • Redis 메모리 사용률
  • 요청 거부율 추이
  • 응답 시간 분포

확장 고려사항

  • 멀티 리전 동기화
  • 분산 환경에서의 일관성
  • 사용자별 동적 임계값 조정

마무리

AI가 대부분의 구현을 도와주는 시대에, 개발자는 "무엇을 만들 것인가"와 "어떻게 운영할 것인가"에 더 집중해야 합니다.

이번 Rate Limiter 구현 경험을 통해 기술 선택의 판단력시스템 설계 역량이 개발자의 핵심 가치임을 다시 한번 확인했습니다.


전체 소스코드: GitHub Repository
질문이나 제안: 댓글이나 Issues로 자유롭게 남겨주세요!

#Redis #RateLimiter #Lua #트래픽제어 #시스템설계

0개의 댓글