Skip to content

ADR-21: 익명 사용자 Rate Limiting

English Version

날짜작성자리포지토리
2026-01-15@KubrickCodeweb

Context

문제 상황

Analyzer API는 인증 없이 공개 접근 가능하여 익명 사용자의 플랫폼 탐색 허용. 그러나 다음과 같은 남용 위험 존재:

위험영향가능성
API 남용/스크래핑서비스 성능 저하중간
리소스 고갈유료 사용자 영향 중단중-저
비용 증폭컴퓨팅 비용 증가중간

기존 아키텍처

플랫폼의 2단계 사용자 경험:

  • 인증된 사용자: 빌링 서비스를 통한 쿼터 시스템 관리 (ADR-13)
  • 익명 사용자: 본 결정 이전에는 보호 메커니즘 부재

잠재 고객 전환을 위한 플랫폼 탐색을 허용하면서 익명 사용자에 대한 스로틀링 필요.

제약 조건

제약 조건출처영향
단일 인스턴스 배포인프라분산 상태 불필요
PostgreSQL 중심ADR-04Redis 추가 시 새 의존성 도입
PaaS 우선 전략ADR-06단순하고 자체 완결적 솔루션 선호
인증된 사용자는 쿼터 보유ADR-13Rate Limiting은 익명 사용자만 대상

Decision

Analyzer API에 대한 익명 사용자용 IP 기반 인메모리 Rate Limiting 구현.

설정:

파라미터
알고리즘Fixed window
제한10 요청
윈도우1분
클라이언트 IP
범위Analyzer API
대상익명 사용자만

구현 패턴:

go
userID := middleware.GetUserID(ctx)

if userID == "" && h.anonymousRateLimiter != nil {
    clientIP := middleware.GetClientIP(ctx)
    if !h.anonymousRateLimiter.Allow(clientIP) {
        return 429 Response
    }
}
// 인증된 사용자는 Rate Limiting 우회

응답 형식은 RFC 7807 Problem Details 준수:

json
{
  "status": 429,
  "title": "Too Many Requests",
  "detail": "Rate limit exceeded. Please sign in for higher limits or try again later."
}

Options Considered

Option A: 인메모리 Fixed Window (선택됨)

동작 방식:

  • IP별 요청 카운터가 매 분 윈도우마다 리셋
  • Go map에 저장, 백그라운드 정리 고루틴 동작
  • 외부 의존성 없음

장점:

  • 인프라 의존성 제로
  • 단순 구현, 예측 가능한 동작
  • 배포 변경 없이 즉시 사용 가능

단점:

  • 단일 인스턴스 전용 (수평 확장 미지원)
  • 재시작 시 상태 손실 (배포 시 제한 리셋)
  • Fixed window 경계 버스트 (최악의 경우: 윈도우 경계에서 20 요청)

Option B: Redis 기반 분산

동작 방식:

  • Redis에 중앙 집중식 카운터 저장
  • TTL과 함께 원자적 증가

장점:

  • 수평 확장을 위한 다중 인스턴스 지원
  • 재시작 간 상태 지속
  • 대규모 검증 완료

단점:

  • 새 인프라 의존성 (ADR-06 위반)
  • 요청당 네트워크 지연 오버헤드
  • 운영 복잡성 (Redis 모니터링, 페일오버)

결정: 현재 단일 인스턴스 배포에서는 기각. 다중 인스턴스 확장 시 재검토.

Option C: Cloud WAF / API Gateway

동작 방식:

  • Cloudflare 또는 API Gateway 레벨에서 Rate Limiting 설정
  • 애플리케이션 도달 전 엣지에서 적용

장점:

  • 애플리케이션 코드 변경 없음
  • 글로벌 엣지 적용
  • DDoS 보호 포함

단점:

  • 엣지에서 인증/익명 사용자 구분 불가
  • 애플리케이션 레벨보다 조잡한 세분화
  • 외부 의존성, 요청당 비용

결정: 심층 방어 계층으로 유지, 주요 솔루션으로는 부적합.

Option D: 애플리케이션 레벨 제한 없음

동작 방식:

  • 인프라 보호(Cloudflare)에만 의존
  • 애플리케이션 레벨 스로틀링 없음

장점:

  • 가장 단순한 접근, 이미 배포됨

단점:

  • 애플리케이션 시맨틱(사용자 유형) 이해 불가
  • 모든 사용자 동등 처리, 2단계 경험과 충돌

결정: 사용자 유형별 동작 제공 불가로 기각.

Implementation

Rate Limiter 컴포넌트

src/backend/
├── common/
│   ├── ratelimit/
│   │   └── limiter.go       # Fixed window IPRateLimiter
│   ├── middleware/
│   │   └── ratelimit.go     # Token bucket 미들웨어 (대안)
│   └── httputil/
│       └── client_ip.go     # IP 추출
└── modules/analyzer/
    └── handler/http.go      # Rate limiter 통합

IP 추출 우선순위

  1. X-Forwarded-For 헤더 (목록의 첫 번째 IP)
  2. X-Real-IP 헤더
  3. RemoteAddr (폴백)

신뢰할 수 있는 리버스 프록시(Railway, Cloudflare) 뒤 배포 가정하에 프록시 헤더 신뢰.

초기화

go
// app.go
anonymousRateLimiter := ratelimit.NewIPRateLimiter(10, time.Minute)
closers = append(closers, anonymousRateLimiter) // Graceful shutdown

Consequences

Positive

1. 외부 의존성 제로

  • Redis 또는 외부 서비스 불필요
  • ADR-06 PaaS 우선 전략 부합
  • 배포 및 운영 단순화

2. 남용 방지

  • 익명 남용으로부터 플랫폼 리소스 보호
  • 인증된 사용자를 위한 공정한 접근 보장
  • 비용 증폭 위험 감소

3. 명확한 사용자 경험

  • 익명 사용자에게 제한 존재 안내
  • 에러 메시지로 인증 유도
  • 인증된 사용자는 제한 완전 우회

Negative

1. 단일 인스턴스 제한

  • 수평 확장 미지원
  • 마이그레이션 경로: 다중 인스턴스 배포 필요 시 Redis 기반 솔루션 구현

2. 상태 미지속

  • 애플리케이션 재시작 시 제한 리셋
  • 영향: 1분 윈도우에서는 수용 가능; 제한이 빠르게 복구

3. IP 기반 식별 한계

  • 공유 IP(NAT, 기업 네트워크)에서 오탐
  • 영향: 낮은 제한(10/분)이 정상 탐색에 거의 영향 없음
  • 완화: 사용자는 인증하여 더 높은 제한 획득 가능

트레이드오프 요약

트레이드오프결정근거
단순성 vs. 확장성단순성 우선현재 단일 인스턴스; 확장 시 재검토
IP 정확도 vs. 구현 비용IP 한계 수용NAT 오탐이 탐색 계층에서 수용 가능
메모리 vs. 외부 의존성메모리 우선익명 사용자 수에 대해 인메모리 수용 가능

References

Open-source test coverage insights