본문으로 건너뛰기

백엔드 개발자를 위한 엣지 컴퓨팅 패턴

엣지 컴퓨팅이란?

What is Edge Computing?

엣지 컴퓨팅은 데이터 처리를 사용자에게 가까운 위치에서 수행하는 패러다임입니다.

Edge computing is a paradigm that performs data processing at locations close to the user.

장점

  • 낮은 지연시간: 물리적 거리 최소화
  • 대역폭 절약: 원본 서버 트래픽 감소
  • 높은 가용성: 분산 처리로 장애 격리
  • 글로벌 확장성: 전 세계 PoP 활용

Advantages

  • Low Latency: Minimized physical distance
  • Bandwidth Savings: Reduced origin server traffic
  • High Availability: Fault isolation through distributed processing
  • Global Scalability: Leveraging worldwide PoPs
[기존/Traditional]
사용자(서울) ─────────────────▶ 서버(미국) ─▶ 응답
User(Seoul) Server(US) Response
200ms

[엣지/Edge]
사용자(서울) ───▶ 엣지(서울) ───▶ 서버(미국)
User(Seoul) Edge(Seoul) Server(US)
10ms (필요 시/if needed)

CDN 기본 활용

정적 리소스 캐싱

Basic CDN Usage

Static Resource Caching

# 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 설정

Cache-Control Configuration in Spring Boot

@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를 통한 정밀 캐시 무효화

Precise Cache Invalidation with 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

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

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 멀티 리전 설정

Regional Data Routing

Spring Boot Multi-Region Configuration

@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))
}
}
}

지역 인식 서비스

Region-Aware Service

@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")
)
}
}

하이브리드 아키텍처

엣지 + 오리진 조합

Hybrid Architecture

Edge + Origin Combination

┌────────────────────────────────────────────────────┐
│ 엣지 레이어 / Edge Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌───────────┐ │
│ │ Auth 검증 │ │ Rate Limit │ │ Cache │ │
│ │ Token 파싱 │ │ Bot 탐지 │ │ Static │ │
│ │ Auth Valid. │ │ Bot Detect │ │ │ │
│ └─────────────┘ └─────────────┘ └───────────┘ │
└───────────────────────┬────────────────────────────┘


┌────────────────────────────────────────────────────┐
│ 오리진 레이어 / Origin Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌───────────┐ │
│ │ Business │ │ Database │ │ External │ │
│ │ Logic │ │ Operations │ │ APIs │ │
│ └─────────────┘ └─────────────┘ └───────────┘ │
└────────────────────────────────────────────────────┘

엣지에서 처리할 작업

Tasks to Process at the Edge

// 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)

Edge Databases

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)

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);
}
};

모니터링 및 분석

엣지 로그 수집

Monitoring and Analytics

Edge Log Collection

// 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)
}
}

엣지 메트릭 대시보드

Edge Metrics Dashboard

# 지역별 요청 분포
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

Summary

Edge computing usage checklist:

TaskProcessing LocationTechnology
Static ResourcesEdgeCDN Caching
Authentication/AuthorizationEdgeEdge Functions
Rate LimitingEdgeEdge Functions + KV
Session ManagementEdgeEdge KV
Business LogicOriginSpring Boot
DatabaseOrigin/EdgeRDS/D1
API CachingEdgeCDN + Surrogate Keys