개별 사용자 속도 제한의 필요성
챗봇 기능은 다른 일반적인 기능보다 리소스를 훨씬 빠르게 소모합니다. 사용자는 짧은 시간 내에 여러 메시지를 보낼 수 있어 RPM이 급증하며, 대화 내용이 길어지거나 첨부 파일이 포함될 경우 컨텍스트 윈도우가 커져 TPM 역시 기하급수적으로 늘어납니다. 조직 단위의 제한을 공유하는 환경에서 특정 사용자의 과도한 사용은 다른 모든 기능의 마비를 초래하므로, 사용자 단위의 정교한 제어가 반드시 수반되어야 합니다.
Redis를 활용한 RPM(분당 요청 수) 제한 구현
Rails의 기본 속도 제한 기능도 유용하지만, 토큰 사용량과 같은 세밀한 제어를 위해서는 Redis와 같은 캐시 스토어를 직접 활용하는 것이 효율적입니다.
- 고정 윈도우 방식: Redis의 INCR 명령어를 사용하여 요청 횟수를 카운트하고, EXPIRE를 통해 1분 뒤에 자동으로 초기화되도록 설정합니다.
- 구현 예시: Usage 클래스를 정의하여 사용자 ID별로 고유한 키를 생성하고, 요청이 발생할 때마다 카운트를 증가시키며 제한 수치 초과 여부를 확인합니다.
TPM(분당 토큰 수) 기반의 정교한 제어
메시지의 길이는 토큰 사용량과 1:1로 매칭되지 않으며, 모델의 응답 토큰 또한 제한에 포함됩니다. 따라서 API 응답에서 제공하는 실제 토큰 사용 데이터를 추적해야 합니다. - 토큰 데이터 추출: OpenAI API 응답에는 입력(input), 출력(output), 추론(thinking) 토큰 정보가 포함됩니다. - 누적 추적: RPM 제한과 동일한 방식으로 Redis를 사용하되, 고정된 값(1)이 아닌 실제 사용된 총 토큰 수를 누적하여 관리합니다.
사용자별 할당량 계산 공식
조직의 전체 제한 수치를 사용자들에게 어떻게 배분할 것인지 결정하는 것이 중요합니다. 초기 단계의 서비스라면 다음과 같은 공식을 적용할 수 있습니다.
- 공식: 사용자별 제한 = (조직 전체 제한 × 버퍼 계수) / 예상 동시 접속자 수
- 버퍼 계수: 갑작스러운 트래픽 급증에 대비하여 전체 용량의 80%(0.8) 정도로 설정하는 것이 안전합니다.
사용자 경험을 고려한 고급 전략: 백그라운드 작업 및 재시도
단순히 요청을 거부(Reject)하고 오류 메시지를 보여주는 것은 사용자 경험에 좋지 않습니다. 대신 ActiveJob을 활용한 비동기 처리 방식을 도입할 수 있습니다.
- 재시도 메커니즘: 사용자가 속도 제한에 도달했을 때 즉시 오류를 내는 대신, 작업을 큐에 넣고 일정 시간(예: 15초) 후에 다시 시도하도록 설계합니다.
- 상태 알림: 사용자에게는 현재 처리 중임을 알리는 로딩 상태를 보여주고, 백그라운드에서 처리가 완료되면 결과를 브로드캐스팅합니다.
- 비즈니스 연계: 제한에 도달한 사용자에게 유료 플랜 업그레이드를 제안하거나, 더 저렴하고 제한이 넉넉한 하위 모델로 자동 전환하는 폴백 전략을 고려할 수 있습니다.