Skip to content

ADR-10: 사용자별 공정성 미들웨어

🇺🇸 English Version

날짜작성자레포지토리
2026-02-02@KubrickCodeworker

배경

문제 상황

사용자별 제한 없이는 대량 요청 제출 시 큐 워커 독점, 불공정한 리소스 분배 발생:

시나리오영향
Free 사용자 10건 작업 제출5개 워커 모두 점유; Pro 사용자 큐에서 대기
단일 사용자 대량 제출다른 사용자 서비스 품질 저하
티어 구분 없음유료 사용자가 무료 사용자 대비 우선권 없음

요구사항

요구사항설명
사용자별 제한전역이 아닌 사용자별 동시 작업 제한
티어 기반 할당량상위 티어에 더 많은 동시 슬롯 제공
비파괴적 제한작업 거부 아닌 지연; 모든 작업 최종 실행
낮은 오버헤드작업 실행 지연 최소화
우아한 처리스누즈된 작업 깨어날 때 thundering herd 방지

결정

River WorkerMiddleware를 통한 티어 기반 할당량의 사용자별 동시 작업 제한 구현.

티어별 제한

티어동시 작업 수
Free1
Pro3
Pro Plus3
Enterprise5

아키텍처

┌─────────────────────────────────────────────────────────────────┐
│                    Fairness Middleware 흐름                      │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  작업 수신                                                       │
│       │                                                          │
│       ▼                                                          │
│  ┌─────────────────┐                                            │
│  │ UserJobExtractor │  작업 args(JSON)에서 userID 추출          │
│  └────────┬────────┘                                            │
│           │                                                      │
│           ▼                                                      │
│  ┌─────────────────┐                                            │
│  │  TierResolver   │  DB에서 사용자 구독 티어 조회              │
│  │  (DB 조회)      │  미확인 시 Free 기본값                     │
│  └────────┬────────┘                                            │
│           │                                                      │
│           ▼                                                      │
│  ┌─────────────────┐                                            │
│  │ PerUserLimiter  │  TryAcquire(userID, tier, jobID)          │
│  │  (인메모리)     │                                            │
│  └────────┬────────┘                                            │
│           │                                                      │
│     ┌─────┴─────┐                                               │
│     │           │                                                │
│  acquired    rejected                                            │
│     │           │                                                │
│     ▼           ▼                                                │
│  워커       JobSnooze(30s + jitter)                             │
│  실행       재시도 반환                                          │
│     │                                                            │
│     ▼                                                            │
│  defer Release(userID, jobID)                                   │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

핵심 설계 결정

결정근거
Hook 대신 WorkerMiddlewareHook은 JobSnooze 반환 불가; 미들웨어만 가능
인메모리 리미터단일 배포 환경에서 인스턴스별 상태 허용
job args 대신 DB에서 티어Web 레이어에 티어 미포함; 느슨한 결합 유지
30s + 10s jitter 스누즈깨어날 때 thundering herd 방지
멱등 acquire/release동일 jobID 슬롯 중복 카운트 방지
시스템 작업 우회빈 userID는 제한 우회 (스케줄 작업)

고려한 옵션

옵션 A: 사용자별 WorkerMiddleware 제한 (선택)

인메모리에서 사용자별 동시 작업 수 추적하는 River WorkerMiddleware 구현. 작업 실행 전 티어 기반 제한 확인. 초과 시 JobSnooze 반환.

장점:

  • 사용자별 정확한 동시성 제어
  • 티어 기반 공정성 차별화
  • 작업 거부 아닌 스누즈
  • Jitter로 thundering herd 방지

단점:

  • 인스턴스별 상태 (분산 아님)
  • 티어 조회용 DB 쿼리

옵션 B: 단일 큐 우선순위 필드

티어 기반 우선순위 필드 추가. 높은 우선순위 먼저 처리.

기각: 우선순위는 순서에 영향, 동시성 아님. 단일 사용자 여전히 워커 독점.

옵션 C: 티어별 전용 워커 풀

티어별 전용 워커의 별도 큐.

기각: 풀 불균형 로드 시 리소스 비효율. 티어 내 단일 사용자 여전히 독점.

옵션 D: River Hook 접근

HookWorkBegin으로 제한 확인.

기각: Hook은 JobSnooze 반환 불가. 기술적 제약, 선호도 아님.

결과

긍정적:

  • 사용자 간 공정한 리소스 분배
  • 명확한 티어 가치 제안 (1 vs 3 vs 5 슬롯)
  • 비파괴적 제한 (거부 아닌 스누즈)
  • 기존 세마포어 패턴과 일관성 (ADR-06)

부정적:

  • 인스턴스별 상태로 수평 확장 제한
  • 티어 조회용 추가 DB 쿼리
  • 제한 초과 사용자 30s+ 지연

운영:

  • 티어별 스누즈 비율 모니터링
  • 환경 변수로 티어 제한 노출
  • 분산 리미터 향후 작업으로 문서화

설정

FAIRNESS_ENABLED=true
FAIRNESS_FREE_LIMIT=1
FAIRNESS_PRO_LIMIT=3
FAIRNESS_ENTERPRISE_LIMIT=5
FAIRNESS_SNOOZE_DURATION=30s
FAIRNESS_SNOOZE_JITTER=10s

참조

Open-source test coverage insights