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