☁️ 클라우드

Cloudflare Workers

Cloudflare Workers (Edge Serverless Platform)

Cloudflare의 엣지 컴퓨팅 플랫폼. V8 isolates로 빠른 시작. 전 세계 300+ 데이터센터에서 실행.

📖 상세 설명

Cloudflare Workers는 Cloudflare의 전 세계 300개 이상의 데이터센터에서 JavaScript/TypeScript 코드를 실행하는 서버리스 엣지 컴퓨팅 플랫폼입니다. 기존 Lambda나 Cloud Functions와 달리 사용자와 가장 가까운 엣지 서버에서 코드가 실행됩니다.

Workers는 Chrome V8 엔진의 isolates 기술을 사용합니다. 컨테이너나 VM 대신 경량화된 isolates를 사용하여 콜드 스타트 없이 0ms 수준의 시작 시간을 제공합니다. 단일 프로세스 내에서 수천 개의 isolates가 동시에 실행될 수 있어 매우 효율적입니다.

주요 특징:

💻 코드 예제

// src/index.ts - 기본 Cloudflare Worker
export interface Env {
  API_KEY: string;
  CACHE_TTL: string;
}

export default {
  async fetch(
    request: Request,
    env: Env,
    ctx: ExecutionContext
  ): Promise<Response> {
    const url = new URL(request.url);

    // CORS 헤더 설정
    const corsHeaders = {
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type',
    };

    // Preflight 요청 처리
    if (request.method === 'OPTIONS') {
      return new Response(null, { headers: corsHeaders });
    }

    // 라우팅
    if (url.pathname === '/api/hello') {
      return new Response(
        JSON.stringify({
          message: 'Hello from the Edge!',
          location: request.cf?.city || 'Unknown',
          country: request.cf?.country || 'Unknown',
          timestamp: new Date().toISOString(),
        }),
        {
          headers: {
            'Content-Type': 'application/json',
            ...corsHeaders,
          },
        }
      );
    }

    // 캐시된 응답 반환
    if (url.pathname === '/api/cached') {
      const cacheKey = new Request(url.toString(), request);
      const cache = caches.default;

      let response = await cache.match(cacheKey);
      if (!response) {
        response = new Response(
          JSON.stringify({ data: 'Fresh data', time: Date.now() }),
          {
            headers: {
              'Content-Type': 'application/json',
              'Cache-Control': `s-maxage=${env.CACHE_TTL}`,
            },
          }
        );
        ctx.waitUntil(cache.put(cacheKey, response.clone()));
      }
      return response;
    }

    return new Response('Not Found', { status: 404 });
  },
};
// src/index.ts - Hono 프레임워크를 사용한 API 라우터
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { cache } from 'hono/cache';
import { jwt } from 'hono/jwt';

type Bindings = {
  JWT_SECRET: string;
  DATABASE: D1Database;
  RATE_LIMITER: RateLimit;
};

const app = new Hono<{ Bindings: Bindings }>();

// 미들웨어 설정
app.use('/*', cors());
app.use('/api/*', async (c, next) => {
  // Rate Limiting
  const { success } = await c.env.RATE_LIMITER.limit({
    key: c.req.header('CF-Connecting-IP') || 'anonymous',
  });
  if (!success) {
    return c.json({ error: 'Rate limit exceeded' }, 429);
  }
  await next();
});

// 공개 엔드포인트
app.get('/', (c) => c.json({ status: 'healthy', edge: c.req.raw.cf?.colo }));

// JWT 인증이 필요한 엔드포인트
app.use('/api/protected/*', jwt({ secret: (c) => c.env.JWT_SECRET }));

// D1 데이터베이스 조회
app.get('/api/users', async (c) => {
  const { results } = await c.env.DATABASE.prepare(
    'SELECT id, name, email FROM users LIMIT 100'
  ).all();
  return c.json(results);
});

// 사용자 생성
app.post('/api/users', async (c) => {
  const body = await c.req.json();
  const { name, email } = body;

  const result = await c.env.DATABASE.prepare(
    'INSERT INTO users (name, email) VALUES (?, ?) RETURNING id'
  ).bind(name, email).first();

  return c.json({ id: result?.id, name, email }, 201);
});

// 캐시된 응답 (1시간)
app.get(
  '/api/cached-data',
  cache({ cacheName: 'api-cache', cacheControl: 'max-age=3600' }),
  (c) => c.json({ data: 'This is cached for 1 hour' })
);

export default app;
// src/kv-session.ts - Workers KV를 사용한 세션 관리
export interface Env {
  SESSIONS: KVNamespace;
  SESSION_TTL: string;
}

interface Session {
  userId: string;
  email: string;
  roles: string[];
  createdAt: number;
  expiresAt: number;
}

// 세션 생성
export async function createSession(
  env: Env,
  userId: string,
  email: string,
  roles: string[]
): Promise<string> {
  const sessionId = crypto.randomUUID();
  const ttl = parseInt(env.SESSION_TTL) || 3600;

  const session: Session = {
    userId,
    email,
    roles,
    createdAt: Date.now(),
    expiresAt: Date.now() + ttl * 1000,
  };

  // KV에 세션 저장 (TTL 자동 만료)
  await env.SESSIONS.put(
    `session:${sessionId}`,
    JSON.stringify(session),
    { expirationTtl: ttl }
  );

  return sessionId;
}

// 세션 조회
export async function getSession(
  env: Env,
  sessionId: string
): Promise<Session | null> {
  const data = await env.SESSIONS.get(`session:${sessionId}`);
  if (!data) return null;

  const session: Session = JSON.parse(data);

  // 만료 확인
  if (session.expiresAt < Date.now()) {
    await env.SESSIONS.delete(`session:${sessionId}`);
    return null;
  }

  return session;
}

// 세션 갱신
export async function refreshSession(
  env: Env,
  sessionId: string
): Promise<boolean> {
  const session = await getSession(env, sessionId);
  if (!session) return false;

  const ttl = parseInt(env.SESSION_TTL) || 3600;
  session.expiresAt = Date.now() + ttl * 1000;

  await env.SESSIONS.put(
    `session:${sessionId}`,
    JSON.stringify(session),
    { expirationTtl: ttl }
  );

  return true;
}

// 세션 삭제 (로그아웃)
export async function deleteSession(
  env: Env,
  sessionId: string
): Promise<void> {
  await env.SESSIONS.delete(`session:${sessionId}`);
}

// wrangler.toml 설정 예시
/*
name = "session-api"
main = "src/index.ts"
compatibility_date = "2024-01-01"

kv_namespaces = [
  { binding = "SESSIONS", id = "abc123..." }
]

[vars]
SESSION_TTL = "7200"
*/

🗣️ 실무에서 이렇게 말하세요

아키텍처 설계 회의에서
"글로벌 사용자를 위한 API는 Cloudflare Workers로 배포하면 좋겠습니다. 서울, 도쿄, 싱가포르 등 전 세계 엣지에서 실행되니까 지연시간이 평균 50ms 이하로 줄어들어요. 특히 JWT 검증이나 캐시 조회 같은 가벼운 로직은 엣지에서 처리하고, 복잡한 비즈니스 로직만 오리진으로 보내면 됩니다."
면접에서 서버리스 경험 질문에
"Lambda의 콜드 스타트 문제를 해결하기 위해 Cloudflare Workers를 도입했습니다. V8 isolates 덕분에 콜드 스타트가 사실상 0ms여서 사용자 경험이 크게 개선되었고요. Workers KV로 세션 관리, Durable Objects로 실시간 채팅을 구현해서 월 1억 요청을 처리하면서도 비용은 Lambda 대비 60% 절감했습니다."
성능 최적화 논의에서
"현재 API 응답시간 p95가 300ms인데, Workers를 프록시로 두면 많이 줄일 수 있어요. 자주 요청되는 데이터는 Workers KV에 캐시하고, 요청 헤더 기반으로 A/B 테스트 라우팅도 엣지에서 처리하면 오리진 서버 부하도 줄어듭니다. Cache API로 HTML도 엣지 캐싱하면 TTFB가 획기적으로 개선될 거예요."

⚠️ 흔한 실수 & 주의사항

CPU 시간 제한 무시

Free 플랜은 요청당 CPU 시간 10ms, Paid는 30ms 제한이 있습니다. 무거운 연산(이미지 처리, 대용량 JSON 파싱)은 Worker에서 직접 처리하지 말고 오리진 서버로 위임하세요. ctx.waitUntil()은 응답 후 실행되지만 여전히 제한 적용됩니다.

KV의 eventually consistent 특성 간과

Workers KV는 전 세계적으로 복제되지만 최종 일관성(eventually consistent)을 가집니다. 데이터 쓰기 후 다른 리전에서 즉시 읽으면 구 버전이 반환될 수 있습니다. 강한 일관성이 필요하면 Durable Objects를 사용하세요.

올바른 방법: 적절한 워크로드 분리

Workers는 인증/인가, 캐시 조회, 요청 라우팅, 헤더 조작 등 가벼운 로직에 최적화되어 있습니다. DB 쿼리나 복잡한 비즈니스 로직은 오리진에서 처리하고, Workers는 엣지 프록시/캐시 레이어로 활용하세요.

🔗 관련 용어

📚 더 배우기