Skip to content

ADR-21: 동시 요청 처리용 할당량 예약

🇺🇸 English Version

날짜작성자리포지토리
2026-02-01@specvitalweb, worker, infra

배경

이벤트 기반 과금의 동시성 갭

ADR-13 (빌링 및 쿼터 아키텍처)에서 작업 완료 시점에 사용량 이벤트를 기록하는 이벤트 기반 사용량 추적 방식 수립. 이 모델은 과금 정확성 보장(성공한 작업만 과금) 및 캐시 우선 아키텍처와의 정합성 확보(캐시 히트 시 이벤트 미생성).

그러나 쿼터 검사(요청 제출 시점)와 쿼터 소비(작업 완료 시점) 사이에 시간적 갭 존재. 높은 동시성 상황에서 이 갭이 경쟁 조건(race condition) 유발:

경쟁 조건 시나리오:

시점동작사용자 상태
T0사용자 4998/5000 사용 중-
T1요청 A 쿼터 검사4998 < 5000 → 통과
T2요청 B 쿼터 검사4998 < 5000 → 통과
T3요청 A 완료5008 사용 (한도 초과)
T4요청 B 완료5018 사용

미해결 시 문제점:

  • 초과 쿼터 작업 처리로 서버 리소스 낭비
  • 실제 사용량이 한도 초과하는 과금 불일치
  • 요청 타이밍에 따른 불공정한 시스템 동작

제약 조건

제약근거
Web 레벨 차단 필수Worker 재검사 시 큐 및 컴퓨팅 리소스 낭비
PostgreSQL 전용 솔루션ADR-04 (River는 PostgreSQL 사용)와 정합성
사용자에게 보이는 과금 이상 없음과금 정확성에 대한 사용자 신뢰 유지
Worker가 라이프사이클 소유ADR-12에서 Worker를 레코드 생성자로 규정

결정

동시 요청 처리를 위한 쿼터 예약 패턴과 원자적 트랜잭션 채택.

예약 메커니즘

진행 중인 쿼터 예약을 추적하는 quota_reservations 테이블 도입:

컬럼타입목적
iduuid (PK)기본 키
user_iduuid (FK)사용자 계정 연결
event_typeusage_event_typespecview 또는 analysis
reserved_amountint예상 쿼터 소비량
job_idbigint (UNIQUE)River 작업 연결 (정리용)
expires_attimestamptz고아 정리용 1시간 TTL

쿼터 검사 공식

sql
used + reserved + requested_amount <= limit

구성 요소:

  • used: 현재 과금 기간의 usage_events 합계
  • reserved: 활성 예약 합계 (expires_at > NOW())
  • requested_amount: 현재 요청에 필요한 단위

트랜잭션 원자성

Web 레이어에서 단일 PostgreSQL 트랜잭션 내 실행:

  1. 쿼터 검사 (활성 예약 포함)
  2. 예약 레코드 생성
  3. River 큐에 작업 삽입 (InsertTx)

원자성 보장: 작업 삽입 실패 시 예약도 생성되지 않음 (롤백).

예약 라이프사이클

Web: 쿼터 검사 → 예약 생성 → InsertTx

Worker: 작업 처리 → 예약 삭제 (성공 또는 실패)

Cleanup: 1시간 후 고아 예약 만료

검토된 옵션

옵션 A: 예약 패턴 (선택됨)

메커니즘: 작업 삽입과 원자적으로 예약 생성; Worker가 완료 시 삭제.

측면평가
쿼터 정확도보장 - 동시 초과 예약 불가
사용자 경험투명 - 예약은 내부 상태
리소스 효율높음 - Web에서 초과 쿼터 차단
복잡도중간 - 추가 테이블 및 정리 작업

옵션 B: 선차감 후 환불

메커니즘: 제출 시 쿼터 차감; 작업 실패 시 환불.

측면평가
쿼터 정확도보장
사용자 경험나쁨 - 처리 중 부풀려진 사용량 표시
리소스 효율높음
복잡도높음 - 다양한 실패 상태별 환불 로직 필요

거부 사유: 과금 불안과 지원 부담 유발. 작업 처리 중 대시보드를 보는 사용자에게 환불될 수 있는 사용량이 표시되어 신뢰 저하.

옵션 C: Worker 재검사

메커니즘: Web에서 낙관적 검사; Worker에서 권위적 검사.

측면평가
쿼터 정확도보장 (Worker 레벨)
사용자 경험나쁨 - 작업 수락 후 비동기 거부
리소스 효율나쁨 - 초과 쿼터 작업이 큐 용량 소비
복잡도낮음

거부 사유: Web 레벨 차단 요구 사항 위반. 거부될 작업에 큐 및 Worker 리소스 낭비.

결과

긍정적

이점영향
정확한 쿼터 집행동시 요청이 한도 초과 불가
리소스 효율초과 쿼터 작업에 Worker 사이클 낭비 없음
사용자 공정성완료된 작업만 과금에 표시
원자적 일관성작업 + 예약이 단일 트랜잭션
깔끔한 실패 처리작업 결과와 관계없이 예약 삭제

부정적

트레이드오프완화 방안
추가 스키마 복잡도표준 패턴; 명확한 목적의 단일 테이블
예약 집계 쿼리 오버헤드(user_id, event_type, expires_at) 인덱스
고아 정리 필요구성 가능한 1시간 TTL의 스케줄 작업
디버깅 복잡도예약/해제 지점에 구조화된 로깅

기술적 영향

측면요구 사항
트랜잭션 범위InsertTx와 CreateReservation이 PostgreSQL 트랜잭션 공유
인덱스 설계효율적인 예약 조회 및 만료를 위한 복합 인덱스
정리 스케줄expires_at < NOW() 조건의 예약 삭제 크론 작업
모니터링고아 수 알림 (Worker 상태 문제 표시)
Worker 수정작업 완료 시 job_id로 예약 삭제

참조

Open-source test coverage insights