# Rate Limiting KV Migration Guide ## 변경 사항 요약 Rate Limiting 시스템을 인메모리 Map에서 Cloudflare KV로 마이그레이션했습니다. ### 기존 문제점 - Workers 인스턴스 간 공유되지 않음 - 재시작 시 초기화됨 - 분산 환경에서 Rate Limit 우회 가능 ### 개선 사항 ✅ 인스턴스 간 공유 (KV 기반) ✅ 재시작 후에도 유지 ✅ 분산 환경에서 일관된 Rate Limiting ✅ 자동 만료 (TTL) --- ## 배포 절차 ### 1. KV Namespace 생성 ```bash # Production용 Namespace 생성 wrangler kv:namespace create RATE_LIMIT_KV ``` 출력 예시: ``` ⛅️ wrangler 3.x.x ------------------- 🌀 Creating namespace with title "telegram-summary-bot-RATE_LIMIT_KV" ✨ Success! Add the following to your configuration file in your kv_namespaces array: { binding = "RATE_LIMIT_KV", id = "abc123def456ghi789jkl012mno345pq" } ``` ### 2. wrangler.toml 업데이트 위에서 출력된 `id` 값을 복사하여 `wrangler.toml` 파일의 20-22번째 줄을 수정합니다: ```toml [[kv_namespaces]] binding = "RATE_LIMIT_KV" id = "abc123def456ghi789jkl012mno345pq" # ← 실제 ID로 변경 ``` ### 3. 로컬 개발용 Namespace 생성 (선택사항) 로컬 테스트 시 별도의 KV를 사용하려면: ```bash wrangler kv:namespace create RATE_LIMIT_KV --preview ``` 출력된 `preview_id`를 `wrangler.toml`에 추가: ```toml [[kv_namespaces]] binding = "RATE_LIMIT_KV" id = "abc123def456ghi789jkl012mno345pq" preview_id = "xyz789abc123def456ghi012jkl345mno" # 로컬 개발용 ``` ### 4. TypeScript 컴파일 확인 ```bash npx tsc --noEmit ``` 에러가 없어야 합니다. ### 5. 로컬 테스트 ```bash npm run dev ``` 다른 터미널에서 테스트 요청: ```bash # 첫 번째 요청 (성공) curl -X POST http://localhost:8787/webhook \ -H "Content-Type: application/json" \ -H "X-Telegram-Bot-Api-Secret-Token: ${WEBHOOK_SECRET}" \ -d '{"update_id":1,"message":{"message_id":1,"from":{"id":123,"is_bot":false,"first_name":"Test"},"chat":{"id":123,"type":"private"},"date":1234567890,"text":"테스트"}}' # 30번 연속 요청 후 31번째 요청 (Rate Limit) # Rate Limit 메시지가 표시되어야 함 ``` ### 6. Production 배포 ```bash npm run deploy ``` ### 7. 배포 확인 ```bash # 로그 스트리밍 npm run tail # Health Check curl https://telegram-summary-bot.kappa-d8e.workers.dev/health ``` --- ## 변경된 파일 ### `/Users/kaffa/telegram-bot-workers/wrangler.toml` ```diff [[d1_databases]] binding = "DB" database_name = "telegram-conversations" database_id = "c285bb5b-888b-405d-b36f-475ae5aed20e" +[[kv_namespaces]] +binding = "RATE_LIMIT_KV" +id = "YOUR_KV_NAMESPACE_ID" # Run: wrangler kv:namespace create RATE_LIMIT_KV ``` ### `/Users/kaffa/telegram-bot-workers/src/types.ts` ```diff export interface Env { DB: D1Database; AI: Ai; BOT_TOKEN: string; WEBHOOK_SECRET: string; // ... 기타 환경변수 + RATE_LIMIT_KV: KVNamespace; } ``` ### `/Users/kaffa/telegram-bot-workers/src/security.ts` ```diff -// Rate Limiting -const rateLimitMap = new Map(); - -export function checkRateLimit( - userId: string, - maxRequests: number = 30, - windowMs: number = 60000 -): boolean { - // 인메모리 Map 기반 로직 -} +// Rate Limiting (Cloudflare KV 기반) +interface RateLimitData { + count: number; + resetAt: number; +} + +export async function checkRateLimit( + kv: KVNamespace, + userId: string, + maxRequests: number = 30, + windowMs: number = 60000 +): Promise { + // KV 기반 로직 (자동 TTL) +} ``` ### `/Users/kaffa/telegram-bot-workers/src/index.ts` ```diff - if (!checkRateLimit(telegramUserId)) { + if (!(await checkRateLimit(env.RATE_LIMIT_KV, telegramUserId))) { await sendMessage( env.BOT_TOKEN, chatId, '⚠️ 너무 많은 요청입니다. 잠시 후 다시 시도해주세요.' ); return; } ``` --- ## 기술 상세 ### Rate Limiting 알고리즘 **Key 형식:** `ratelimit:{userId}` **데이터 구조:** ```typescript { count: number, // 현재 윈도우 내 요청 수 resetAt: number // 윈도우 만료 시각 (Unix timestamp) } ``` **처리 로직:** 1. KV에서 사용자별 카운터 조회 2. 윈도우 만료 확인 (현재 시각 > resetAt) - 만료 시: 새 윈도우 시작 (count=1) - 유효 시: count 확인 3. count >= maxRequests (기본 30) → Rate Limit 초과 4. count < maxRequests → count 증가 후 KV 업데이트 **자동 만료:** - KV의 `expirationTtl` 사용 (초 단위) - 윈도우 종료 시 자동 삭제 (메모리 효율) **에러 처리:** - KV 오류 시 Rate Limit 통과 (서비스 가용성 우선) - 로그 기록: `[RateLimit] KV 오류: ...` --- ## 모니터링 ### KV Namespace 확인 ```bash # KV Namespace 목록 wrangler kv:namespace list # 특정 키 조회 wrangler kv:key get "ratelimit:821596605" --namespace-id=YOUR_KV_ID # 모든 키 목록 wrangler kv:key list --namespace-id=YOUR_KV_ID ``` ### Cloudflare Dashboard 1. https://dash.cloudflare.com → Workers & Pages 2. KV → `telegram-summary-bot-RATE_LIMIT_KV` 클릭 3. Key-Value 쌍 확인 (실시간) ### 로그 확인 ```bash wrangler tail # Rate Limit 오류만 필터링 wrangler tail --format json | jq 'select(.message | contains("RateLimit"))' ``` --- ## 트러블슈팅 ### KV Namespace ID를 잊었을 때 ```bash wrangler kv:namespace list ``` ### KV 데이터 초기화 (테스트용) ```bash # 특정 사용자 Rate Limit 초기화 wrangler kv:key delete "ratelimit:821596605" --namespace-id=YOUR_KV_ID # 전체 초기화 (주의!) wrangler kv:key list --namespace-id=YOUR_KV_ID | jq -r '.[] | .name' | xargs -I {} wrangler kv:key delete "{}" --namespace-id=YOUR_KV_ID ``` ### Rate Limit 테스트 스크립트 ```bash #!/bin/bash # rate-limit-test.sh TOKEN="YOUR_WEBHOOK_SECRET" for i in {1..35}; do echo "Request $i:" curl -s -X POST http://localhost:8787/webhook \ -H "Content-Type: application/json" \ -H "X-Telegram-Bot-Api-Secret-Token: $TOKEN" \ -d '{"update_id":'$i',"message":{"message_id":'$i',"from":{"id":123,"is_bot":false,"first_name":"Test"},"chat":{"id":123,"type":"private"},"date":1234567890,"text":"테스트 '$i'"}}' \ | jq -r '.message // "OK"' sleep 0.5 done ``` --- ## 성능 영향 ### Before (인메모리 Map) - 응답 시간: ~1ms (동기) - 메모리: 인스턴스별 독립 - 분산 환경: 비효율적 ### After (KV) - 응답 시간: ~20-50ms (KV read/write 포함) - 메모리: 0 (KV로 오프로드) - 분산 환경: 일관성 보장 **권장 사항:** KV 호출을 줄이기 위해 windowMs를 늘리지 말 것 (현재 60초 최적) --- ## 롤백 절차 (문제 발생 시) ### 1. 이전 버전 복구 ```bash git revert HEAD npm run deploy ``` ### 2. wrangler.toml에서 KV Namespace 제거 ```toml # 아래 3줄 주석 처리 또는 삭제 # [[kv_namespaces]] # binding = "RATE_LIMIT_KV" # id = "..." ``` ### 3. 재배포 ```bash npm run deploy ``` --- ## FAQ **Q: 로컬 개발 시 KV를 사용하지 않으려면?** A: `--remote` 플래그 사용하여 Production KV 직접 사용 (권장하지 않음) ```bash wrangler dev --remote ``` **Q: KV 비용은 얼마나 드나요?** A: Cloudflare Workers Free Plan: 100,000 read/day, 1,000 write/day 무료 - Rate Limiting: 1 read + 1 write per request - 하루 1,000명 사용자까지 무료 **Q: KV가 느린 경우?** A: KV는 글로벌 분산 스토리지이므로 20-50ms latency 정상 - 대안: Durable Objects (더 빠르지만 비용 높음) **Q: Rate Limit 설정 변경하려면?** A: `security.ts:checkRateLimit()` 파라미터 수정 ```typescript // 기본값: 30 requests / 60초 export async function checkRateLimit( kv: KVNamespace, userId: string, maxRequests: number = 50, // ← 변경 windowMs: number = 120000 // ← 2분으로 변경 ): Promise ``` --- ## 문서 업데이트 이 마이그레이션으로 인해 다음 문서도 업데이트 필요: - [x] `CLAUDE.md` - Rate Limiting 섹션 수정 - [ ] `README.md` - 설치/배포 절차 업데이트 (KV Namespace 생성 추가) - [ ] `schema.sql` - 변경 없음 (KV는 별도 저장소) --- ## 체크리스트 배포 전 확인: - [ ] `wrangler kv:namespace create RATE_LIMIT_KV` 실행 완료 - [ ] `wrangler.toml`에 실제 KV Namespace ID 입력 - [ ] `npx tsc --noEmit` 타입 에러 없음 - [ ] 로컬 테스트 (`npm run dev`) 정상 동작 - [ ] Rate Limit 테스트 (30회 연속 요청) 성공 - [ ] Production 배포 (`npm run deploy`) 완료 - [ ] Health Check 확인 - [ ] 실제 Telegram 메시지 테스트 완료 배포 완료!