## 왜 - nginx IP별 레이트리밋(PR #332, 2층: 비싼 경로 `piki_llm` + 전역 `piki_general`)이 머지됐지만, **IP는 거친 1차 방어**다. CGNAT·공유망 오탐과 IP 변경 우회 한계로 정밀 제어엔 부적합하다. - 비싼 Gemini 경로(위시 등록·이미지 OCR, 토너먼트 link·images)는 **전부 인증 필수**라, userId 단위로 "기간당 N회" 정밀 quota를 걸어 비용·공정성을 정확히 제어할 수 있다. - nginx 429는 GlobalExceptionHandler를 안 거쳐 `ApiResponseBody`를 고정 JSON으로 흉내낼 뿐이다. 앱 레이어면 진짜 `ApiResponseBody.fail`로 일관 응답을 내려보낼 수 있다. ## 무엇을 - 인증 사용자(userId)별 비싼 경로 요청 횟수를 기간 윈도우로 제한한다. 한도·윈도우 값은 구현 시 결정한다. - 저장소는 이미 가동 중인 Redis를 활용한다. 구현 방식(Bucket4j(+redis) / 직접 INCR+EXPIRE 등)은 설계 단계에서 선택한다. - 적용 지점: 인터셉터/필터에서 JWT 인증 이후 userId를 추출해 비싼 경로 quota를 검사한다. - 초과 시 커스텀 예외(429, `HttpMappable`) → GlobalExceptionHandler → `ApiResponseBody.fail` 로 일관 응답한다. `Retry-After` 헤더를 고려하고, `*Api.kt`/`*ApiExamples` 의 429 문서화를 동반한다. - nginx IP 2층은 그대로 두고 위에 얹는 **보완 레이어**다(대체가 아니라 IP=미인증·flood 1차, userId=인증 후 정밀 quota). ## 참고 - nginx IP 층: #332 (dev 머지 완료)
왜
piki_llm+ 전역piki_general)이 머지됐지만, IP는 거친 1차 방어다. CGNAT·공유망 오탐과 IP 변경 우회 한계로 정밀 제어엔 부적합하다.ApiResponseBody를 고정 JSON으로 흉내낼 뿐이다. 앱 레이어면 진짜ApiResponseBody.fail로 일관 응답을 내려보낼 수 있다.무엇을
HttpMappable) → GlobalExceptionHandler →ApiResponseBody.fail로 일관 응답한다.Retry-After헤더를 고려하고,*Api.kt/*ApiExamples의 429 문서화를 동반한다.참고