보안 개선: - API 키 하드코딩 제거 (NAMECHEAP_API_KEY_INTERNAL) - CORS 정책: * → hosting.anvil.it.com 제한 - /health 엔드포인트 DB 정보 노출 방지 - Rate Limiting 인메모리 Map → Cloudflare KV 전환 - 분산 환경 일관성 보장 - 재시작 후에도 유지 - 자동 만료 (TTL) 문서: - CLAUDE.md Security 섹션 추가 - KV Namespace 설정 가이드 추가 - 배포/마이그레이션 가이드 추가 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
8.5 KiB
8.5 KiB
Rate Limiting KV Migration Guide
변경 사항 요약
Rate Limiting 시스템을 인메모리 Map에서 Cloudflare KV로 마이그레이션했습니다.
기존 문제점
- Workers 인스턴스 간 공유되지 않음
- 재시작 시 초기화됨
- 분산 환경에서 Rate Limit 우회 가능
개선 사항
✅ 인스턴스 간 공유 (KV 기반) ✅ 재시작 후에도 유지 ✅ 분산 환경에서 일관된 Rate Limiting ✅ 자동 만료 (TTL)
배포 절차
1. KV Namespace 생성
# 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번째 줄을 수정합니다:
[[kv_namespaces]]
binding = "RATE_LIMIT_KV"
id = "abc123def456ghi789jkl012mno345pq" # ← 실제 ID로 변경
3. 로컬 개발용 Namespace 생성 (선택사항)
로컬 테스트 시 별도의 KV를 사용하려면:
wrangler kv:namespace create RATE_LIMIT_KV --preview
출력된 preview_id를 wrangler.toml에 추가:
[[kv_namespaces]]
binding = "RATE_LIMIT_KV"
id = "abc123def456ghi789jkl012mno345pq"
preview_id = "xyz789abc123def456ghi012jkl345mno" # 로컬 개발용
4. TypeScript 컴파일 확인
npx tsc --noEmit
에러가 없어야 합니다.
5. 로컬 테스트
npm run dev
다른 터미널에서 테스트 요청:
# 첫 번째 요청 (성공)
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 배포
npm run deploy
7. 배포 확인
# 로그 스트리밍
npm run tail
# Health Check
curl https://telegram-summary-bot.kappa-d8e.workers.dev/health
변경된 파일
/Users/kaffa/telegram-bot-workers/wrangler.toml
[[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
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
-// Rate Limiting
-const rateLimitMap = new Map<string, { count: number; resetAt: number }>();
-
-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<boolean> {
+ // KV 기반 로직 (자동 TTL)
+}
/Users/kaffa/telegram-bot-workers/src/index.ts
- if (!checkRateLimit(telegramUserId)) {
+ if (!(await checkRateLimit(env.RATE_LIMIT_KV, telegramUserId))) {
await sendMessage(
env.BOT_TOKEN,
chatId,
'⚠️ 너무 많은 요청입니다. 잠시 후 다시 시도해주세요.'
);
return;
}
기술 상세
Rate Limiting 알고리즘
Key 형식: ratelimit:{userId}
데이터 구조:
{
count: number, // 현재 윈도우 내 요청 수
resetAt: number // 윈도우 만료 시각 (Unix timestamp)
}
처리 로직:
- KV에서 사용자별 카운터 조회
- 윈도우 만료 확인 (현재 시각 > resetAt)
- 만료 시: 새 윈도우 시작 (count=1)
- 유효 시: count 확인
- count >= maxRequests (기본 30) → Rate Limit 초과
- count < maxRequests → count 증가 후 KV 업데이트
자동 만료:
- KV의
expirationTtl사용 (초 단위) - 윈도우 종료 시 자동 삭제 (메모리 효율)
에러 처리:
- KV 오류 시 Rate Limit 통과 (서비스 가용성 우선)
- 로그 기록:
[RateLimit] KV 오류: ...
모니터링
KV Namespace 확인
# 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
- https://dash.cloudflare.com → Workers & Pages
- KV →
telegram-summary-bot-RATE_LIMIT_KV클릭 - Key-Value 쌍 확인 (실시간)
로그 확인
wrangler tail
# Rate Limit 오류만 필터링
wrangler tail --format json | jq 'select(.message | contains("RateLimit"))'
트러블슈팅
KV Namespace ID를 잊었을 때
wrangler kv:namespace list
KV 데이터 초기화 (테스트용)
# 특정 사용자 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 테스트 스크립트
#!/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. 이전 버전 복구
git revert HEAD
npm run deploy
2. wrangler.toml에서 KV Namespace 제거
# 아래 3줄 주석 처리 또는 삭제
# [[kv_namespaces]]
# binding = "RATE_LIMIT_KV"
# id = "..."
3. 재배포
npm run deploy
FAQ
Q: 로컬 개발 시 KV를 사용하지 않으려면?
A: --remote 플래그 사용하여 Production KV 직접 사용 (권장하지 않음)
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() 파라미터 수정
// 기본값: 30 requests / 60초
export async function checkRateLimit(
kv: KVNamespace,
userId: string,
maxRequests: number = 50, // ← 변경
windowMs: number = 120000 // ← 2분으로 변경
): Promise<boolean>
문서 업데이트
이 마이그레이션으로 인해 다음 문서도 업데이트 필요:
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 메시지 테스트 완료
배포 완료!