백엔드 개발자를 위한 엣지 컴퓨팅 패턴 Edge Computing Patterns for Backend Developers
엣지 컴퓨팅이란?
엣지 컴퓨팅은 데이터 처리를 사용자에게 가까운 위치에서 수행하는 패러다임입니다.
장점
- 낮은 지연시간: 물리적 거리 최소화
- 대역폭 절약: 원본 서버 트래픽 감소
- 높은 가용성: 분산 처리로 장애 격리
- 글로벌 확장성: 전 세계 PoP 활용
[기존]
사용자(서울) ─────────────────▶ 서버(미국) ─▶ 응답
200ms
[엣지]
사용자(서울) ───▶ 엣지(서울) ───▶ 서버(미국)
10ms (필요 시)
CDN 기본 활용
정적 리소스 캐싱
# Cloudflare Page Rules
- match: "*.example.com/static/*"
cache_level: cache_everything
edge_cache_ttl: 86400 # 24시간
- match: "api.example.com/v1/products/*"
cache_level: cache_everything
edge_cache_ttl: 300 # 5분
cache_by_cookie: false
Spring Boot에서 Cache-Control 설정
@Configuration
class WebConfig : WebMvcConfigurer {
override fun addResourceHandlers(registry: ResourceHandlerRegistry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/")
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS).cachePublic())
}
}
@RestController
class ProductController(
private val productService: ProductService
) {
@GetMapping("/api/products/{id}")
fun getProduct(@PathVariable id: String): ResponseEntity<Product> {
val product = productService.getProduct(id)
return ResponseEntity.ok()
.cacheControl(CacheControl.maxAge(5, TimeUnit.MINUTES).cachePublic())
.eTag(product.version.toString())
.body(product)
}
}
Surrogate Keys를 통한 정밀 캐시 무효화
@RestController
class ProductController(
private val productService: ProductService
) {
@GetMapping("/api/categories/{categoryId}/products")
fun getProductsByCategory(
@PathVariable categoryId: String
): ResponseEntity<List<Product>> {
val products = productService.getByCategory(categoryId)
return ResponseEntity.ok()
.header("Surrogate-Key", "category-$categoryId products")
.cacheControl(CacheControl.maxAge(1, TimeUnit.HOURS))
.body(products)
}
}
// 캐시 무효화
@Service
class CacheInvalidationService(
private val webClient: WebClient
) {
fun invalidateCategory(categoryId: String) {
webClient.post()
.uri("https://api.fastly.com/service/{serviceId}/purge/category-$categoryId")
.header("Fastly-Key", fastlyApiKey)
.retrieve()
.toBodilessEntity()
.block()
}
}
Edge Functions
Cloudflare Workers
// workers/api-router.js
export default {
async fetch(request, env) {
const url = new URL(request.url);
// 지역별 라우팅
const country = request.cf?.country || 'US';
const regionOrigin = getRegionOrigin(country);
// A/B 테스트
const variant = getVariant(request);
// 캐시 확인
const cacheKey = new Request(url.toString(), request);
const cache = caches.default;
let response = await cache.match(cacheKey);
if (!response) {
// 원본 요청
response = await fetch(regionOrigin + url.pathname, {
headers: {
...request.headers,
'X-Variant': variant,
'X-Country': country
}
});
// 캐시 저장
if (response.ok) {
const cached = response.clone();
cached.headers.set('Cache-Control', 'public, max-age=60');
await cache.put(cacheKey, cached);
}
}
return response;
}
};
function getRegionOrigin(country) {
const regions = {
'KR': 'https://api-ap.example.com',
'JP': 'https://api-ap.example.com',
'US': 'https://api-us.example.com',
'DE': 'https://api-eu.example.com',
};
return regions[country] || regions['US'];
}
function getVariant(request) {
const cookie = request.headers.get('Cookie');
// 기존 variant 확인 또는 새로 할당
return 'A'; // 또는 'B'
}
AWS Lambda@Edge
// Lambda@Edge - Viewer Request
class ViewerRequestHandler : RequestHandler<CloudFrontEvent, CloudFrontResponse> {
override fun handleRequest(event: CloudFrontEvent, context: Context): CloudFrontResponse {
val request = event.records[0].cf.request
// 인증 토큰 검증
val authHeader = request.headers["authorization"]?.get(0)?.value
if (authHeader == null || !validateToken(authHeader)) {
return CloudFrontResponse().apply {
status = "401"
statusDescription = "Unauthorized"
body = "Invalid token"
}
}
// 요청 변환
request.headers["x-user-id"] = listOf(
Header("x-user-id", extractUserId(authHeader))
)
return request
}
}
지역별 데이터 라우팅
Spring Boot 멀티 리전 설정
@Configuration
class RegionalRoutingConfig {
@Bean
fun regionalDataSource(
@Value("\${region}") region: String
): DataSource {
val config = when (region) {
"ap-northeast-2" -> DataSourceConfig(
primary = "jdbc:postgresql://db-ap-northeast-2.example.com:5432/mydb",
replica = "jdbc:postgresql://db-ap-replica.example.com:5432/mydb"
)
"us-east-1" -> DataSourceConfig(
primary = "jdbc:postgresql://db-us-east-1.example.com:5432/mydb",
replica = "jdbc:postgresql://db-us-replica.example.com:5432/mydb"
)
else -> throw IllegalArgumentException("Unknown region: $region")
}
return createRoutingDataSource(config)
}
private fun createRoutingDataSource(config: DataSourceConfig): AbstractRoutingDataSource {
return object : AbstractRoutingDataSource() {
override fun determineCurrentLookupKey(): Any {
return if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
"replica"
} else {
"primary"
}
}
}.apply {
setTargetDataSources(mapOf(
"primary" to createHikariDataSource(config.primary),
"replica" to createHikariDataSource(config.replica)
))
setDefaultTargetDataSource(createHikariDataSource(config.primary))
}
}
}
지역 인식 서비스
@Service
class RegionalService(
private val regionalApiClients: Map<String, WebClient>
) {
fun getDataFromNearestRegion(userId: String, userRegion: String): Data {
val client = regionalApiClients[userRegion]
?: regionalApiClients["default"]!!
return client.get()
.uri("/api/data/{userId}", userId)
.retrieve()
.bodyToMono(Data::class.java)
.block()!!
}
}
@Configuration
class RegionalClientConfig {
@Bean
fun regionalApiClients(): Map<String, WebClient> {
return mapOf(
"ap-northeast-2" to WebClient.create("https://api-ap.example.com"),
"us-east-1" to WebClient.create("https://api-us.example.com"),
"eu-west-1" to WebClient.create("https://api-eu.example.com"),
"default" to WebClient.create("https://api-us.example.com")
)
}
}
하이브리드 아키텍처
엣지 + 오리진 조합
┌────────────────────────────────────────────────────┐
│ 엣지 레이어 │
│ ┌─────────────┐ ┌─────────────┐ ┌───────────┐ │
│ │ Auth 검증 │ │ Rate Limit │ │ Cache │ │
│ │ Token 파싱 │ │ Bot 탐지 │ │ Static │ │
│ └─────────────┘ └─────────────┘ └───────────┘ │
└───────────────────────┬────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────┐
│ 오리진 레이어 │
│ ┌─────────────┐ ┌─────────────┐ ┌───────────┐ │
│ │ Business │ │ Database │ │ External │ │
│ │ Logic │ │ Operations │ │ APIs │ │
│ └─────────────┘ └─────────────┘ └───────────┘ │
└────────────────────────────────────────────────────┘
엣지에서 처리할 작업
// Cloudflare Worker - 하이브리드 처리
export default {
async fetch(request, env) {
const url = new URL(request.url);
// 1. 정적 리소스 - 엣지에서 완전 처리
if (url.pathname.startsWith('/static/')) {
return handleStaticResource(request, env);
}
// 2. 인증 - 엣지에서 검증
const authResult = await validateAuth(request, env);
if (!authResult.valid) {
return new Response('Unauthorized', { status: 401 });
}
// 3. Rate Limiting - 엣지에서 처리
const rateLimitResult = await checkRateLimit(request, env);
if (rateLimitResult.exceeded) {
return new Response('Too Many Requests', {
status: 429,
headers: { 'Retry-After': rateLimitResult.retryAfter }
});
}
// 4. 캐시 가능한 API - 엣지 캐시 확인
if (isCacheable(request)) {
const cached = await getCachedResponse(request, env);
if (cached) return cached;
}
// 5. 오리진으로 전달
const response = await fetch(env.ORIGIN_URL + url.pathname, {
method: request.method,
headers: {
...request.headers,
'X-User-Id': authResult.userId,
'X-Request-Region': request.cf.colo
},
body: request.body
});
// 6. 응답 캐싱
if (isCacheable(request) && response.ok) {
await cacheResponse(request, response.clone(), env);
}
return response;
}
};
엣지 데이터베이스
Cloudflare D1 (SQLite at Edge)
// D1 사용 예시
export default {
async fetch(request, env) {
const url = new URL(request.url);
if (url.pathname === '/api/products') {
const { results } = await env.DB.prepare(
'SELECT * FROM products WHERE category = ? LIMIT 10'
)
.bind(url.searchParams.get('category'))
.all();
return Response.json(results);
}
// 오리진으로 폴백
return fetch(request);
}
};
Cloudflare KV (Key-Value at Edge)
// KV를 활용한 세션 관리
export default {
async fetch(request, env) {
const sessionId = getCookie(request, 'session_id');
if (!sessionId) {
return new Response('No session', { status: 401 });
}
// 엣지에서 세션 조회 (< 1ms)
const session = await env.SESSIONS.get(sessionId, 'json');
if (!session) {
return new Response('Invalid session', { status: 401 });
}
// 세션 정보를 헤더에 추가하여 오리진 전달
const modifiedRequest = new Request(request);
modifiedRequest.headers.set('X-User-Id', session.userId);
modifiedRequest.headers.set('X-User-Role', session.role);
return fetch(modifiedRequest);
}
};
모니터링 및 분석
엣지 로그 수집
// Spring Boot에서 엣지 정보 수집
@RestController
class ApiController {
@GetMapping("/api/data")
fun getData(
@RequestHeader("CF-Ray") cfRay: String?,
@RequestHeader("CF-IPCountry") country: String?,
@RequestHeader("X-Request-Region") region: String?
): ResponseEntity<Data> {
// 메트릭 기록
Metrics.counter("api.requests",
"country", country ?: "unknown",
"edge_region", region ?: "unknown"
).increment()
// 로그
logger.info("Request from country=$country, edge=$region, ray=$cfRay")
return ResponseEntity.ok(data)
}
}
엣지 메트릭 대시보드
# 지역별 요청 분포
sum by (country) (rate(api_requests_total[5m]))
# 엣지 캐시 히트율
sum(rate(edge_cache_hits_total[5m])) /
sum(rate(edge_requests_total[5m])) * 100
# 지역별 응답 시간
histogram_quantile(0.95,
sum by (le, region) (
rate(http_request_duration_seconds_bucket[5m])
)
)
정리
엣지 컴퓨팅 활용 체크리스트:
| 작업 | 처리 위치 | 기술 | |——|———-|——| | 정적 리소스 | 엣지 | CDN 캐싱 | | 인증/인가 | 엣지 | Edge Functions | | Rate Limiting | 엣지 | Edge Functions + KV | | 세션 관리 | 엣지 | Edge KV | | 비즈니스 로직 | 오리진 | Spring Boot | | 데이터베이스 | 오리진/엣지 | RDS/D1 | | API 캐싱 | 엣지 | CDN + Surrogate Keys |
What is Edge Computing?
Edge computing is a paradigm where data processing occurs close to the user.
Benefits
- Low Latency: Minimize physical distance
- Bandwidth Savings: Reduce origin server traffic
- High Availability: Fault isolation through distributed processing
- Global Scalability: Leverage worldwide PoPs
Hybrid Architecture
Edge + Origin Combination
┌────────────────────────────────────────────────────┐
│ Edge Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌───────────┐ │
│ │ Auth Check │ │ Rate Limit │ │ Cache │ │
│ │ Token Parse │ │ Bot Detect │ │ Static │ │
│ └─────────────┘ └─────────────┘ └───────────┘ │
└───────────────────────┬────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────┐
│ Origin Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌───────────┐ │
│ │ Business │ │ Database │ │ External │ │
│ │ Logic │ │ Operations │ │ APIs │ │
│ └─────────────┘ └─────────────┘ └───────────┘ │
└────────────────────────────────────────────────────┘
Summary
Edge computing checklist:
| Task | Location | Technology | |——|———-|————| | Static Resources | Edge | CDN Caching | | Auth/Authz | Edge | Edge Functions | | Rate Limiting | Edge | Edge Functions + KV | | Session Management | Edge | Edge KV | | Business Logic | Origin | Spring Boot | | Database | Origin/Edge | RDS/D1 | | API Caching | Edge | CDN + Surrogate Keys |
댓글남기기