배경
Polaris의 미션 도메인은 사용자에게 하루 단위의 미션을 제안하고, AI 도메인은 캐릭터 말투를 반영한 미션 제안 문구, 완료 질문, 완료 후 반응 문구를 생성한다.
MVP에서는 AI가 미션 자체를 완전히 자유 생성하지 않는다. 미션 템플릿을 먼저 선택하고, AI는 선택된 템플릿의 의미를 유지한 상태에서 사용자에게 보여줄 문구만 캐릭터 말투로 변환한다. 또한 AI 호출이 실패해도 미션 생성 흐름이 중단되지 않도록 mission_templates의 fallback 문구를 사용할 수 있게 설계되어 있다.
현재 미션 생성 흐름은 다음 책임을 가진다.
mission 모듈
→ 오늘 생성 가능한 미션인지 검증
→ mission template 선택
→ user_missions 생성
→ ai 모듈에 문구 생성 요청
→ AI 결과를 user_missions / mission_completion_answers에 반영
ai 모듈
→ requestId 기반 멱등 처리
→ 캐릭터 말투 기반 문구 생성
→ 생성 결과 검증
→ 생성 결과와 fallback 여부를 저장외부 AI provider를 붙이기 전까지는 LocalMissionTextGenerator로 deterministic한 문구를 만들 수 있었다. 하지만 Gemini 또는 OpenAI 같은 외부 AI provider를 실제로 호출하기 시작하면 다음 위험이 생긴다.
- 외부 AI API는 유료 호출이므로 호출량 폭주가 비용 문제로 이어질 수 있다.
- 네트워크 timeout, 클라이언트 재시도, 서버 재시도 때문에 같은 요청 또는 유사한 요청이 짧은 시간에 반복될 수 있다.
- 사용자가 미션 새로고침을 여러 번 누르거나, 특정 버그로 미션 생성 요청이 반복되면 AI 호출이 불필요하게 증가할 수 있다.
- 서버가 여러 대로 늘어나면 각 서버가 독립적으로 호출량을 계산할 경우 전체 호출량 제한이 깨질 수 있다.
- 외부 provider 장애가 길어질 때, 애플리케이션이 계속 provider를 호출하면 장애가 비용과 latency 문제로 확산될 수 있다.
Polaris는 향후 모듈러 모놀리스에서 MSA 형태로 확장될 수 있다. 지금은 하나의 백엔드 애플리케이션 안에서 여러 도메인 모듈을 운용하더라도, 각 도메인은 gRPC 경계를 기준으로 독립적인 책임을 가지도록 설계하고 있다. 따라서 외부 AI provider를 보호하는 정책은 mission 모듈이 아니라 AI provider를 직접 호출하는 ai 모듈의 경계에서 처리하는 것이 자연스럽다.
논의한 문제
팀에서는 다음 질문을 기준으로 AI provider 호출량 제어 방식을 검토했다.
- 외부 AI provider 호출량 제한은 어느 모듈에서 담당해야 하는가?
- mission의 하루 최대 미션 제안 제한만으로 외부 AI 비용 폭주를 막을 수 있는가?
- gateway 또는 Nginx rate limit만으로 내부 gRPC 호출과 batch 호출까지 보호할 수 있는가?
- 단일 서버에서는 in-memory rate limit으로 충분한가?
- 서버가 여러 대로 늘어났을 때도 전체 AI 호출량 제한을 보장할 수 있는가?
- Redis를 rate limit 저장소로 사용할 때 얻는 이점과 부담은 무엇인가?
- Redis 장애 시 외부 AI 호출을 허용할 것인가, 차단할 것인가?
- rate limit 초과 또는 Redis 장애 시 사용자 경험은 어떻게 유지할 것인가?
- requestId 기반 멱등 처리와 rate limit은 어떤 순서로 적용해야 하는가?
- MVP에서 구현할 범위와 고도화 단계로 넘길 범위는 어디까지인가?
검토한 선택지
선택지 1. 별도 애플리케이션 rate limit 없이 외부 provider quota만 사용
Gemini 또는 OpenAI 콘솔에서 제공하는 quota, budget alert, 결제 한도에 의존하는 방식이다.
장점:
- 애플리케이션 코드가 단순하다.
- 별도 Redis나 rate limit 구현이 필요 없다.
- provider 콘솔에서 기본적인 quota 제한을 설정할 수 있다.
- 초기 실험 단계에서는 가장 빠르게 외부 AI 호출을 붙일 수 있다.
단점:
- 애플리케이션 레벨에서 사용자별, provider별, 기능별 호출량을 세밀하게 제어하기 어렵다.
- quota에 도달하기 전까지는 불필요한 호출이 계속 발생할 수 있다.
- provider quota 초과 시 애플리케이션 내부에서 의도한 fallback 흐름으로 제어하기 어렵다.
- 어떤 기능이 호출량을 많이 사용했는지 추적하기 어렵다.
- 장애 상황에서 provider 호출을 미리 차단하지 못하고 timeout 비용을 계속 지불할 수 있다.
검토 결과:
provider quota와 budget alert는 반드시 필요한 마지막 방어선이다. 하지만 이것만으로는 애플리케이션 내부의 비용 제어, 사용자 경험 유지, 기능별 호출량 분석을 충분히 처리하기 어렵다. 따라서 provider quota만 사용하는 방식은 Polaris의 운영 기준에는 부족하다고 판단했다.
선택지 2. Gateway 또는 Nginx rate limit만 사용
외부 사용자의 HTTP 요청을 gateway 또는 Nginx에서 제한하는 방식이다.
장점:
- 사용자 트래픽이 백엔드 내부로 과도하게 들어오는 것을 막을 수 있다.
- IP 기준 DDoS 또는 반복 요청 방어에 효과가 있다.
- Nginx rate limit은 인프라 레벨에서 설정할 수 있어 애플리케이션 코드 변경이 적다.
- gateway에서 API별 rate limit을 두면 전체 서비스 보호에 도움이 된다.
단점:
- 내부 gRPC 호출, batch 호출, scheduler 호출은 gateway/Nginx를 거치지 않을 수 있다.
- "HTTP 요청 수"와 "외부 AI provider 호출 수"는 같지 않다.
- 하나의 HTTP 요청 안에서 AI provider 호출이 여러 번 발생할 수도 있다.
- IP 기준 제한은 로그인 사용자, provider, model, 기능 단위 제어에 적합하지 않다.
- 외부 AI 호출 직전의 fallback 처리와 직접 연결하기 어렵다.
검토 결과:
Gateway/Nginx rate limit은 서비스 전체를 보호하는 1차 방어선으로 필요하다. 하지만 외부 AI provider 호출량을 정확히 제어하는 위치는 아니다. AI provider 호출 보호는 provider를 직접 호출하는 ai 모듈 내부에서 별도로 처리해야 한다고 판단했다.
선택지 3. mission 도메인의 비즈니스 제한만 사용
mission 도메인에는 하루 최대 15개 미션 제안 제한이 있다. 이 제한을 AI 호출량 제어 수단으로 사용하는 방식이다.
장점:
- 이미 mission 도메인 정책으로 존재하는 제한을 활용할 수 있다.
- 사용자별 미션 생성 횟수를 제한하므로 일부 호출량 감소 효과가 있다.
- 비즈니스 정책과 연결되어 있어 사용자 경험을 설명하기 쉽다.
단점:
- 하루 15개 제한은 "사용자에게 미션을 너무 많이 제안하지 않기 위한 정책"이지, 외부 AI provider 비용 보호 정책은 아니다.
- AI 도메인이 mission 외의 기능에서도 사용되면 해당 제한이 적용되지 않는다.
- 같은 미션 생성 요청이 재시도될 때 provider 호출이 반복되는 문제를 직접 막지 못한다.
- 여러 사용자 또는 batch 작업이 동시에 몰릴 때 전체 provider 호출량 제한을 보장하지 못한다.
- AI provider별 quota, model별 비용 차이, 장애 상황을 반영하기 어렵다.
검토 결과:
mission의 하루 최대 제안 제한은 유지한다. 다만 이 제한은 비즈니스 정책이고, AI provider 비용 보호 정책은 아니다. 따라서 mission 도메인의 제한만으로 외부 AI 호출량 제어를 대체하지 않는다.
선택지 4. ai 모듈 내부 in-memory rate limit
ai 모듈 애플리케이션 메모리에 카운터를 두고 provider 호출 전 제한을 확인하는 방식이다.
장점:
- 구현이 단순하다.
- Redis 같은 외부 저장소가 필요 없다.
- 네트워크 왕복이 없어 빠르다.
- 로컬 개발과 단위 테스트가 쉽다.
- 단일 인스턴스 MVP에서는 일정 수준의 호출량 보호가 가능하다.
단점:
- 서버가 여러 대로 늘어나면 각 서버가 서로 다른 카운터를 가진다.
- 서버 2대에서 분당 30회 제한을 걸면 실제 전체 호출량은 분당 최대 60회가 될 수 있다.
- 서버 재시작 시 카운터가 초기화된다.
- batch 서버, app 서버, ai 서버가 분리되면 전체 provider 호출량을 공유하기 어렵다.
- 운영에서 실제 비용 상한을 설명하기 어렵다.
검토 결과:
in-memory rate limit은 로컬 개발 또는 테스트용 구현으로는 유용하다. 하지만 Polaris는 여러 도메인 모듈을 가지고 있고, 향후 app 서버와 ai/batch 서버가 분리될 수 있다. 외부 AI API는 유료 호출이므로 전체 호출량 제한을 보장할 수 없는 in-memory 방식만 사용하는 것은 운영 기준에 맞지 않는다고 판단했다.
선택지 5. Redis 기반 분산 rate limit
Redis를 중앙 카운터 저장소로 사용하고, ai 모듈이 외부 provider 호출 직전에 Redis에서 호출 가능 여부를 확인하는 방식이다.
장점:
- 여러 서버 인스턴스가 같은 Redis 카운터를 공유한다.
- 다중 인스턴스 환경에서도 전체 provider 호출량 제한을 보장할 수 있다.
- provider, model, user, 기능 단위로 key를 확장하기 쉽다.
- 서버 재시작과 무관하게 rate limit window 동안 카운터가 유지된다.
- 외부 AI 비용 폭주를 애플리케이션 레벨에서 방어할 수 있다.
- Redis TTL을 사용하면 window 만료 처리가 단순하다.
- 향후 OpenAI provider, batch 생성, RAG 호출에도 같은 정책을 확장할 수 있다.
단점:
- Redis 의존성이 추가된다.
- Redis 장애 시 정책을 정해야 한다.
- Redis 네트워크 왕복 비용이 추가된다.
- rate limit key 설계와 TTL 정책을 관리해야 한다.
- 로컬 개발 환경에서 Redis가 필요할 수 있다.
검토 결과:
Polaris는 이미 Redis를 세션, 캐시, 재시도 큐, 중복 이벤트 방지 후보 저장소로 검토하고 있다. 외부 AI provider는 유료 API이고, fallback 문구를 사용할 수 있는 구조가 이미 있으므로 Redis 기반 rate limit의 운영상 이점이 구현 부담보다 크다고 판단했다.
결정
Polaris는 외부 AI provider 호출 보호를 위해 ai 모듈에 Redis 기반 분산 rate limit을 적용한다.
rate limit은 외부 AI provider를 직접 호출하기 직전에 확인한다.
mission 모듈
→ ai gRPC GenerateMissionTexts 요청
→ ai 모듈 requestId 멱등 확인
→ Redis 기반 rate limit 확인
→ Gemini/OpenAI provider 호출
→ 생성 결과 검증
→ 실패 또는 제한 초과 시 fallback 문구 사용Redis rate limit 확인에 실패하거나 제한을 초과한 경우에는 외부 AI provider를 호출하지 않는다. 대신 기존 mission template의 fallback 문구를 사용한다.
즉, Redis 장애 또는 rate limit 초과 상황에서는 fail-closed 정책을 사용한다.
Redis 사용 가능 + limit 여유 있음
→ 외부 AI provider 호출
Redis 사용 가능 + limit 초과
→ 외부 AI provider 호출 안 함
→ fallback 문구 사용
→ usage log에 RATE_LIMITED 기록
Redis 장애 또는 timeout
→ 외부 AI provider 호출 안 함
→ fallback 문구 사용
→ usage log에 RATE_LIMIT_UNAVAILABLE 기록결정 이유
1. 외부 AI API는 유료 호출이므로 애플리케이션 레벨 방어가 필요하다
Gemini, OpenAI 같은 외부 AI provider는 호출량과 token 사용량에 따라 비용이 발생한다. 따라서 기능이 동작하는지만 확인하는 것이 아니라, 운영 중 호출량 폭주를 어떻게 막을지까지 설계해야 한다.
provider 콘솔의 quota와 budget alert는 필요하지만, 그것은 마지막 방어선이다. 애플리케이션 내부에서도 "이 요청은 지금 외부 AI를 호출해도 되는가?"를 판단해야 한다.
2. in-memory rate limit은 다중 인스턴스에서 전체 제한을 보장하지 못한다
단일 서버에서는 in-memory rate limit이 단순하고 빠르다. 하지만 서버가 여러 대로 늘어나면 각 서버가 자기 메모리의 카운터만 본다.
예를 들어 분당 30회 제한을 in-memory로 걸었을 때:
ai-server-1: 분당 30회 허용
ai-server-2: 분당 30회 허용
ai-server-3: 분당 30회 허용
결과:
전체 시스템 기준 분당 최대 90회 호출 가능이 경우 설정값은 분당 30회지만, 실제 외부 AI provider 호출량은 인스턴스 수에 따라 증가한다. 운영에서 비용 상한을 설명하기 어렵고, 장애 상황에서도 호출량이 예상보다 커질 수 있다.
3. Redis를 중앙 카운터로 사용하면 전체 호출량을 공유할 수 있다
Redis는 모든 서버 인스턴스가 같은 카운터를 보게 해준다.
ai-server-1
ai-server-2
ai-server-3
↓
Redis key: ai:rate-limit:provider:gemini:model:gemini-2.5-flash:minute:202605201430모든 인스턴스가 같은 key를 증가시키므로, 서버가 여러 대로 늘어나도 전체 호출량 제한을 유지할 수 있다.
4. fallback 구조가 있으므로 fail-closed를 선택할 수 있다
Redis가 장애일 때 선택지는 두 가지다.
fail-open:
rate limit 확인이 안 되면 외부 AI 호출을 허용한다.
fail-closed:
rate limit 확인이 안 되면 외부 AI 호출을 차단한다.fail-open은 사용자에게 AI 개인화 문구를 제공할 가능성은 높지만, 비용 폭주 방어선이 사라진다. Redis 장애가 발생한 상태에서 provider 호출까지 계속 허용하면 장애가 비용 문제로 확산될 수 있다.
Polaris는 AI 문구 생성에 실패해도 mission template fallback 문구를 사용할 수 있다. 즉, 외부 AI 호출을 차단해도 핵심 미션 생성 흐름은 유지된다. 사용자에게 완전히 실패를 반환하지 않고, 개인화 품질만 일시적으로 낮아진다.
따라서 Polaris는 Redis 장애 시 fail-closed를 선택한다.
5. requestId 멱등 처리와 함께 사용하면 중복 비용을 줄일 수 있다
AI 문구 생성 요청에는 requestId 기반 멱등 처리를 적용한다. 같은 requestId와 같은 요청 본문이 다시 들어오면 이미 저장된 생성 결과를 반환한다.
따라서 처리 순서는 다음과 같아야 한다.
1. requestId로 기존 생성 결과 조회
2. 이미 같은 요청이 처리되었으면 저장된 결과 반환
3. 기존 결과가 없을 때만 rate limit 확인
4. rate limit 통과 시 외부 provider 호출
5. 생성 결과 저장이 순서를 지키면 네트워크 재시도나 클라이언트 중복 요청이 발생해도 rate limit 카운터와 외부 provider 호출을 불필요하게 소비하지 않는다.
6. ai 모듈 경계에서 처리하는 것이 책임 위치가 가장 명확하다
mission 모듈은 "어떤 미션을 제안할지"와 "미션 상태를 어떻게 전이할지"를 책임진다. 반면 외부 AI provider 호출 여부, provider 장애 처리, token 사용량, 생성 결과 검증은 ai 모듈의 책임이다.
따라서 rate limit은 mission 모듈이 아니라 ai 모듈에서 처리한다.
이렇게 하면 향후 mission 외의 기능이 ai 모듈을 사용하더라도 동일한 provider 보호 정책을 재사용할 수 있다.
적용 범위
MVP에서 적용할 범위
MVP에서는 다음 범위까지 구현한다.
- ai 모듈에
AiRateLimiter추상화 추가 - Redis 기반
RedisAiRateLimiter구현 - 외부 provider 호출 직전 rate limit 확인
- Redis 장애 또는 rate limit 초과 시 외부 provider 호출 차단
- fallback 문구 사용
- usage log에 fallback 여부와 error type 기록
- rate limit 설정값을 환경변수로 분리
- 테스트에서는 실제 Redis 또는 외부 AI provider에 의존하지 않도록 fake/mock 사용
MVP에서 제외할 범위
MVP에서는 다음 기능을 제외한다.
- 관리자 화면에서 rate limit 값을 실시간 변경
- 사용자별 세부 정책을 DB에서 동적으로 관리
- Redis Cluster 구성
- 정교한 sliding window 또는 token bucket 알고리즘
- provider별 비용 예측 대시보드
- RAG 호출량 별도 제한
단, 구조는 위 기능을 나중에 추가할 수 있게 확장 가능해야 한다.
rate limit 적용 순서
AI 문구 생성 요청은 다음 순서로 처리한다.
1. 요청 필수값 검증
2. requestId 기반 기존 생성 결과 조회
3. 기존 결과가 있으면 즉시 반환
4. 기존 결과가 없으면 rate limit 확인
5. rate limit 통과 시 외부 provider 호출
6. provider 응답 검증
7. 정상 응답이면 생성 결과 저장
8. 실패, 제한 초과, Redis 장애 시 fallback 결과 저장
9. usage log 저장
10. gRPC 응답 반환중요한 점은 requestId 멱등 확인을 rate limit보다 먼저 수행한다는 것이다.
같은 요청의 재시도는 새로운 AI 호출이 아니다. 따라서 이미 처리된 requestId는 rate limit 카운터를 추가로 소비하지 않아야 한다.
rate limit key 설계
MVP에서는 다음 key를 우선 사용한다.
provider + model 기준 전체 제한
ai:rate-limit:provider:{provider}:model:{model}:minute:{yyyyMMddHHmm}예시:
ai:rate-limit:provider:gemini:model:gemini-2.5-flash:minute:202605201430목적:
- 전체 Gemini 호출량 제한
- model별 비용 차이를 반영할 수 있는 구조 확보
- 향후 OpenAI provider 추가 시 같은 방식으로 확장
user 기준 짧은 시간 제한
ai:rate-limit:user:{userId}:provider:{provider}:minute:{yyyyMMddHHmm}예시:
ai:rate-limit:user:123:provider:gemini:minute:202605201430목적:
- 한 사용자의 반복 클릭 또는 클라이언트 버그가 전체 quota를 소비하는 것을 방지
- mission의 하루 최대 15개 제한과 별도로 짧은 시간 폭주를 방어
TTL
분 단위 window key는 60초보다 약간 긴 TTL을 둔다.
TTL = 70초이렇게 하면 window 경계에서 key가 너무 빨리 사라지는 문제를 줄일 수 있다.
rate limit 알고리즘
MVP에서는 fixed window counter 방식을 사용한다.
동작 방식:
1. 현재 분 단위 key 생성
2. Redis INCR로 카운터 증가
3. 최초 생성된 key면 EXPIRE 설정
4. 증가 후 값이 limit 이하이면 허용
5. limit 초과이면 거절장점:
- 구현이 단순하다.
- Redis 명령어로 원자적 증가가 가능하다.
- TTL 기반 정리가 쉽다.
- MVP 수준의 호출량 보호에는 충분하다.
단점:
- window 경계에서 순간적으로 허용량이 몰릴 수 있다.
- 매우 정교한 초당 제어가 필요한 경우에는 부족할 수 있다.
고도화가 필요하면 sliding window 또는 token bucket 방식으로 변경할 수 있다. 이때도 AiRateLimiter 인터페이스를 유지하면 application service 코드는 크게 바꾸지 않아도 된다.
환경변수
AI provider rate limit 설정은 환경변수로 분리한다.
AI_RATE_LIMIT_ENABLED=true
AI_RATE_LIMIT_BACKEND=redis
AI_RATE_LIMIT_PROVIDER_REQUESTS_PER_MINUTE=30
AI_RATE_LIMIT_USER_REQUESTS_PER_MINUTE=5
AI_RATE_LIMIT_TIMEOUT_MILLIS=200
AI_RATE_LIMIT_FAIL_CLOSED=true각 값의 의미:
| 환경변수 | 의미 | 기본값 |
|---|---|---|
AI_RATE_LIMIT_ENABLED |
AI provider rate limit 사용 여부 | true |
AI_RATE_LIMIT_BACKEND |
rate limit 저장소 | redis |
AI_RATE_LIMIT_PROVIDER_REQUESTS_PER_MINUTE |
provider + model 기준 분당 호출 제한 | 30 |
AI_RATE_LIMIT_USER_REQUESTS_PER_MINUTE |
user 기준 분당 호출 제한 | 5 |
AI_RATE_LIMIT_TIMEOUT_MILLIS |
Redis rate limit 확인 timeout | 200 |
AI_RATE_LIMIT_FAIL_CLOSED |
Redis 확인 실패 시 외부 AI 호출 차단 여부 | true |
주의:
- 운영 환경에서는
AI_RATE_LIMIT_FAIL_CLOSED=true를 유지한다. - 테스트에서는 fake rate limiter를 사용한다.
- 로컬에서 외부 AI 호출을 직접 검증할 때도 가능하면 Redis를 함께 띄운다.
- 단순 로컬 실험을 위해 rate limit을 끄는 경우에도 해당 설정은 커밋하지 않는다.
실패 처리 정책
rate limit 초과
rate limit을 초과하면 외부 AI provider를 호출하지 않는다.
처리:
fallback 문구 사용
ai_mission_generations.fallback_used = true
ai_usage_logs.error_type = RATE_LIMITED사용자 관점:
- 미션 생성 자체는 성공한다.
- 캐릭터 말투 개인화 품질은 일시적으로 낮아질 수 있다.
- 클라이언트에는 일반적인 미션 문구가 내려간다.
Redis 장애
Redis에 접근할 수 없거나 timeout이 발생하면 외부 AI provider를 호출하지 않는다.
처리:
fallback 문구 사용
ai_mission_generations.fallback_used = true
ai_usage_logs.error_type = RATE_LIMIT_UNAVAILABLE결정 이유:
Redis가 장애인 상황에서 fail-open으로 외부 AI 호출을 허용하면 비용 폭주 방어선이 사라진다. Polaris는 fallback 문구가 있으므로 fail-closed를 선택해도 핵심 미션 생성 흐름을 유지할 수 있다.
외부 provider 장애
Gemini/OpenAI 호출 중 timeout, 5xx, invalid response, JSON parsing 실패, 정책 위반이 발생하면 fallback 문구를 사용한다.
처리:
fallback 문구 사용
ai_mission_generations.fallback_used = true
ai_usage_logs.error_type = PROVIDER_TIMEOUT / PROVIDER_ERROR / INVALID_RESPONSE / POLICY_VIOLATION트레이드오프
장점
- 다중 인스턴스 환경에서도 전체 AI 호출량 제한을 유지할 수 있다.
- 외부 AI API 비용 폭주를 애플리케이션 레벨에서 방어할 수 있다.
- rate limit 초과 또는 Redis 장애 시에도 fallback으로 미션 생성 흐름을 유지할 수 있다.
- provider, model, user 단위로 제한 정책을 확장할 수 있다.
- requestId 멱등 처리와 함께 중복 호출 비용을 줄일 수 있다.
- 향후 OpenAI provider, RAG, batch 미션 생성에도 같은 보호 구조를 재사용할 수 있다.
단점
- Redis 의존성이 추가된다.
- Redis 장애 시 AI 개인화 품질이 일시적으로 낮아진다.
- Redis 네트워크 왕복이 추가된다.
- rate limit 설정값을 운영하면서 조정해야 한다.
- fixed window 방식은 window 경계에서 순간 호출량이 몰릴 수 있다.
트레이드오프 판단
Polaris의 AI 문구 생성은 사용자 경험을 좋게 만드는 요소지만, 미션 생성 자체를 반드시 중단시켜야 하는 핵심 필수 의존성은 아니다. fallback 문구가 있으므로 AI provider 호출을 차단해도 서비스는 계속 동작할 수 있다.
따라서 "항상 AI 개인화 문구를 제공하는 것"보다 "외부 유료 API 비용 폭주와 장애 확산을 막는 것"을 우선한다.
구현 규칙
1. provider 호출 직전에만 적용한다
rate limit은 외부 AI provider를 호출하기 직전에 적용한다. mission template 선택, user_missions 생성, requestId 기존 결과 조회 같은 내부 DB 작업에는 적용하지 않는다.
2. requestId 재시도는 rate limit을 소비하지 않는다
이미 처리된 requestId는 저장된 결과를 반환한다. 이 경우 rate limit 카운터를 증가시키지 않는다.
3. fallback은 실패가 아니라 degrade 전략이다
rate limit 초과 또는 Redis 장애는 사용자에게 반드시 에러로 노출하지 않는다. 미션 문구는 fallback으로 내려가고, 내부 로그와 usage log를 통해 원인을 추적한다.
4. 운영에서는 fail-closed를 기본값으로 한다
Redis rate limit 확인 실패 시 외부 AI provider 호출을 차단한다.
5. 테스트는 실제 외부 AI provider에 의존하지 않는다
단위 테스트와 CI는 실제 Gemini/OpenAI API key 없이 통과해야 한다. provider 호출부와 rate limiter는 fake 또는 mock으로 검증한다.
6. secret은 Git에 올리지 않는다
Gemini/OpenAI API key는 .env, GitHub Actions Secrets, 운영 secret manager 등으로만 주입한다. application.yaml, migration, test fixture, 문서 예시에는 실제 key를 적지 않는다.
결과
- 외부 AI provider 호출은 ai 모듈에서 중앙 제어한다.
- Redis 기반 분산 rate limit을 사용해 다중 인스턴스 환경에서도 전체 호출량 제한을 유지한다.
- Redis 장애 또는 rate limit 초과 시 외부 AI 호출을 차단하고 fallback 문구를 사용한다.
- requestId 멱등 처리를 rate limit보다 먼저 수행해 재시도 요청이 추가 호출량을 소비하지 않게 한다.
- provider quota와 budget alert는 별도의 인프라 방어선으로 유지한다.
- in-memory rate limit은 운영 기본 방식으로 사용하지 않는다.
검증 계획
단위 테스트
- 같은 requestId 재요청 시 rate limiter가 호출되지 않는지 검증한다.
- rate limit 허용 시 provider가 호출되는지 검증한다.
- rate limit 초과 시 provider가 호출되지 않고 fallback이 사용되는지 검증한다.
- Redis rate limit 확인 실패 시 provider가 호출되지 않고 fallback이 사용되는지 검증한다.
- fallback 사용 시 usage log에 적절한 error type이 저장되는지 검증한다.
통합 테스트
- Redis에 저장된 provider + model 기준 카운터가 TTL과 함께 증가하는지 검증한다.
- user 기준 카운터가 별도로 증가하는지 검증한다.
- limit 초과 시 같은 window 안에서 추가 호출이 거절되는지 검증한다.
- window가 지나면 다시 호출이 허용되는지 검증한다.
운영 전 수동 검증
1. AI_PROVIDER_ENABLED=true
2. AI_PROVIDER_TYPE=gemini
3. GEMINI_API_KEY 설정
4. Redis 실행
5. 분당 제한값을 낮게 설정
6. 미션 생성 요청을 반복 호출
7. limit 초과 후 fallback_used=true 저장 확인
8. Redis 중단
9. provider 호출 없이 fallback이 내려오는지 확인후속 과제
- Redis 기반
AiRateLimiter구현 - provider/model/user 기준 key 정책 코드화
- AI usage log의
error_type값 정리 - rate limit 초과 로그를 event-log 모듈로 전달할지 검토
- Google Cloud budget alert와 Gemini quota 설정
- OpenAI provider 추가 시 동일 rate limit 정책 적용
- RAG 도입 시 RAG 검색/생성 호출량 제한을 별도 key로 확장
- fixed window 방식의 한계를 실험하고, 필요 시 sliding window 또는 token bucket 방식으로 고도화
- 문서에 in-memory vs Redis, fail-open vs fail-closed 실험 결과 기록
'IL > TIL' 카테고리의 다른 글
| 20260522 [TIL] - 채팅 문의 인덱싱 정리 (0) | 2026.05.22 |
|---|---|
| 최종프로젝트에서 flyway 쓰는 이유 (0) | 2026.05.21 |
| 20260510 [TIL] - 카페 주문 시스템 (0) | 2026.05.10 |
| 20260404 [TIL] - 플러스 스프링 과제 (0) | 2026.04.02 |
| 20260310 [TIL] 클라우드 아키텍처 설계 & 배포 (0) | 2026.03.10 |