Skip to content

nginx IP별 레이트리밋 — 비싼 Gemini 경로 + 전역 2층 - #332

Merged
m-a-king merged 4 commits into
devfrom
infra/nginx-llm-rate-limit
Jun 1, 2026
Merged

nginx IP별 레이트리밋 — 비싼 Gemini 경로 + 전역 2층#332
m-a-king merged 4 commits into
devfrom
infra/nginx-llm-rate-limit

Conversation

@m-a-king

@m-a-king m-a-king commented Jun 1, 2026

Copy link
Copy Markdown
Collaborator

Situation

  • API 에 비싼 외부 호출이 있다 — Gemini 링크 추출(위시 등록·토너먼트 link)과 이미지 OCR(위시 이미지·토너먼트 images). 최악 약 90s 까지 잡는 무거운 호출이다.
  • 한 IP 가 이 경로들을 폭주시키면 Gemini 비용은 물론 톰캣 스레드·DB 커넥션이 묶여 다른 API 까지 latency 가 번진다. 그런데 지금까지 어떤 레이트리밋도 없었다.
  • 인프라는 EIP 직결(앞단에 ALB/CloudFront 없음) + 단일 노드(blue/green 도 한 박스)다. 이 구조가 nginx IP 기반 제한에 유리하다.

Task

  • 비싼 Gemini 호출 폭주를 앱 앞단(nginx)에서 IP별로 막는 1차 방어를 둔다.
  • 논의 핵심: "IP별이면 충분한가, 결국 userId별까지 필요한가." → IP 는 거친 1차 그물, userId 는 정밀 칼로 역할을 나누기로 결론. 이번엔 IP 만, userId 정밀 quota 는 후속.
  • 일반 조회·CRUD 는 건드리지 않고 비싼 경로만 정확히 잡아야 한다.

Action

적용 범위와 키 설계

  • 비싼 Gemini 경로 4개에만 limit_req 적용 — POST /api/v1/wishlists(링크 추출), POST /api/v1/wishlists/images(OCR), POST /api/v1/tournaments/{id}/items/link·/items/images. 일반 경로는 제한 없음.
  • map $request_methodPOST 만 카운트 키에 담는다. 같은 경로의 GET(위시 목록 조회)·기타 메서드는 빈 키가 되어 nginx 가 세지 않는다 — location 을 메서드별로 쪼개지 않고 POST 만 잡는 트릭.
  • 키는 $binary_remote_addr. EIP 직결이라 이게 진짜 클라이언트 IP 이고, 단일 노드라 shared memory zone 카운터가 IP 별로 정확하다. (앞단 LB 도입 시 real_ip 모듈로 키를 바꿔야 한다는 점을 주석에 남김.)

느슨한 1차 그물 정책

  • 일부러 느슨하게 — 분당 30회 + burst=20 nodelay. CGNAT·공유망(한 공인 IP 뒤 여러 사용자) 오탐을 0 에 가깝게 두고 명백한 flood 만 막는다.
  • userId 별 정밀 quota("하루 N회")는 앱 레이어 후속 작업으로 분리. 비싼 경로 4개가 전부 인증 필수라 userId quota 가 잘 맞고, 그쪽은 429 를 ApiResponseBody.fail 로 내려 응답 포맷도 일관되게 가져갈 수 있다. IP 와 userId 는 대체가 아니라 보완(IP 는 미인증·flood, userId 는 인증 후 비용·공정성).

정리·검증

  • 초과 시 limit_req_status 429(기본 503 대신). 단 이 429 바디는 ApiResponseBody 래퍼를 거치지 않는다(앱 앞 nginx 가 끊으므로) — 일관 포맷이 필요하면 error_page JSON 흉내 또는 위 userId 앱 레이어로.
  • proxy_set_header·proxy_read_timeout 90s 공통 설정을 location 에서 server 레벨로 올려 중복 제거.
  • docker nginx 로 nginx -t 문법 검증 통과(ssl 인증서·upstream include 는 검증용 더미로 우회). 실제 배포 검증은 deploy.yml 의 nginx -t 가 한 번 더 한다.

Result

  • 비싼 Gemini 경로의 IP별 폭주가 앱에 닿기 전 nginx 에서 429 로 끊긴다. 일반 조회·CRUD 는 영향 없음.
  • 느슨한 설정이라 정상 사용자(연속 아이템 추가 등)는 막히지 않는다. 운영 트래픽을 보며 rate/burst 조정 가능.
  • 후속: userId 별 정밀 quota(앱 레이어, Redis 이미 가동 중). 이번 nginx 설정과 충돌 없이 위에 얹는 보완 레이어다.

연관 이슈

Updates

전역(일반 경로) 레이트리밋 추가 — 2층 구조로 확장

  • 비싼 Gemini 경로만 막던 1층에서, 모든 경로에 아주 느슨한 전역 그물(piki_general, 초당 20 + burst=40 nodelay)을 더해 2층으로 확장했다. 일반 조회·CRUD 도 무제한이면 스크래핑·flood 에 노출되기 때문 — "호출이 과한 건 좋을 게 없다"는 판단. (585e6d0)
  • 다만 일반 경로는 정상 트래픽(화면당 GET 여럿·무한스크롤)이 비싼 경로보다 훨씬 잦아, 같은 강도면 정상 사용자가 막힌다. 그래서 비싼 경로(분당 30)보다 훨씬 느슨하게(초당 20) 둬 명백한 폭주만 컷하는 거친 그물로 설계했다. (585e6d0)
  • 키는 raw $binary_remote_addr(메서드·경로 무관 전부 카운트). location / 에만 적용했다 — 비싼 경로는 piki_llm(초당 0.5)이 이보다 타이트해 지배하므로 겹치지 않는다. docker nginx -t 재검증 통과. (585e6d0)

CodeRabbit 리뷰 대응

  • (A) 레이트리밋 429 가 nginx 기본 응답이라 클라이언트의 "모든 응답=ApiResponseBody" 파싱이 깨질 수 있다는 지적 → error_page 429 = @rate_limited 로 같은 스키마({data, detail, pageResponse}) 고정 JSON + Retry-After 반환. 공통 응답 status·code 제거 + 요청 추적 traceId 도입 #309 로 바디에서 status·code 가 빠져 동적 필드가 없어 정확히 흉내 가능. docker 로 429 응답이 application/json + Retry-After + 해당 JSON 바디임을 런타임 확인. (edac946)
  • (B) location =·...$ 만으로는 trailing slash 변형(/api/v1/wishlists/ 등)이 location / 로 떨어져 piki_llm 우회 가능 → 세 비싼 경로를 정규식 + /?$ 로 묶어 차단. Spring trailing-slash 매칭이 기본 false 라 앱은 404(비싼 호출 미실행)지만 방어적으로. (edac946)
  • 검증 중 발견: nginx return 은 rewrite phase 라 limit_req(preaccess phase)보다 먼저 실행돼 우회된다. 실제 conf 는 proxy_pass 라 정상 동작하며, 런타임 미니 테스트만 proxy_pass 백엔드로 고쳐 재현했다.

레이트리밋 정책 요약 (2층)

zone 대상 한도
1/2 piki_llm 비싼 Gemini 경로 4개(POST) 분당 30 + burst 20 POST 만(map) → $binary_remote_addr
2/2 piki_general 그 외 모든 경로 초당 20 + burst 40 raw $binary_remote_addr

Summary by CodeRabbit

  • 인프라
    • API에 IP 기반 레이트리밋을 도입하여 일부 고비용 POST 경로에 대해 더 엄격한 제한을 적용했습니다.
    • 그 외 엔드포인트에는 느슨한 전역 제한을 적용하여 전체 안정성을 향상시켰습니다.
    • 프록시 응답 제한이 90초로 설정되었습니다.
  • 버그 픽스
    • 레이트 리밋(429) 발생 시 클라이언트에 항상 일관된 JSON 에러 응답을 반환하도록 개선했습니다.

- 비싼 Gemini 경로(위시 등록·이미지 OCR, 토너먼트 link·images 추가) 4개에만 limit_req 적용. 일반 조회·CRUD 는 제외
- map 으로 POST 만 카운트 키에 담아, 같은 경로의 GET(위시 목록 조회)은 빈 키로 제외 — location 을 메서드별로 쪼개지 않고 POST 만 잡는다
- 키는 $binary_remote_addr: EIP 직결(앞단 LB 없음)이라 진짜 클라이언트 IP 이고, 단일 노드(blue/green 도 한 박스)라 shared memory zone 카운터가 IP 별로 정확하다
- 일부러 느슨하게(분당 30회 + burst 20 nodelay) 둬 CGNAT·공유망 오탐을 0 으로 하고 명백한 flood 만 막는 거친 1차 그물로 설계. 인증 사용자별 정밀 quota(userId + Redis)는 앱 레이어 후속 작업으로 분리 — 둘은 대체가 아니라 보완
- 초과 시 limit_req_status 로 기본 503 대신 429 반환. 단 이 429 바디는 ApiResponseBody 래퍼를 거치지 않는다(앱 앞 nginx 가 끊으므로)
- proxy_set_header·proxy_read_timeout 공통 설정을 server 레벨로 올려 location 간 중복 제거
- docker nginx 로 nginx -t 문법 검증 통과(ssl·upstream include 는 검증용 더미로 우회)
@m-a-king m-a-king added the infra 운영 환경 (IaC·클라우드 리소스·secret·배포 workflow) label Jun 1, 2026
@m-a-king m-a-king self-assigned this Jun 1, 2026
@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown

Slack 스레드 연동용 메타데이터입니다. slack-pr-bot 워크플로가 자동 생성하며, 수정·삭제하면 PR 과 Slack 알림 연동이 끊깁니다.

@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

POST 전용 IP 키와 두 개의 limit_req_zone(piki_llm, piki_general)을 도입하고, 비용이 큰 특정 POST 경로들에 빡센 제한을 적용하며 429은 고정 JSON으로 즉시 반환하도록 nginx 구성을 변경합니다. 서버 공통 proxy_read_timeout 90s도 추가됩니다.

Changes

Nginx 레이트리밋 및 경로 세분화

Layer / File(s) Summary
레이트리밋 정책 선언 및 공통 설정
infra/nginx/api.depromeet18team3.cloud.conf
POST 전용 map으로 키를 생성하고 limit_req_zone 두 개(piki_llm, piki_general) 및 limit_req_status 429를 선언. 서버 공통에 proxy_read_timeout 90s 추가. error_page 429 = @rate_limited와 `location `@rate_limited로 고정 JSON과 Retry-After 헤더 반환하도록 구성.
비용 경로 분리 및 레이트리밋 적용
infra/nginx/api.depromeet18team3.cloud.conf
Gemini 비용 경로(토너먼트 아이템 `link

리뷰 포인트 & 개선 제안

  • 정규식 매칭 범위 검증: ~* /tournaments/([0-9]+)/(link\|images) 등 정규식이 의도한 경로만 매칭하는지 확인하세요(세부 엔드포인트 충돌 검사 권장). nginx 정규식 우선순위 문서: https://nginx.org/en/docs/http/ngx_http_core_module.html#location
  • 429 핸들러 스키마 안정성: 앱이 기대하는 JSON 스키마와 정확히 일치하는지(필드명·타입) 검증하세요. 테스트로 429 반환 시 프런트엔드가 파싱되는지 확인하면 안전합니다.
  • 숫자 근거·모니터링: burst=20, piki_llm rate 값과 piki_general 설정은 운영 트래픽 모니터링 후 조정하세요. Grafana/로그 기반 임계치 검증을 권장합니다.
  • 타임아웃 일관성: proxy_read_timeout 90s가 업스트림 평균 응답시간과 부합하는지 로그로 검증하세요(타임아웃으로 인한 연결 누수 위험 점검).
  • 로깅·지표: 429 이벤트를 식별 가능한 로그/메트릭(label)에 포함시켜 알람을 걸어두세요.

짧고 굵게: 설정이 깔끔합니다 — 다만 정규식 범위와 수치 근거는 운영 검증이 필요합니다.


🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch infra/nginx-llm-rate-limit

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@infra/nginx/api.depromeet18team3.cloud.conf`:
- Line 35: Nginx currently returns plain 429 (limit_req_status 429) which breaks
clients expecting ApiResponseBody; add an error_page 429 that routes to a custom
`@rate_limited` handler which returns a JSON ApiResponseBody shape
({"data":null,"detail":"…","pageResponse":{"nextCursor":null,"hasNext":false}})
with Content-Type: application/json and a Retry-After header (use default_type
application/json and set_retry_after), and ensure the handler is referenced by
error_page 429; also relax the strict location matching (avoid exact-match
"location =" and trailing-end "$")—use a regex that accepts optional trailing
slash (e.g., /?$) or add a normalized redirect location so rate-limiting cannot
be bypassed by a trailing slash.
- Around line 71-83: The Nginx locations for the endpoints defined by location =
/api/v1/wishlists, location = /api/v1/wishlists/images and location ~
^/api/v1/tournaments/[^/]+/items/(link|images)$ can be bypassed by requests with
a trailing slash (e.g. /api/v1/wishlists/), so update these blocks to match both
forms or canonicalize: change the exact-match locations to regexes that accept
an optional trailing slash (e.g. ^/api/v1/wishlists/?$ and
^/api/v1/wishlists/images/?$) and adjust the tournament regex to include /?$ at
the end (e.g. ^/api/v1/tournaments/[^/]+/items/(link|images)/?$), ensuring the
limit_req and proxy_pass directives remain inside those updated location blocks;
alternatively add a small redirect rule to canonicalize trailing slashes to the
non-slash form before proxying.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 3334c2f2-dea3-487e-8848-417b9e8a95f9

📥 Commits

Reviewing files that changed from the base of the PR and between 879ef86 and 3bc0446.

📒 Files selected for processing (1)
  • infra/nginx/api.depromeet18team3.cloud.conf

Comment thread infra/nginx/api.depromeet18team3.cloud.conf Outdated
Comment thread infra/nginx/api.depromeet18team3.cloud.conf Outdated
@github-actions
github-actions Bot requested review from 1o18z and sevineleven June 1, 2026 03:43
- 비싼 Gemini 경로만 막던 데서, 모든 경로에 아주 느슨한 전역 그물(piki_general, 초당 20 + burst 40)을 2층으로 추가
- 일반 조회·CRUD 도 무제한이면 스크래핑·flood 에 노출되므로 명백한 폭주는 컷한다. 단 일반 경로는 정상 트래픽(화면당 GET 여럿·무한스크롤)이 비싼 경로보다 잦아, 같은 강도면 정상 사용자가 막힌다 — 그래서 비싼 경로(1/2)보다 훨씬 느슨하게 둬 오탐을 막는 거친 그물로 설계
- 키는 raw $binary_remote_addr(메서드·경로 무관 전부 카운트). 비싼 경로는 piki_llm(초당 0.5)이 이보다 타이트해 지배하므로 location / 에만 적용(겹치지 않음)
- docker nginx -t 문법 검증 통과
@m-a-king m-a-king changed the title nginx IP별 레이트리밋으로 Gemini 호출 폭주 1차 차단 nginx IP별 레이트리밋 — 비싼 Gemini 경로 + 전역 2층 Jun 1, 2026
m-a-king and others added 2 commits June 1, 2026 20:09
CodeRabbit 리뷰 반영.
- (A) limit_req_status 429 만으로는 nginx 기본 응답이라, 클라이언트가 "모든 응답=ApiResponseBody" 로 파싱하면 레이트리밋 구간에서 깨진다. error_page 429 → @rate_limited 로 같은 스키마({data, detail, pageResponse}) 고정 JSON + Retry-After 를 내린다. #309 로 바디에서 status·code 가 빠져 동적 필드가 없어 정확히 흉내 가능. 완전한 해법은 후속 userId 앱 레이어(GlobalExceptionHandler 가 진짜 ApiResponseBody 생성)이고, 그 전까지의 정합성 보강이다
- (B) location = 와 ...$ 로만 매칭하면 trailing slash 변형(/api/v1/wishlists/ 등)이 location / 로 떨어져 piki_llm(비싼 경로 타이트 제한)을 우회한다. 세 비싼 경로를 모두 정규식 + /?$ 로 묶어 차단. Spring trailing-slash 매칭이 기본 false 라 앱은 404 를 주지만(비싼 호출 미실행), 방어를 견고하게 둔다
- docker 로 런타임 검증: error_page 429 가 실제로 JSON + Retry-After 를 반환함을 확인. (검증 중 return 은 rewrite phase 라 limit_req(preaccess) 를 우회함을 발견 — 미니 테스트를 proxy_pass 백엔드로 고쳐 재현)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
infra/nginx/api.depromeet18team3.cloud.conf (1)

92-105: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

비용 regex location에 piki_general도 함께 걸어 전역 레이트리밋 우회 가능성을 제거하세요.
infra/nginx/api.depromeet18team3.cloud.conf에서 piki_generallocation / { limit_req zone=piki_general ... }에만 있고, 비용 경로 3개(location ~ ^/api/v1/...)는 regex 매칭으로 location /을 대체합니다. 그 결과 비용 경로에 해당하는 GET/PUT 등 비-POST 요청은 piki_general이 적용되지 않아 /api/v1/wishlists 같은 엔드포인트가 스크래핑/플러드에 상대적으로 취약해질 수 있습니다. 비용 3개 location에도 limit_req zone=piki_general ...을 추가해 우회 여지를 없애세요.

🔧 제안 diff
     location ~ ^/api/v1/tournaments/[^/]+/items/(link|images)/?$ {
+        limit_req zone=piki_general burst=40 nodelay;
         limit_req zone=piki_llm burst=20 nodelay;
         proxy_pass http://team3;
     }

     location ~ ^/api/v1/wishlists/?$ {
+        limit_req zone=piki_general burst=40 nodelay;
         limit_req zone=piki_llm burst=20 nodelay;
         proxy_pass http://team3;
     }

     location ~ ^/api/v1/wishlists/images/?$ {
+        limit_req zone=piki_general burst=40 nodelay;
         limit_req zone=piki_llm burst=20 nodelay;
         proxy_pass http://team3;
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@infra/nginx/api.depromeet18team3.cloud.conf` around lines 92 - 105, The three
regex location blocks matching
"^/api/v1/tournaments/[^/]+/items/(link|images)/?$", "^/api/v1/wishlists/?$" and
"^/api/v1/wishlists/images/?$" currently only apply limit_req zone=piki_llm;
update each of these location blocks to also include limit_req zone=piki_general
(with appropriate burst/nodelay settings) so the global rate limit cannot be
bypassed by regex locations, ensuring both piki_llm and piki_general run for
those endpoints.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@infra/nginx/api.depromeet18team3.cloud.conf`:
- Around line 92-105: The three regex location blocks matching
"^/api/v1/tournaments/[^/]+/items/(link|images)/?$", "^/api/v1/wishlists/?$" and
"^/api/v1/wishlists/images/?$" currently only apply limit_req zone=piki_llm;
update each of these location blocks to also include limit_req zone=piki_general
(with appropriate burst/nodelay settings) so the global rate limit cannot be
bypassed by regex locations, ensuring both piki_llm and piki_general run for
those endpoints.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 2695253b-fb82-4078-9e60-50f23c4479b8

📥 Commits

Reviewing files that changed from the base of the PR and between 3bc0446 and 94b3887.

📒 Files selected for processing (1)
  • infra/nginx/api.depromeet18team3.cloud.conf

@m-a-king
m-a-king merged commit b7ea395 into dev Jun 1, 2026
9 checks passed
@m-a-king
m-a-king deleted the infra/nginx-llm-rate-limit branch June 1, 2026 12:25
m-a-king added a commit that referenced this pull request Jun 6, 2026
- SSE 누락 조사 중, 같은 분리 누락으로 dev 에만 빠진 설정이 더 있음을 확인해 보정한다. dev/prod nginx conf 가 별도 파일이라 #374 분리 때 dev 가 prod 축약본으로 만들어지면서 레이트리밋(#332)과 listen 443 의 http2 가 dev 에서 누락돼 있었다.
- 레이트리밋: 비싼 Gemini 경로(piki_llm 분당 30) + 전역 그물(piki_general 초당 20) + 429 를 ApiResponseBody 모양 JSON 으로 내리는 처리까지 prod 와 동일하게 추가. dev 서버에서도 429 가 발생하는 동작 변화가 있으나, 드리프트를 남기면 레이트리밋 회귀를 prod 에서만 처음 겪게 되므로 정합을 택했다.
- http2: listen 443 ssl http2. 클라이언트와 nginx 사이 leg 최적화라 백엔드 동작과 무관하고 무해하다.
- 정합 후 dev conf 는 prod 와 nginx 지시어가 100% 동일하고, server_name·ssl_certificate 경로의 도메인 문자열만 환경별로 다르다. 주석은 dev 고유 헤더(정합 의도·certbot 안내)만 차이.
- 검증: 로컬 docker nginx -t 통과. dev conf 를 prod 도메인으로 정규화해 diff 하면 지시어 차이 0(주석만 다름) 확인.
m-a-king added a commit that referenced this pull request Jun 6, 2026
* fix: dev nginx 에 SSE 구독 프록시 설정 추가 (prod 와 정합)

- dev 서버에서 /api/v1/notifications/subscribe 가 catch-all location / 로 빠져 nginx 기본값(proxy_buffering on + proxy_http_version 1.0)으로 처리돼, SSE 스트림(connect·하트비트·알림)이 버퍼에 갇혀 클라이언트까지 흐르지 않던 문제. connect·하트비트조차 안 와 비즈니스 로직(수신자 actor 제외)이 아니라 전송 파이프라인 문제로 좁혀짐.
- 원인은 prod conf(#367)에 있던 SSE 전용 location 이 dev/prod 분리(#374) 때 dev conf 축약본으로 옮겨지지 않은 누락. #399 가 같은 종류의 누락(read_timeout)을 한 번 보정했으나 SSE location 은 남아 있었다.
- prod 와 동일하게 proxy_http_version 1.1(1.0 은 chunked 스트리밍 불가) + proxy_buffering off 를 둔 SSE 전용 location 을 추가.
- 공통 proxy_set_header 4개와 proxy_read_timeout 60s 를 location / 에서 server 레벨로 추출해 SSE location 도 상속하게 함 (prod 구조와 일치, location / 동작은 불변).
- 레이트리밋·http2 도 같은 분리 누락으로 dev 에 빠져 있으나 동작 변화가 있어 이 핫픽스에서 제외하고 별도로 다룬다.
- 로컬 docker nginx -t 로 dev·prod conf 둘 다 syntax 통과 검증.

* chore: dev nginx 를 prod 와 정합 — 레이트리밋·http2 추가

- SSE 누락 조사 중, 같은 분리 누락으로 dev 에만 빠진 설정이 더 있음을 확인해 보정한다. dev/prod nginx conf 가 별도 파일이라 #374 분리 때 dev 가 prod 축약본으로 만들어지면서 레이트리밋(#332)과 listen 443 의 http2 가 dev 에서 누락돼 있었다.
- 레이트리밋: 비싼 Gemini 경로(piki_llm 분당 30) + 전역 그물(piki_general 초당 20) + 429 를 ApiResponseBody 모양 JSON 으로 내리는 처리까지 prod 와 동일하게 추가. dev 서버에서도 429 가 발생하는 동작 변화가 있으나, 드리프트를 남기면 레이트리밋 회귀를 prod 에서만 처음 겪게 되므로 정합을 택했다.
- http2: listen 443 ssl http2. 클라이언트와 nginx 사이 leg 최적화라 백엔드 동작과 무관하고 무해하다.
- 정합 후 dev conf 는 prod 와 nginx 지시어가 100% 동일하고, server_name·ssl_certificate 경로의 도메인 문자열만 환경별로 다르다. 주석은 dev 고유 헤더(정합 의도·certbot 안내)만 차이.
- 검증: 로컬 docker nginx -t 통과. dev conf 를 prod 도메인으로 정규화해 diff 하면 지시어 차이 0(주석만 다름) 확인.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

infra 운영 환경 (IaC·클라우드 리소스·secret·배포 workflow)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants