엣지 컴퓨팅 플랫폼 비교 분석 2025
Cloudflare Workers vs AWS Lambda@Edge vs Fastly Compute@Edge - 엣지 컴퓨팅 선택 가이드
🌐 엣지 컴퓨팅 플랫폼 비교 분석 2025
글로벌 저지연 애플리케이션을 위한 엣지 컴퓨팅 플랫폼 선택 가이드
📊 개요
비교 대상
- Cloudflare Workers: 가장 큰 엣지 네트워크
- AWS Lambda@Edge: AWS 통합 엣지 컴퓨팅
- Fastly Compute@Edge: 고성능 엣지 플랫폼
- Vercel Edge Functions: Next.js 최적화
- Deno Deploy: 모던 JavaScript 런타임
- Netlify Edge Functions: JAMstack 친화적
평가 기준
- 글로벌 커버리지
- 성능 (콜드 스타트, 응답 시간)
- 지원 언어/런타임
- 가격
- 기능 제한
- 개발자 경험
📈 상세 비교표
핵심 특성 비교
| 특성 | Cloudflare Workers | Lambda@Edge | Fastly Compute | Vercel Edge | |------|-------------------|-------------|----------------|-------------| | PoP 수 | 280+ | 400+ | 60+ | 전 세계 | | 콜드 스타트 | <10ms | 100-300ms | <50ms | <50ms | | 최대 실행 시간 | 30초 | 30초 | 60초 | 25초 | | 메모리 | 128MB | 128MB | 128MB | 128MB | | CPU 시간 | 10ms-50ms | 제한 없음 | 제한 없음 | 제한 있음 | | 언어 지원 | JS/Wasm | JS/Python | Rust/JS/Go | JS/TS |
기술적 제한사항
| 항목 | Cloudflare | Lambda@Edge | Fastly | Vercel | |------|------------|-------------|---------|--------| | 번들 크기 | 1MB | 1MB | 100MB | 1MB | | 환경 변수 | 1MB | 4KB | 제한 없음 | 64KB | | 요청 크기 | 100MB | 40KB | 8MB | 4.5MB | | 응답 크기 | 무제한 | 1MB | 무제한 | 4.5MB | | WebSocket | ✅ | ❌ | ✅ | 제한적 | | 스토리지 | KV/R2/D1 | ❌ | KV Store | KV |
가격 비교 (월 기준)
| 사용량 | Cloudflare | Lambda@Edge | Fastly | Vercel | |--------|------------|-------------|---------|--------| | 10M 요청 | $5 | $6 | $50 | $20 | | 100M 요청 | $50 | $60 | $500 | $200 | | 1B 요청 | $500 | $600 | $5,000 | $2,000 | | 무료 티어 | 100K/일 | 제한적 | 없음 | 제한적 |
💼 사용 사례별 구현
🌍 글로벌 A/B 테스팅
추천: Cloudflare Workers
// Cloudflare Workers - A/B 테스팅 구현 export default { async fetch(request, env, ctx) { const url = new URL(request.url); // 사용자 정보 추출 const country = request.cf?.country || 'US'; const city = request.cf?.city || 'Unknown'; const colo = request.cf?.colo || 'Unknown'; // 사용자 ID 또는 세션 기반 일관된 할당 const userId = request.headers.get('x-user-id') || await generateUserId(request); // A/B 그룹 결정 (일관성 보장) const abGroup = await getABGroup(userId, env.AB_TEST); // KV에서 테스트 설정 로드 const testConfig = await env.CONFIG_KV.get('ab-test-config', 'json'); const variant = testConfig.variants[abGroup]; // 요청 라우팅 let response; if (variant.type === 'redirect') { // 다른 오리진으로 리다이렉트 response = await fetch(variant.origin + url.pathname, request); } else if (variant.type === 'rewrite') { // HTML 수정 response = await fetch(request); response = await modifyHTML(response, variant.modifications); } else { // 프록시 response = await fetch(request); } // 메트릭 수집 (비동기) ctx.waitUntil( collectMetrics({ testId: testConfig.id, variant: abGroup, userId, country, city, timestamp: Date.now(), responseTime: Date.now() - startTime }, env.ANALYTICS) ); // 응답에 헤더 추가 response = new Response(response.body, response); response.headers.set('x-ab-variant', abGroup); response.headers.set('x-edge-location', colo); return response; } }; // HTML 수정 함수 async function modifyHTML(response, modifications) { const rewriter = new HTMLRewriter(); modifications.forEach(mod => { rewriter.on(mod.selector, { element(element) { if (mod.action === 'replace') { element.setInnerContent(mod.content); } else if (mod.action === 'setAttribute') { element.setAttribute(mod.attribute, mod.value); } else if (mod.action === 'addClass') { element.setAttribute('class', element.getAttribute('class') + ' ' + mod.className ); } } }); }); return rewriter.transform(response); } // 메트릭 수집 async function collectMetrics(data, analytics) { // Analytics Engine으로 전송 await analytics.writeDataPoint({ indexes: [data.testId, data.variant], blobs: [data.country, data.city], doubles: [data.responseTime], timestamp: data.timestamp }); // 실시간 집계를 위한 Durable Object 업데이트 const id = analytics.idFromName(`test-${data.testId}`); const stub = analytics.get(id); await stub.fetch('https://internal/increment', { method: 'POST', body: JSON.stringify(data) }); } // Durable Object for 실시간 집계 export class ABTestAnalytics { constructor(state, env) { this.state = state; this.env = env; } async fetch(request) { const url = new URL(request.url); if (url.pathname === '/increment') { const data = await request.json(); await this.increment(data); return new Response('ok'); } if (url.pathname === '/stats') { const stats = await this.getStats(); return new Response(JSON.stringify(stats)); } } async increment(data) { const key = `${data.variant}:${data.country}`; const current = await this.state.storage.get(key) || { impressions: 0, totalTime: 0 }; current.impressions++; current.totalTime += data.responseTime; await this.state.storage.put(key, current); } } 🔒 보안 게이트웨이
추천: AWS Lambda@Edge
// Lambda@Edge - 보안 검증 및 필터링 exports.handler = async (event, context) => { const request = event.Records[0].cf.request; const headers = request.headers; // 1. IP 기반 지역 차단 const clientIp = headers['cloudfront-viewer-address'][0].value; const country = headers['cloudfront-viewer-country'][0].value; if (await isBlockedCountry(country)) { return { status: '403', statusDescription: 'Forbidden', body: 'Access denied from your region' }; } // 2. DDoS 방어 - Rate Limiting const rateLimitKey = `ratelimit:${clientIp}`; const requests = await incrementAndGetCount(rateLimitKey); if (requests > 100) { // 분당 100 요청 제한 return { status: '429', statusDescription: 'Too Many Requests', headers: { 'retry-after': [{ key: 'Retry-After', value: '60' }] } }; } // 3. WAF 규칙 실행 const wafResult = await runWAFRules(request); if (wafResult.blocked) { await logSecurityEvent({ type: 'waf_block', rule: wafResult.rule, ip: clientIp, uri: request.uri, headers: headers }); return { status: '403', statusDescription: 'Forbidden', body: 'Request blocked by security rules' }; } // 4. JWT 토큰 검증 (API 보호) if (request.uri.startsWith('/api/')) { const authHeader = headers.authorization?.[0]?.value; if (!authHeader || !authHeader.startsWith('Bearer ')) { return { status: '401', statusDescription: 'Unauthorized', headers: { 'www-authenticate': [{ key: 'WWW-Authenticate', value: 'Bearer realm="api"' }] } }; } const token = authHeader.substring(7); try { const payload = await verifyJWT(token); // 토큰 정보를 헤더에 추가하여 오리진에 전달 request.headers['x-user-id'] = [{ key: 'X-User-Id', value: payload.sub }]; request.headers['x-user-role'] = [{ key: 'X-User-Role', value: payload.role }]; } catch (error) { return { status: '401', statusDescription: 'Unauthorized', body: 'Invalid token' }; } } // 5. 요청 변환 및 보안 헤더 추가 request.headers['x-real-ip'] = [{ key: 'X-Real-IP', value: clientIp }]; request.headers['x-forwarded-country'] = [{ key: 'X-Forwarded-Country', value: country }]; // 민감한 헤더 제거 delete request.headers['x-amzn-trace-id']; delete request.headers['x-amz-cf-id']; return request; }; // WAF 규칙 실행 async function runWAFRules(request) { const rules = [ // SQL Injection 탐지 { name: 'sql_injection', pattern: /((union|select|insert|update|delete|drop|create)\s+.*\s+(from|into|where|table))|(-{2})|(\/\*|\*\/)/i, target: ['uri', 'querystring', 'body'] }, // XSS 탐지 { name: 'xss', pattern: /(<script[^>]*>|javascript:|onerror=|onload=|alert\(|document\.|window\.|eval\()/i, target: ['querystring', 'body'] }, // Path Traversal { name: 'path_traversal', pattern: /(\.\.\/|\.\.\\/i, target: ['uri', 'querystring'] } ]; for (const rule of rules) { for (const target of rule.target) { let value = ''; if (target === 'uri') { value = request.uri; } else if (target === 'querystring') { value = request.querystring; } else if (target === 'body' && request.body) { value = Buffer.from(request.body.data, 'base64').toString(); } if (rule.pattern.test(value)) { return { blocked: true, rule: rule.name }; } } } return { blocked: false }; } ⚡ 실시간 개인화
추천: Fastly Compute@Edge
// Fastly Compute@Edge - Rust로 구현한 실시간 개인화 use fastly::{Error, Request, Response}; use fastly::http::{header, Method, StatusCode}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; #[derive(Serialize, Deserialize)] struct UserProfile { id: String, preferences: HashMap<String, String>, history: Vec<String>, segment: String, } #[derive(Serialize, Deserialize)] struct PersonalizationRule { segment: String, modifications: Vec<Modification>, } #[derive(Serialize, Deserialize)] struct Modification { selector: String, action: String, value: String, } #[fastly::main] fn main(req: Request) -> Result<Response, Error> { // 사용자 식별 let user_id = get_user_id(&req); // 사용자 프로필 로드 (엣지 KV에서) let profile = match load_user_profile(&user_id) { Some(p) => p, None => create_default_profile(&user_id), }; // 개인화 규칙 로드 let rules = load_personalization_rules(); let applicable_rules: Vec<_> = rules.iter() .filter(|r| r.segment == profile.segment) .collect(); // 오리진 요청 let mut beresp = req.clone_without_body() .send("origin_0")?; // HTML 응답인 경우 개인화 적용 if is_html_response(&beresp) { let body = beresp.take_body_str(); let personalized_body = apply_personalizations( &body, &applicable_rules, &profile ); beresp.set_body(personalized_body); } // 추천 헤더 추가 if req.get_path().starts_with("/api/recommendations") { let recommendations = generate_recommendations(&profile); beresp.set_header( "X-Recommendations", serde_json::to_string(&recommendations)? ); } // 개인화 메트릭 수집 (비동기) log_personalization_event(PersonalizationEvent { user_id: user_id.clone(), segment: profile.segment.clone(), path: req.get_path().to_string(), timestamp: std::time::SystemTime::now(), applied_rules: applicable_rules.len(), }); Ok(beresp) } fn apply_personalizations( html: &str, rules: &[&PersonalizationRule], profile: &UserProfile ) -> String { let mut result = html.to_string(); for rule in rules { for modification in &rule.modifications { result = match modification.action.as_str() { "replace" => { // CSS 선택자 기반 교체 replace_by_selector( &result, &modification.selector, &modification.value ) }, "inject" => { // 동적 콘텐츠 주입 inject_content( &result, &modification.selector, &personalize_content(&modification.value, profile) ) }, "remove" => { // 요소 제거 remove_by_selector(&result, &modification.selector) }, _ => result, }; } } result } fn generate_recommendations(profile: &UserProfile) -> Vec<String> { // 사용자 히스토리 기반 추천 생성 let mut recommendations = Vec::new(); // 협업 필터링 로직 let similar_users = find_similar_users(&profile.id); for user in similar_users { let user_profile = load_user_profile(&user).unwrap(); for item in user_profile.history { if !profile.history.contains(&item) { recommendations.push(item); } } } // 콘텐츠 기반 필터링 for item in &profile.history { let similar_items = find_similar_items(item); recommendations.extend(similar_items); } // 중복 제거 및 상위 N개 반환 recommendations.dedup(); recommendations.truncate(10); recommendations } // WebAssembly 모듈 호출 (복잡한 ML 모델) #[link(wasm_import_module = "ml_model")] extern "C" { fn predict_user_segment(features: *const f32, len: usize) -> i32; } fn update_user_segment(profile: &mut UserProfile) { // 특징 추출 let features = extract_features(profile); // WASM ML 모델 호출 let segment_id = unsafe { predict_user_segment(features.as_ptr(), features.len()) }; profile.segment = match segment_id { 0 => "casual".to_string(), 1 => "power_user".to_string(), 2 => "enterprise".to_string(), _ => "default".to_string(), }; } 🎯 Next.js 앱 최적화
추천: Vercel Edge Functions
// Vercel Edge Functions - Next.js 미들웨어 import { NextRequest, NextResponse } from 'next/server'; import { geolocation, ipAddress } from '@vercel/edge'; export const config = { matcher: [ '/((?!api|_next/static|_next/image|favicon.ico).*)', ], }; export async function middleware(request: NextRequest) { // 지역 정보 추출 const { country, city, region } = geolocation(request); const ip = ipAddress(request); // 1. 지역별 리다이렉트 if (request.nextUrl.pathname === '/') { const locale = getLocaleByCountry(country); if (locale && locale !== 'en') { return NextResponse.redirect( new URL(`/${locale}`, request.url) ); } } // 2. 기능 플래그 (엣지에서 결정) const features = await getFeatureFlags({ userId: request.cookies.get('userId')?.value, country, ip, }); // 3. 실험 그룹 할당 const experiments = assignExperiments(request, features); // 4. 성능 최적화 - 이미지 포맷 협상 if (request.nextUrl.pathname.match(/\.(jpg|jpeg|png|gif)$/)) { const accept = request.headers.get('accept') || ''; if (accept.includes('image/avif')) { return NextResponse.rewrite( new URL( request.nextUrl.pathname.replace(/\.[^.]+$/, '.avif'), request.url ) ); } else if (accept.includes('image/webp')) { return NextResponse.rewrite( new URL( request.nextUrl.pathname.replace(/\.[^.]+$/, '.webp'), request.url ) ); } } // 5. 봇 감지 및 처리 const isBot = detectBot(request.headers.get('user-agent') || ''); if (isBot) { // 봇용 정적 버전 제공 return NextResponse.rewrite( new URL('/api/static-render', request.url) ); } // 6. 보안 헤더 추가 const response = NextResponse.next(); response.headers.set('X-Frame-Options', 'DENY'); response.headers.set('X-Content-Type-Options', 'nosniff'); response.headers.set('X-XSS-Protection', '1; mode=block'); response.headers.set( 'Content-Security-Policy', getCSPHeader(features) ); // 7. 쿠키 설정 (실험 및 기능 플래그) response.cookies.set('features', JSON.stringify(features), { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax', maxAge: 60 * 60 * 24 * 7, // 1주일 }); response.cookies.set('experiments', JSON.stringify(experiments), { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax', maxAge: 60 * 60 * 24 * 30, // 30일 }); // 8. 커스텀 헤더 추가 (디버깅용) response.headers.set('X-Edge-Location', city || 'unknown'); response.headers.set('X-Edge-Country', country || 'unknown'); response.headers.set('X-Edge-IP', ip || 'unknown'); return response; } // 기능 플래그 로드 (Edge Config) async function getFeatureFlags(context: { userId?: string; country?: string; ip?: string; }) { const flags = await fetch( `https://edge-config.vercel.com/flags`, { headers: { Authorization: `Bearer ${process.env.EDGE_CONFIG_TOKEN}`, }, } ).then(res => res.json()); // 조건부 플래그 평가 const evaluatedFlags: Record<string, boolean> = {}; for (const [key, config] of Object.entries(flags)) { if (typeof config === 'boolean') { evaluatedFlags[key] = config; } else if (typeof config === 'object') { // 복잡한 규칙 평가 evaluatedFlags[key] = evaluateRule(config, context); } } return evaluatedFlags; } // 실험 할당 function assignExperiments( request: NextRequest, features: Record<string, boolean> ): Record<string, string> { const experiments: Record<string, string> = {}; const userId = request.cookies.get('userId')?.value || generateUserId(); // 각 실험에 대해 일관된 할당 if (features.newCheckoutFlow) { experiments.checkoutVariant = hashToVariant( userId, ['control', 'variant-a', 'variant-b'], 'checkout-experiment' ); } if (features.personalizedHomepage) { experiments.homepageVariant = hashToVariant( userId, ['default', 'personalized', 'ai-powered'], 'homepage-experiment' ); } return experiments; } // 일관된 해시 기반 할당 function hashToVariant( userId: string, variants: string[], experimentName: string ): string { const hash = require('crypto') .createHash('md5') .update(userId + experimentName) .digest('hex'); const hashInt = parseInt(hash.substring(0, 8), 16); const index = hashInt % variants.length; return variants[index]; } 🏢 실제 기업 사례
Cloudflare Workers 사용
- Discord: API 라우팅 및 보안
- Shopify: 동적 콘텐츠 캐싱
- DoorDash: 지역별 라우팅
Lambda@Edge 사용
- Amazon Prime Video: 비디오 스트리밍 최적화
- Slack: 정적 자산 최적화
- 많은 미디어 기업: 콘텐츠 보호
Fastly Compute@Edge 사용
- The New York Times: 콘텐츠 전달
- Reddit: 트래픽 관리
- Stripe: API 가속
Vercel Edge Functions 사용
- TikTok: 웹 프론트엔드
- Hulu: 스트리밍 서비스
- 많은 Next.js 앱: SSR 최적화
🔧 개발 도구 및 테스팅
로컬 개발 환경
# Cloudflare Workers wrangler dev # Vercel Edge vercel dev # Fastly fastly compute serve # Deno Deploy deno task dev 테스팅 전략
| 플랫폼 | 단위 테스트 | 통합 테스트 | E2E 테스트 | |--------|------------|------------|----------| | Cloudflare | Vitest | Miniflare | Wrangler | | Lambda@Edge | Jest | SAM Local | AWS SDK | | Fastly | Rust tests | Local server | CLI | | Vercel | Jest | Edge Runtime | Playwright |
💰 비용 최적화 전략
비용 절감 팁
- 캐싱 극대화: 엣지에서 최대한 캐시
- 조건부 요청: 필요한 경우만 오리진 호출
- 압축: Brotli/Gzip 적극 활용
- 이미지 최적화: 실시간 리사이징
ROI 계산
// 엣지 컴퓨팅 ROI 계산기 function calculateROI({ monthlyRequests, avgLatencyReduction, // ms conversionImpact, // % avgOrderValue, currentConversionRate }) { // 지연시간 감소로 인한 수익 증가 const latencyImpact = (avgLatencyReduction / 100) * 0.01; // 100ms = 1% 전환율 const newConversionRate = currentConversionRate * (1 + latencyImpact); // 월간 추가 수익 const additionalRevenue = monthlyRequests * (newConversionRate - currentConversionRate) * avgOrderValue; // 엣지 컴퓨팅 비용 const edgeCost = calculateEdgeCost(monthlyRequests); return { additionalRevenue, edgeCost, netBenefit: additionalRevenue - edgeCost, roi: ((additionalRevenue - edgeCost) / edgeCost) * 100 }; } 🎯 선택 가이드
Cloudflare Workers 선택 시
✅ 최대 글로벌 커버리지
✅ 낮은 지연시간 중요
✅ KV/R2/D1 필요
✅ WebSocket 지원
❌ 복잡한 계산 작업
❌ 긴 실행 시간
Lambda@Edge 선택 시
✅ AWS 에코시스템
✅ CloudFront 사용 중
✅ 보안 기능 중요
✅ Python 지원 필요
❌ 빠른 콜드 스타트
❌ 실시간 양방향 통신
Fastly Compute@Edge 선택 시
✅ 고성능 필요
✅ Rust/Go 선호
✅ 대용량 처리
✅ 미디어 스트리밍
❌ 작은 규모
❌ 저렴한 비용
Vercel Edge Functions 선택 시
✅ Next.js 사용
✅ 프론트엔드 최적화
✅ 간단한 설정
✅ TypeScript 중심
❌ 복잡한 백엔드
❌ Non-JS 워크로드
📚 추가 리소스
공식 문서
성능 벤치마크
- Edge Computing Report 2024
- CDN Performance Benchmark
- Real User Monitoring (RUM) 데이터
커뮤니티
- Cloudflare Developers Discord
- AWS Edge Services Forum
- Vercel Community
- Edge Computing Subreddit
💡 핵심 조언: 엣지 컴퓨팅은 "사용자와의 거리"를 줄이는 기술입니다. 단순히 "빠르다"가 아니라 "어디서나 빠르다"가 중요합니다. 글로벌 서비스라면 Cloudflare Workers, AWS 중심이라면 Lambda@Edge, Next.js 앱이라면 Vercel Edge Functions를 선택하세요. 중요한 것은 사용자 경험 개선이지, 기술 자체가 아닙니다.