- Add cdn_enabled and cdn_cache_hit_rate API parameters - Use case별 기본 캐시 히트율 자동 적용 (video: 92%, blog: 90%, etc.) - 원본 서버 트래픽(origin_monthly_tb)과 절감 비용(cdn_savings_cost) 계산 - 응답에 CDN breakdown 필드 추가 (bandwidth_estimate, bandwidth_info) - 캐시 키에 CDN 옵션 포함하여 정확한 캐시 분리 - 4개 CDN 관련 테스트 추가 (총 59 tests) - CLAUDE.md 문서 업데이트 Cost impact example (10K video streaming): - Without CDN: $18,370 → With CDN 92%: $1,464 (92% savings) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
419 lines
18 KiB
Markdown
419 lines
18 KiB
Markdown
# CLAUDE.md
|
||
|
||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||
|
||
## Project Overview
|
||
|
||
Cloudflare Worker-based AI server recommendation service. Uses OpenAI GPT-4o-mini (via AI Gateway), D1 database, KV cache, and VPS benchmark data to recommend cost-effective servers based on natural language requirements.
|
||
|
||
**Production URL**: `https://cloud-orchestrator.kappa-d8e.workers.dev`
|
||
|
||
## Commands
|
||
|
||
```bash
|
||
# Development
|
||
npm run dev # Start local development server (wrangler dev)
|
||
npm run deploy # Deploy to Cloudflare Workers
|
||
npm run typecheck # TypeScript type checking
|
||
npm test # Run tests (Vitest)
|
||
npm run test:watch # Run tests in watch mode
|
||
|
||
# Database operations (D1)
|
||
npx wrangler d1 execute cloud-instances-db --file=schema.sql # Apply schema
|
||
npx wrangler d1 execute cloud-instances-db --file=seed.sql # Seed data
|
||
npx wrangler d1 execute cloud-instances-db --file=fix-tech-specs.sql # Update tech specs
|
||
npx wrangler d1 execute cloud-instances-db --command="SELECT ..." # Ad-hoc queries
|
||
|
||
# View logs
|
||
npx wrangler tail
|
||
```
|
||
|
||
## Architecture
|
||
|
||
```
|
||
src/
|
||
├── index.ts # Main router, CORS, request handling
|
||
├── config.ts # Configuration constants
|
||
├── types.ts # TypeScript type definitions
|
||
├── region-utils.ts # Region matching utilities
|
||
├── utils.ts # Re-exports from utils/ (backward compatibility)
|
||
├── utils/ # Modular utilities (split from monolithic utils.ts)
|
||
│ ├── index.ts # Central export point
|
||
│ ├── http.ts # HTTP responses, CORS, escapeHtml
|
||
│ ├── validation.ts # Input validation, type guards
|
||
│ ├── bandwidth.ts # Bandwidth estimation & cost calculation
|
||
│ ├── cache.ts # Caching, rate limiting
|
||
│ ├── ai.ts # AI prompt sanitization
|
||
│ └── exchange-rate.ts # Currency conversion
|
||
├── repositories/
|
||
│ └── AnvilServerRepository.ts # DB queries for Anvil servers
|
||
├── services/
|
||
│ └── ai-service.ts # AI recommendations & fallback logic
|
||
├── handlers/
|
||
│ ├── health.ts # GET /api/health
|
||
│ ├── servers.ts # GET /api/servers
|
||
│ ├── recommend.ts # POST /api/recommend
|
||
│ └── report.ts # GET /api/recommend/report
|
||
└── __tests__/
|
||
├── utils.test.ts # Validation & security tests (27 tests)
|
||
└── bandwidth.test.ts # Bandwidth estimation tests (32 tests, including CDN)
|
||
```
|
||
|
||
### Key Data Flow
|
||
|
||
1. User sends request (`tech_stack`, `expected_users`, `use_case`, `region_preference`)
|
||
2. Tech specs calculation with **DB workload multiplier** based on use_case
|
||
3. Candidate filtering with **flexible region matching**
|
||
4. VPS benchmarks retrieval (Geekbench 6), **prioritizing same provider**
|
||
5. AI analysis returns 3 tiers: Budget, Balanced, Premium
|
||
6. Results cached in KV (5 min TTL, empty results not cached)
|
||
|
||
### D1 Database Tables (cloud-instances-db)
|
||
|
||
**Primary tables (Anvil pricing)**:
|
||
- `anvil_instances` - Anvil server specifications (vcpus, memory_gb, disk_gb, transfer_tb, etc.)
|
||
- `anvil_regions` - Anvil data center regions (name, display_name, country_code)
|
||
- `anvil_pricing` - Anvil pricing data (monthly_price in USD)
|
||
- `anvil_transfer_pricing` - Transfer/bandwidth overage pricing by region (price_per_gb in USD)
|
||
|
||
**Support tables**:
|
||
- `tech_specs` - Resource requirements per technology (vcpu_per_users, min_memory_mb)
|
||
- `vps_benchmarks` - Geekbench 6 benchmark data (269 records)
|
||
- `benchmark_results` / `benchmark_types` / `processors` - Phoronix benchmark data
|
||
|
||
**Legacy tables (no longer used)**:
|
||
- `providers`, `instance_types`, `pricing`, `regions` - Old Linode/Vultr data
|
||
|
||
## Key Implementation Details
|
||
|
||
### DB Workload Multiplier (`recommend.ts`)
|
||
|
||
Database resource requirements vary by workload type, not just user count:
|
||
|
||
| Workload Type | Multiplier | Example Use Cases |
|
||
|---------------|------------|-------------------|
|
||
| Heavy | 0.3x | analytics, log server, reporting, dashboard |
|
||
| Medium-Heavy | 0.5x | e-commerce, ERP, CRM, community forum |
|
||
| Medium | 0.7x | API, SaaS, app backend |
|
||
| Light | 1.0x | blog, portfolio, documentation, wiki |
|
||
|
||
**Example**: PostgreSQL (vcpu_per_users: 200) with 1000 users
|
||
- Analytics dashboard: 1000 / (200 × 0.3) = 17 vCPU
|
||
- E-commerce: 1000 / (200 × 0.5) = 10 vCPU
|
||
- Personal blog: 1000 / (200 × 1.0) = 5 vCPU
|
||
|
||
### Bandwidth Estimation (`bandwidth.ts`)
|
||
|
||
Estimates monthly bandwidth based on use_case patterns:
|
||
|
||
| Pattern | Page Size | Pages/Day | Active Ratio |
|
||
|---------|-----------|-----------|--------------|
|
||
| E-commerce | 2.5MB | 20 | 40% |
|
||
| Streaming | 50MB | 5 | 20% |
|
||
| Analytics | 0.7MB | 30 | 50% |
|
||
| Blog/Content | 1.5MB | 4 | 30% |
|
||
|
||
Heavy bandwidth (>1TB/month) triggers warning about overage costs.
|
||
|
||
### CDN Cache Hit Rate (`bandwidth.ts`)
|
||
|
||
CDN 사용 시 원본 서버 트래픽을 자동 계산하여 실제 비용을 추정합니다.
|
||
|
||
**API Parameters**:
|
||
- `cdn_enabled`: CDN 사용 여부 (기본: `true`)
|
||
- `cdn_cache_hit_rate`: 캐시 히트율 override (0.0-1.0, 기본: use_case별 자동)
|
||
|
||
**Use Case별 기본 캐시 히트율**:
|
||
|
||
| Use Case | 캐시 히트율 | 설명 |
|
||
|----------|-------------|------|
|
||
| video/streaming | 92% | 동일 영상 반복 시청 |
|
||
| blog/static | 90% | 대부분 정적 콘텐츠 |
|
||
| file/download | 85% | 소프트웨어/에셋 다운로드 |
|
||
| ecommerce | 70% | 상품 이미지 캐시 가능 |
|
||
| forum | 60% | 정적/동적 혼합 |
|
||
| api/saas | 30% | 동적 응답 위주 |
|
||
| gaming | 20% | 실시간 통신 |
|
||
| chat | 10% | 실시간 메시지 |
|
||
|
||
**응답 필드** (`bandwidth_estimate`):
|
||
- `cdn_enabled`: CDN 사용 여부
|
||
- `cdn_cache_hit_rate`: 적용된 캐시 히트율
|
||
- `gross_monthly_tb`: CDN 적용 전 총 트래픽
|
||
- `origin_monthly_tb`: CDN 적용 후 원본 서버 트래픽
|
||
|
||
**응답 필드** (`bandwidth_info`):
|
||
- `cdn_savings_tb`: CDN으로 절감된 트래픽 (TB)
|
||
- `cdn_savings_cost`: CDN으로 절감된 비용 ($)
|
||
|
||
**비용 절감 예시** (10,000 동시접속 video streaming):
|
||
```
|
||
CDN 없음: 총 2,966TB → 원본 2,966TB → 초과비용 $18,370
|
||
CDN 92%: 총 2,966TB → 원본 237TB → 초과비용 $1,464
|
||
절감: 2,729TB 절감, $16,906 절감 (92%)
|
||
```
|
||
|
||
### Flexible Region Matching
|
||
|
||
Both `/api/recommend` and `/api/servers` use `buildFlexibleRegionConditionsAnvil()` for anvil_regions:
|
||
```sql
|
||
LOWER(ar.name) = ? OR
|
||
LOWER(ar.name) LIKE ? OR
|
||
LOWER(ar.display_name) LIKE ? OR
|
||
LOWER(ar.country_code) = ?
|
||
```
|
||
|
||
Valid inputs: `"korea"`, `"KR"`, `"seoul"`, `"tokyo"`, `"japan"`, `"ap-northeast-2"`, `"icn"`
|
||
|
||
Country names are auto-expanded via `COUNTRY_NAME_TO_REGIONS` mapping.
|
||
|
||
### Exchange Rate Handling (`utils.ts`)
|
||
|
||
- Korean users (lang=ko) see prices in KRW
|
||
- Exchange rate fetched from open.er-api.com with 1-hour KV cache
|
||
- Fallback rate: 1450 KRW/USD if API unavailable
|
||
|
||
### Transfer Pricing (`bandwidth_info`)
|
||
|
||
Each recommendation includes `bandwidth_info` with transfer/bandwidth cost details:
|
||
|
||
| Field | Description | KRW Rounding |
|
||
|-------|-------------|--------------|
|
||
| `included_transfer_tb` | Free bandwidth included in plan (TB/month) | - |
|
||
| `overage_cost_per_gb` | Overage cost per GB | 1원 단위 |
|
||
| `overage_cost_per_tb` | Overage cost per TB | 100원 단위 |
|
||
| `estimated_monthly_tb` | Estimated monthly usage (TB) | - |
|
||
| `estimated_overage_tb` | Estimated overage (TB) | - |
|
||
| `estimated_overage_cost` | Estimated overage charges | 100원 단위 |
|
||
| `total_estimated_cost` | Server + overage total | 100원 단위 |
|
||
| `currency` | "USD" or "KRW" | - |
|
||
|
||
**Data sources**:
|
||
- `included_transfer_tb`: From `anvil_instances.transfer_tb`
|
||
- `overage_cost_per_gb`: From `anvil_transfer_pricing.price_per_gb`
|
||
|
||
### HTML Report Endpoint (`handlers/report.ts`)
|
||
|
||
`GET /api/recommend/report?data={base64}&lang={en|ko}`
|
||
|
||
Generates printable/PDF-friendly HTML report from recommendation results.
|
||
|
||
**Parameters**:
|
||
- `data`: Base64-encoded JSON of recommendation response
|
||
- `lang`: Language (en, ko, ja, zh) - defaults to 'en'
|
||
|
||
**Usage**:
|
||
```javascript
|
||
// Get recommendations
|
||
const result = await fetch('/api/recommend', {...});
|
||
const data = await result.json();
|
||
|
||
// Generate report URL
|
||
const reportUrl = `/api/recommend/report?data=${btoa(JSON.stringify(data))}&lang=ko`;
|
||
window.open(reportUrl); // Opens printable HTML
|
||
```
|
||
|
||
### Region-Based Recommendation Strategy (`recommend.ts`)
|
||
|
||
**When region IS specified** (e.g., `region_preference: ["seoul"]`):
|
||
- Returns 3 spec tiers (Budget/Balanced/Premium) within that region
|
||
- Example: Seoul 1 - Standard 4GB, Standard 8GB, Pro 16GB
|
||
|
||
**When NO region specified**:
|
||
- Returns same/similar spec from 3 DIFFERENT regions for location comparison
|
||
- Example: Standard 4GB from Osaka 2, Seoul 1, Singapore 1
|
||
- Implemented by sending only 1 server per region to AI (forces diversity)
|
||
|
||
### AI Prompt Strategy (`recommend.ts`)
|
||
|
||
- Uses OpenAI GPT-4o-mini via Cloudflare AI Gateway (bypasses regional restrictions)
|
||
- Server list format: `[server_id=XXXX] Provider Name...` for accurate ID extraction
|
||
- **server_id uses `ap.id`** (pricing ID, unique per instance+region combination)
|
||
- Scoring: Cost efficiency (40%) + Capacity fit (30%) + Scalability (30%)
|
||
- Capacity response in Korean for Korean users
|
||
- **Prompt injection protection**: User inputs sanitized via `sanitizeForAIPrompt()`
|
||
- **Token optimization**: Candidates limited to top 15 by price
|
||
|
||
### Security Features
|
||
|
||
- **XSS prevention**: `escapeHtml()` applied to all user data in HTML reports
|
||
- **Input validation**: Comprehensive checks with length limits (tech_stack ≤20, use_case ≤500, region_preference ≤10 items)
|
||
- **Cache integrity**: Validates cached JSON structure before returning
|
||
- **Rate limiting**: 60 req/min per IP with in-memory fallback when KV unavailable
|
||
- **SQL injection prevention**: All queries use parameterized statements via Repository pattern
|
||
- **Security headers**: CSP, HSTS, X-Frame-Options, X-Content-Type-Options
|
||
- **API key protection**: Keys never logged, sanitized from error messages
|
||
- **Prompt injection protection**: `sanitizeForAIPrompt()` filters malicious patterns
|
||
- **Type safety**: No `any` types - uses `unknown` + type guards
|
||
|
||
### AI Fallback System (`services/ai-service.ts`)
|
||
|
||
When OpenAI API is unavailable, the system automatically falls back to rule-based recommendations:
|
||
- Sorts candidates by price
|
||
- Selects 3 tiers: Budget (cheapest), Balanced (median), Premium (top 25%)
|
||
- Provides basic capacity estimates based on vCPU count
|
||
- Warns user that AI is temporarily unavailable
|
||
|
||
### Configuration (`config.ts`)
|
||
|
||
Centralized limits and constants:
|
||
```typescript
|
||
LIMITS = {
|
||
MAX_REQUEST_BODY_BYTES: 10240, // 10KB
|
||
CACHE_TTL_SECONDS: 300, // 5 minutes
|
||
RATE_LIMIT_MAX_REQUESTS: 60, // per minute
|
||
MAX_AI_CANDIDATES: 15, // reduce API cost
|
||
MAX_TECH_STACK: 20,
|
||
MAX_USE_CASE_LENGTH: 500,
|
||
}
|
||
```
|
||
|
||
## Bindings (wrangler.toml)
|
||
|
||
```toml
|
||
[[kv_namespaces]]
|
||
binding = "CACHE"
|
||
id = "c68cdb477022424cbe4594f491390c8a"
|
||
|
||
[[d1_databases]]
|
||
binding = "DB"
|
||
database_name = "cloud-instances-db"
|
||
database_id = "bbcb472d-b25e-4e48-b6ea-112f9fffb4a8"
|
||
|
||
[vars]
|
||
OPENAI_API_KEY = "sk-..." # Set via wrangler secret
|
||
```
|
||
|
||
## Testing
|
||
|
||
**Note**: Use single-line curl commands. Backslash line continuation (`\`) may not work in some environments.
|
||
|
||
```bash
|
||
# Health check
|
||
curl -s https://cloud-orchestrator.kappa-d8e.workers.dev/api/health | jq .
|
||
|
||
# Recommendation - nodejs/redis real-time chat (Japan)
|
||
curl -s -X POST https://cloud-orchestrator.kappa-d8e.workers.dev/api/recommend -H "Content-Type: application/json" -d '{"tech_stack":["nodejs","redis"],"expected_users":1000,"use_case":"real-time chat","region_preference":["japan"]}' | jq .
|
||
|
||
# Recommendation - php/mysql community forum (Korea)
|
||
curl -s -X POST https://cloud-orchestrator.kappa-d8e.workers.dev/api/recommend -H "Content-Type: application/json" -d '{"tech_stack":["php","mysql"],"expected_users":800,"use_case":"community forum","region_preference":["korea"]}' | jq .
|
||
|
||
# Recommendation - analytics dashboard (heavier DB workload)
|
||
curl -s -X POST https://cloud-orchestrator.kappa-d8e.workers.dev/api/recommend -H "Content-Type: application/json" -d '{"tech_stack":["postgresql"],"expected_users":500,"use_case":"analytics dashboard","region_preference":["japan"]}' | jq .
|
||
|
||
# Server list with filters (supports flexible region: korea, seoul, tokyo, etc.)
|
||
curl -s "https://cloud-orchestrator.kappa-d8e.workers.dev/api/servers?region=korea&minCpu=4" | jq .
|
||
|
||
# CDN cache hit rate - default (use_case별 자동 적용)
|
||
curl -s -X POST https://cloud-orchestrator.kappa-d8e.workers.dev/api/recommend -H "Content-Type: application/json" -d '{"tech_stack":["nginx"],"expected_users":10000,"use_case":"video streaming","region_preference":["japan"]}' | jq '.bandwidth_estimate | {cdn_enabled, cdn_cache_hit_rate, gross_monthly_tb, origin_monthly_tb}'
|
||
|
||
# CDN disabled (원본 서버 전체 트래픽)
|
||
curl -s -X POST https://cloud-orchestrator.kappa-d8e.workers.dev/api/recommend -H "Content-Type: application/json" -d '{"tech_stack":["nginx"],"expected_users":10000,"use_case":"video streaming","region_preference":["japan"],"cdn_enabled":false}' | jq '.bandwidth_estimate'
|
||
|
||
# Custom CDN cache hit rate (80%)
|
||
curl -s -X POST https://cloud-orchestrator.kappa-d8e.workers.dev/api/recommend -H "Content-Type: application/json" -d '{"tech_stack":["nginx"],"expected_users":10000,"use_case":"video streaming","region_preference":["japan"],"cdn_cache_hit_rate":0.80}' | jq '.recommendations[0].bandwidth_info | {cdn_cache_hit_rate, cdn_savings_tb, cdn_savings_cost}'
|
||
|
||
# HTML Report (encode recommendation result as base64)
|
||
# 1. Get recommendation and save to variable
|
||
RESULT=$(curl -s -X POST https://cloud-orchestrator.kappa-d8e.workers.dev/api/recommend -H "Content-Type: application/json" -d '{"tech_stack":["nodejs"],"expected_users":500,"use_case":"simple api","lang":"ko"}')
|
||
# 2. Generate report URL with base64-encoded data
|
||
REPORT_URL="https://cloud-orchestrator.kappa-d8e.workers.dev/api/recommend/report?data=$(echo $RESULT | base64 | tr -d '\n')&lang=ko"
|
||
# 3. Open in browser or fetch
|
||
echo $REPORT_URL
|
||
```
|
||
|
||
## Recent Changes
|
||
|
||
### CDN Cache Hit Rate (Latest)
|
||
|
||
**New Feature**: CDN 캐시 히트율 기반 원본 서버 트래픽 및 비용 계산
|
||
|
||
**API Parameters Added**:
|
||
- `cdn_enabled`: CDN 사용 여부 (기본: true)
|
||
- `cdn_cache_hit_rate`: 캐시 히트율 override (0.0-1.0)
|
||
|
||
**Use Case별 기본 캐시 히트율**:
|
||
- video/streaming: 92%, blog/static: 90%, file: 85%
|
||
- ecommerce: 70%, forum: 60%, api: 30%, gaming: 20%, chat: 10%
|
||
|
||
**Response Fields Added**:
|
||
- `bandwidth_estimate`: cdn_enabled, cdn_cache_hit_rate, gross_monthly_tb, origin_monthly_tb
|
||
- `bandwidth_info`: cdn_savings_tb, cdn_savings_cost
|
||
|
||
**Cost Impact Example** (10K concurrent video streaming):
|
||
- Without CDN: $18,370 overage
|
||
- With CDN 92%: $1,464 overage (92% savings)
|
||
|
||
**Tests Added**: 4 new CDN-specific tests (total 59 tests)
|
||
|
||
### Major Architecture Refactoring
|
||
|
||
**Security Hardening**:
|
||
- Fixed XSS vulnerability in HTML reports with `escapeHtml()`
|
||
- Added cache data integrity validation
|
||
- Added `region_preference` input validation (max 10 items, 50 chars each)
|
||
- Replaced all `any` types with `unknown` + type guards
|
||
|
||
**Architecture Improvements**:
|
||
- Split `utils.ts` (801 lines) → 6 modular files in `utils/`
|
||
- Extracted AI logic to `services/ai-service.ts` (recommend.ts -49%)
|
||
- Added Repository pattern: `repositories/AnvilServerRepository.ts`
|
||
- Reduced DB query duplication across handlers
|
||
|
||
**New Features**:
|
||
- AI fallback: Rule-based recommendations when OpenAI unavailable
|
||
- Vitest testing: 55 tests covering validation, security, bandwidth
|
||
- Duplicate server prevention in AI recommendations
|
||
|
||
**Files Added**:
|
||
- `src/utils/{index,http,validation,bandwidth,cache,ai,exchange-rate}.ts`
|
||
- `src/services/ai-service.ts`
|
||
- `src/repositories/AnvilServerRepository.ts`
|
||
- `src/__tests__/{utils,bandwidth}.test.ts`
|
||
- `vitest.config.ts`
|
||
|
||
### Region Diversity & Bug Fixes
|
||
- **Region diversity**: No region specified → same spec from 3 different regions for comparison
|
||
- **Cache key fix**: `region_preference` now included in cache key
|
||
- **Server ID fix**: Changed from `ai.id` (instance) to `ap.id` (pricing) for unique region+instance identification
|
||
- **Prompt cleanup**: Removed obsolete Linode/Vultr/DigitalOcean references (Anvil only)
|
||
|
||
### Transfer Pricing & Reporting
|
||
- **Transfer pricing**: Added `anvil_transfer_pricing` table data to recommendations
|
||
- **bandwidth_info**: Each recommendation includes transfer costs (included_tb, overage costs)
|
||
- **available_regions**: Lists other regions where same server spec is available with prices
|
||
- **HTML report**: New `/api/recommend/report` endpoint for printable reports
|
||
- **KRW conversion**: Bandwidth costs converted to KRW for Korean users (GB: 1원, TB/total: 100원 rounding)
|
||
|
||
### Anvil Pricing Migration
|
||
- **New tables**: Migrated from `pricing` to `anvil_pricing` tables
|
||
- **Provider**: Now uses "Anvil" as single provider (previously Linode/Vultr)
|
||
- **Exchange rate**: Real-time USD→KRW conversion via open.er-api.com
|
||
- **Removed**: `provider_filter` parameter no longer supported
|
||
- **Currency handling**: Korean users see KRW, others see USD
|
||
|
||
### Architecture
|
||
- **Modular architecture**: Split from single 2370-line file into organized modules
|
||
- **Centralized config**: All magic numbers moved to `LIMITS` in config.ts
|
||
- **Type safety**: `parseAIResponse(unknown)` with proper type guards
|
||
|
||
### Features
|
||
- **DB workload multiplier**: Database resource calculation based on use_case
|
||
- **Bandwidth estimation**: Automatic bandwidth category detection for provider filtering
|
||
- **Tech specs update**: Realistic vcpu_per_users values for 150+ technologies
|
||
|
||
### Security
|
||
- **Prompt injection protection**: `sanitizeForAIPrompt()` filters malicious patterns
|
||
- **Rate limiting fallback**: In-memory Map when KV unavailable
|
||
- **Input sanitization**: All user inputs validated and length-limited
|
||
|
||
### Performance
|
||
- **O(1) VPS lookup**: Map-based benchmark matching (was O(n×m))
|
||
- **AI token optimization**: Candidates limited to 15 (was 50)
|
||
- **KV caching**: 5-minute TTL, empty results not cached
|
||
- **Parallel queries**: Promise.all for independent DB operations
|
||
|
||
### Code Quality
|
||
- **Dead code removed**: Unused `queryVPSBenchmarks` function deleted
|
||
- **Flexible region matching**: Both endpoints support country/city/code inputs (korea, seoul, icn)
|