6 분 소요


엣지 컴퓨팅이란?

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

장점

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

댓글남기기