13년차의 서버실

서버, 인프라, 홈랩 기술 블로그

[태그:] Wrangler

  • [Cloud] Cloudflare Workers & R2 비용 최적화: 숨겨진 과금 피하는 실전 전략

    [Cloud] Cloudflare Workers & R2 비용 최적화: 숨겨진 과금 피하는 실전 전략

    Cloudflare Workers & R2 비용 최적화: 숨겨진 과금 피하는 실전 전략

    안녕하세요, 13년차 서버실 지킴이입니다. 오늘은 많은 분들이 관심 가질 만한 주제, 바로 Cloudflare Workers & R2 비용 최적화에 대한 이야기를 풀어보려고 합니다. 클라우드를 쓰다 보면 예상치 못한 과금에 당황하는 경우가 종종 있잖아요? 저도 처음엔 Cloudflare의 매력적인 Free Tier에 혹해서 이것저것 써보다가, 나중에 대시보드를 보고 ‘어라? 이 비용은 어디서 나온 거지?’ 하고 삽질 좀 했습니다. 특히 Cloudflare Workers(클라우드플레어 워커스, 서버리스 엣지 컴퓨팅 플랫폼)와 Cloudflare R2(클라우드플레어 R2, S3 호환 오브젝트 스토리지)는 정말 강력한 조합이지만, 그만큼 숨겨진 과금 포인트들이 있거든요. 오늘은 제가 직접 겪었던 경험을 바탕으로, 어떻게 하면 이 숨겨진 과금들을 피하고 Cloudflare 비용 최적화를 할 수 있는지 실전 전략을 공유해 드릴게요.

    Cloudflare Workers와 R2를 활용한 일반적인 데이터 흐름 아키텍처 다이어그램

    Cloudflare Workers와 R2를 사용한 기본적인 데이터 흐름 아키텍처 다이어그램. 사용자의 요청이 Workers를 통해 R2에 저장된 데이터를 가져오거나 처리하는 과정을 보여줍니다.

    Cloudflare Workers와 R2: 핵심 개념 이해하기

    먼저, Cloudflare Workers와 R2가 무엇인지 간단하게 짚고 넘어갈게요. 이미 잘 아시는 분들도 계시겠지만, 비용 최적화의 기본은 서비스의 작동 방식을 정확히 이해하는 거거든요.

    • Cloudflare Workers (클라우드플레어 워커스): 쉽게 말해, 전 세계 Cloudflare 엣지 네트워크에 배포되는 서버리스 JavaScript/WebAssembly 환경이에요. 사용자의 요청에 가장 가까운 엣지에서 코드가 실행되니 응답 속도가 빠르고, 서버 관리 부담이 없어서 정말 편합니다. Free Tier도 넉넉해서 개인 프로젝트나 소규모 서비스에 많이 쓰이죠.
    • Cloudflare R2 (클라우드플레어 R2): Amazon S3(아마존 S3)와 호환되는 오브젝트 스토리지 서비스입니다. 가장 큰 특징은 바로 이그레스(Egress, 외부로 나가는 데이터 전송) 비용이 무료라는 점이에요! 이게 진짜 혁명적이거든요. 다른 클라우드 스토리지 서비스들은 데이터가 나갈 때마다 비용을 청구해서, 트래픽이 많아지면 배보다 배꼽이 커지는 경우가 많았거든요.

    이 두 서비스는 조합하면 정적 파일 호스팅, API 게이트웨이, 이미지 리사이징 등 다양한 작업을 엣지에서 효율적으로 처리할 수 있습니다. 하지만 이 매력적인 무료 정책 뒤에는 몇 가지 주의해야 할 과금 포인트들이 숨어있어요.

    실전 구현: Workers로 R2 데이터 서빙하기

    제가 자주 사용하는 시나리오 중 하나는 Workers를 통해 R2에 저장된 정적 파일(이미지, 비디오 등)을 서빙하거나, 간단한 API 게이트웨이 역할을 하는 것입니다. 한번 기본적인 구현 과정을 살펴볼까요?

    1. R2 버킷 생성

    먼저 Cloudflare 대시보드에서 R2 버킷을 생성합니다. 버킷 이름은 고유해야 해요.

    2. Workers 프로젝트 생성 및 R2 바인딩

    <code>wrangler CLI(명령줄 인터페이스)를 사용해서 Workers 프로젝트를 만들고, R2 버킷을 바인딩해줍니다. 이 과정이 제일 중요해요.

    npx wrangler generate my-r2-worker --ts
    cd my-r2-worker
    

    wrangler.toml 파일을 열어서 R2 바인딩을 추가합니다. bucket_name은 여러분이 생성한 R2 버킷 이름으로 바꿔주세요.

    name = "my-r2-worker"
    main = "src/index.ts"
    compatibility_date = "2023-10-26"
    
    [[r2_buckets]]
    binding = "MY_BUCKET" # Workers 코드에서 사용할 변수 이름
    bucket_name = "my-awesome-r2-bucket" # 실제 R2 버킷 이름
    

    3. Workers 코드 작성: R2 데이터 반환

    src/index.ts 파일에서 R2 버킷에서 파일을 읽어 반환하는 코드를 작성합니다. 여기서는 간단하게 모든 요청에 대해 index.html 파일을 반환한다고 가정해볼게요.

    interface Env {
      MY_BUCKET: R2Bucket;
    }
    
    export default {
      async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
        const url = new URL(request.url);
        const key = url.pathname.slice(1) || 'index.html'; // 요청 경로를 R2 객체 키로 사용
    
        const object = await env.MY_BUCKET.get(key);
    
        if (object === null) {
          return new Response('File Not Found', { status: 404 });
        }
    
        const headers = new Headers();
        object.writeHttpMetadata(headers);
        headers.set('etag', object.httpEtag);
    
        // R2 객체 스트림을 직접 반환하여 Workers Egress 비용 최적화
        return new Response(object.body, {
          headers,
        });
      },
    };
    

    여기서 중요한 포인트가 하나 있습니다. return new Response(object.body, { headers }); 이 부분인데요. R2에서 읽어온 object.body 스트림을 Workers가 직접 클라이언트로 전달하도록 하는 것이 중요합니다. 이렇게 하면 Workers가 데이터를 내부적으로 처리하는 시간을 최소화하고, R2 Egress Free 정책의 혜택을 최대한 받을 수 있어요. 만약 Workers가 이 데이터를 가공하거나 다른 곳으로 전달한다면, Workers Data Egress(워커스 데이터 이그레스) 비용으로 과금될 수 있거든요. 저도 처음엔 이 부분을 간과해서 예상치 못한 과금이 발생했었습니다 😅.

    Cloudflare 대시보드 Workers 서비스의 R2 버킷 바인딩 설정 화면

    Cloudflare 대시보드에서 Workers 서비스에 R2 버킷이 올바르게 바인딩된 설정 화면을 보여줍니다.

    ⚠️ 숨겨진 과금 포인트와 실전 최적화 전략

    이제 본격적으로 숨겨진 과금 포인트를 파헤쳐보고, 제가 직접 겪은 삽질 경험을 바탕으로 한 Cloudflare 비용 최적화 전략을 공유해 드릴게요.

    1. Workers Compute Duration(워커스 컴퓨트 듀레이션) 과금 최소화

    Workers는 코드가 실행되는 시간에 따라 과금되죠. Free Tier는 하루에 10만 건의 요청과 1,000,000ms(1초)의 CPU 시간이 주어지지만, 이를 초과하면 과금됩니다.

    • 불필요한 작업 줄이기: Workers 내부에서 복잡한 연산, 외부 API 호출 대기 시간 등을 최소화해야 합니다. 특히 R2에서 데이터를 읽어오는 동안 Workers가 대기하는 시간도 Compute Duration에 포함될 수 있거든요.
    • 스트리밍 활용: 위 코드 예시처럼 R2의 object.body를 직접 Response로 반환하면, Workers가 모든 데이터를 메모리에 로드할 필요 없이 스트리밍으로 처리하여 Compute Duration을 줄일 수 있어요.
    • Durable Objects(듀러블 오브젝트) 사용 주의: Durable Objects는 상태 저장형 Workers로 매우 강력하지만, 일반 Workers보다 과금 기준이 복잡하고 비쌀 수 있습니다. 꼭 필요한 경우에만 사용하고, 사용 패턴을 면밀히 분석해야 해요.

    2. R2 API Requests(R2 API 요청) 최적화

    R2는 스토리지 용량 외에도 API 요청 횟수에 따라 과금됩니다. 특히 Class A(쓰기 작업) 요청이 Class B(읽기 작업) 요청보다 비싸죠.

    • 캐싱 전략: Workers에서 R2 데이터를 읽어올 때, Cloudflare의 Cache API(캐시 API)를 적극 활용하세요. 자주 접근하는 데이터는 엣지 캐시에 저장하여 R2 API 호출 횟수를 줄일 수 있어요. 예를 들어, 이미지 파일 같은 정적 콘텐츠는 Cache API와 함께 Cache-Control 헤더를 잘 설정하면 R2 요청을 확 줄일 수 있습니다.
    • Batch 작업: 여러 객체를 한 번에 처리해야 한다면, Batch API를 사용하여 요청 횟수를 줄일 수 있는지 고려해보세요.

    3. 데이터 이그레스(Data Egress) 착시 효과 방지

    ⚠️ R2는 Egress Free가 맞지만, Workers의 Egress는 별도 과금됩니다. 제가 가장 크게 삽질했던 부분이 바로 여기인데요.

    • Workers Data Egress: Workers가 R2에서 데이터를 읽어서 클라이언트에게 전달할 때, 이 데이터는 R2의 Egress가 아닌 Workers의 Data Egress로 계산됩니다. 하지만 위에서 설명한 return new Response(object.body, { headers }); 방식처럼 R2의 스트림을 그대로 전달하면, Cloudflare는 이를 R2 Egress로 간주하여 무료 혜택을 적용해줍니다. 즉, Workers가 R2 데이터를 “거의 가공 없이” 프록시처럼 전달할 때만 R2의 Egress Free 혜택을 받는다고 생각하는 게 좋아요. 만약 Workers가 R2 데이터를 읽어서 이미지 리사이징을 하거나, JSON 형태로 가공해서 보내면, 그건 Workers Data Egress로 과금될 수 있거든요. 이 부분은 공식 문서도 좀 헷갈리게 되어 있어서, 저처럼 삽질하는 분들이 많더라고요.
    • Cloudflare CDN(콘텐츠 전송 네트워크) 활용: R2에 직접 도메인을 연결하고 Cloudflare CDN을 사용하면, R2 Egress Free 혜택을 온전히 누릴 수 있습니다. Workers는 복잡한 로직이 필요할 때만 사용하고, 단순 정적 파일 서빙은 R2 + CDN 조합이 비용 효율적이에요.

    4. 로그 분석으로 비용 낭비 요소 찾기

    Cloudflare Workers는 요청 로그를 제공합니다. 이를 통해 어떤 요청이 Compute Duration을 많이 소모하는지, 어떤 Workers가 비정상적으로 많이 호출되는지 등을 파악할 수 있어요. 저도 주기적으로 로그를 보면서 최적화 포인트를 찾아냅니다.

    Cloudflare 대시보드에서 Workers 및 R2의 상세 비용 분석 대시보드

    Cloudflare 대시보드에서 Workers의 Compute Duration, Requests, R2 스토리지 및 API 요청 수 등 자세한 비용 분석 데이터를 보여주는 화면입니다.

    검증 및 결과: 비용 모니터링의 중요성

    최적화 전략을 적용했다면, 이제 그 효과를 검증할 차례입니다. Cloudflare 대시보드의 Analytics(분석) 섹션과 Billing(청구) 섹션을 주기적으로 확인하는 것이 중요해요.

    • Workers Analytics: Workers의 요청 수, CPU 시간, 오류율 등을 자세히 볼 수 있습니다. 여기서 비정상적인 패턴이나 높은 CPU 시간을 소모하는 Workers를 찾아내 개선할 수 있어요.
    • R2 Analytics: R2의 스토리지 사용량, Class A/B API 요청 수, 데이터 전송량 등을 확인할 수 있습니다. 특히 API 요청 수가 예상보다 높다면 캐싱 전략을 다시 점검해야 합니다.
    • Billing Page: 마지막으로 청구 페이지에서 실제 과금 내역을 확인합니다. 과금 항목별로 상세 내역을 볼 수 있으니, 어떤 서비스에서 비용이 발생했는지 정확히 파악하고 다음 최적화 계획을 세울 수 있어요.

    저도 처음에는 대시보드 보는 법도 익숙지 않아서 헤맸는데, 몇 번 해보니 금방 적응되더라고요. 실제 비용이 줄어드는 걸 보면 그렇게 뿌듯할 수가 없습니다 🎉.

    Cloudflare Workers 및 R2 비용 최적화 핵심 전략 요약 인포그래픽

    Cloudflare Workers 및 R2 비용 절감을 위한 핵심 전략들을 시각적으로 요약한 인포그래픽. 캐싱, 스트리밍, 로깅, 모니터링 등의 키워드와 간략한 설명을 포함합니다.

    마무리하며: 지속적인 관심이 최고의 비용 절감 전략

    오늘은 Cloudflare Workers와 R2를 사용하면서 제가 직접 겪었던 Cloudflare 비용 최적화 경험과 실전 전략들을 공유해 드렸습니다. 핵심은 서비스의 과금 방식을 정확히 이해하고, 불필요한 자원 소모를 최소화하며, 지속적으로 모니터링하는 것입니다.

    Cloudflare는 정말 매력적인 서비스들을 많이 제공하지만, 클라우드 서비스가 다 그렇듯 ‘무료’라는 말에 혹해서 무작정 사용하다 보면 예상치 못한 비용 폭탄을 맞을 수도 있어요. 하지만 오늘 알려드린 전략들을 잘 활용하시면, Cloudflare Workers와 R2의 강력한 성능과 R2의 Egress Free라는 엄청난 장점을 비용 걱정 없이 누릴 수 있을 겁니다.

    저도 여전히 새로운 기술들을 실험하고 삽질하면서 배우고 있습니다. 여러분도 저의 경험을 발판 삼아 더 효율적인 인프라를 구축하시길 바랍니다! 다음번에는 Cloudflare의 다른 서비스, 예를 들어 KV(Key-Value Store)나 Durable Objects를 활용할 때의 비용 고려 사항에 대해 이야기해볼까 합니다. 그때까지 다들 즐거운 삽질(?) 되세요!

  • [Cloud] Cloudflare Workers로 서버리스 API 및 엣지 컴퓨팅 구축 가이드

    서버 없이 전 세계에서 실행되는 API? Cloudflare Workers 이야기

    솔직히 말씀드리면, 처음 Cloudflare Workers를 접했을 때 “이게 뭔 소리지?” 싶었거든요. 서버리스(Serverless)라는 개념 자체는 알고 있었는데, “엣지에서 실행된다”는 말이 딱 와닿지 않았습니다. 그러다 실제로 간단한 API 하나를 올려봤는데… 서울에서 요청하면 서울 근처 데이터센터에서, 미국에서 요청하면 미국 데이터센터에서 응답이 오는 거예요. 레이턴시(Latency, 응답 지연)가 확 줄어드는 게 느껴졌습니다. 그때 “아, 이래서 엣지 컴퓨팅이구나” 하고 감이 왔죠.

    홈랩에서 이것저것 돌리다 보면 늘 고민이 생기잖아요. 외부에서 접근 가능한 간단한 API가 필요한데, 서버 하나 띄우자니 유지보수가 귀찮고, 클라우드 VM 쓰자니 비용이 아깝고. 그 틈새를 Cloudflare Workers가 정말 잘 채워줬습니다. 오늘은 제가 실제로 Workers로 서버리스 API를 구축하면서 겪은 경험을 바탕으로, 처음 시작하시는 분들도 따라할 수 있게 정리해 드릴게요.

    ▲ Cloudflare Workers의 엣지 컴퓨팅 구조 — 전 세계 데이터센터에서 코드가 실행되는 개념도


    Cloudflare Workers가 뭔지 쉽게 풀어보기

    엣지 컴퓨팅(Edge Computing)이란?

    일반적인 클라우드 서버는 특정 리전(Region, 지역)에 고정되어 있어요. 예를 들어 AWS ap-northeast-2(서울 리전)에 서버를 올리면, 미국 사용자가 접속할 때는 태평양을 건너서 요청이 오가야 하죠. 이게 레이턴시의 주범입니다.

    엣지 컴퓨팅은 이 문제를 뒤집어서 생각한 거예요. “서버를 중앙에 두지 말고, 사용자 가까이에 두자!” 라는 발상이죠. Cloudflare는 전 세계 200개 이상의 도시에 데이터센터(PoP, Point of Presence)를 운영하고 있는데, Workers 코드가 이 모든 위치에서 실행됩니다. 쉽게 말해, 사용자가 어디에 있든 가장 가까운 서버에서 응답이 오는 거예요.

    Workers의 핵심 특징

    • V8 엔진 기반: Node.js가 아닌 V8 Isolate(아이솔레이트) 방식으로 실행됩니다. 컨테이너보다 훨씬 빠르게 시작해요 (콜드 스타트 거의 없음)
    • JavaScript / TypeScript 지원: 프론트엔드 개발자분들도 친숙하게 쓸 수 있어요
    • Workers KV: 엣지에서 사용 가능한 키-값(Key-Value) 스토리지
    • 무료 티어 존재: 하루 10만 요청까지 무료 (최신 정보는 Cloudflare 공식 가격 페이지 확인 권장)
    • 배포가 초간단: Wrangler CLI 명령어 하나로 전 세계 배포 완료
    구분 전통적 서버 일반 서버리스 (Lambda 등) Cloudflare Workers
    실행 위치 단일 리전 단일 리전 전 세계 엣지
    콜드 스타트 없음 수백ms ~ 수초 거의 없음 (0ms 수준)
    관리 부담 높음 낮음 매우 낮음
    런타임 자유 Node.js, Python 등 V8 (JS/TS/Wasm)
    무료 티어 없음 있음 있음

    환경 설정: Wrangler CLI 설치부터 시작

    본격적으로 시작해볼게요. Wrangler는 Cloudflare Workers 개발을 위한 공식 CLI(Command Line Interface) 도구입니다. 이게 없으면 아무것도 못 하니까 먼저 설치부터요.

    1단계: Node.js 및 Wrangler 설치

    Node.js 16 이상이 설치되어 있다는 전제 하에 진행합니다.

    # Wrangler CLI 전역 설치
    npm install -g wrangler
    
    # 설치 확인
    wrangler --version
    
    # Cloudflare 계정 로그인 (브라우저가 열립니다)
    wrangler login

    로그인하면 브라우저에서 Cloudflare 대시보드로 이동해서 권한 허용을 요청해요. 그냥 허용 누르시면 됩니다. 처음에 이 과정이 좀 낯설었는데, 익숙해지면 진짜 편하더라고요.

    2단계: 새 Workers 프로젝트 생성

    # 새 프로젝트 생성 (대화형 설정)
    npm create cloudflare@latest my-api-worker
    
    # 프로젝트 디렉토리로 이동
    cd my-api-worker

    생성 과정에서 몇 가지 질문이 나오는데, “Hello World” 템플릿 선택하고, TypeScript 쓸지 JavaScript 쓸지 고르면 됩니다. 저는 보통 TypeScript를 선택하는 편이에요. 타입 안정성이 있으니까요.

    프로젝트 구조는 이렇게 됩니다:

    my-api-worker/
    ├── src/
    │   └── index.ts        # 메인 Worker 코드
    ├── wrangler.toml       # Worker 설정 파일
    ├── package.json
    └── tsconfig.json

    실전 구현: 서버리스 REST API 만들기

    이제 진짜 코드를 써볼게요. 단순한 “Hello World”는 재미없으니까, 실제로 쓸 만한 간단한 REST API를 만들어보겠습니다. 라우팅(Routing)과 메서드 분기를 포함한 구조예요.

    ▲ Wrangler를 이용한 로컬 개발 환경 — wrangler dev 명령으로 로컬에서 Workers를 테스트할 수 있어요

    기본 라우팅이 포함된 API Worker

    // src/index.ts
    
    export interface Env {
      // 나중에 KV 바인딩을 여기 추가할 거예요
      MY_KV: KVNamespace;
    }
    
    export default {
      async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
        const url = new URL(request.url);
        const path = url.pathname;
        const method = request.method;
    
        // CORS 헤더 설정
        const corsHeaders = {
          'Access-Control-Allow-Origin': '*',
          'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
          'Access-Control-Allow-Headers': 'Content-Type',
        };
    
        // Preflight 요청 처리
        if (method === 'OPTIONS') {
          return new Response(null, { headers: corsHeaders });
        }
    
        // 라우팅 처리
        if (path === '/api/hello' && method === 'GET') {
          return Response.json(
            { message: '안녕하세요! Cloudflare Workers에서 응답합니다.', timestamp: Date.now() },
            { headers: corsHeaders }
          );
        }
    
        if (path === '/api/items' && method === 'GET') {
          return handleGetItems(env, corsHeaders);
        }
    
        if (path === '/api/items' && method === 'POST') {
          return handlePostItem(request, env, corsHeaders);
        }
    
        // 404 처리
        return Response.json(
          { error: 'Not Found', path },
          { status: 404, headers: corsHeaders }
        );
      },
    };
    
    // GET /api/items — KV에서 아이템 목록 조회
    async function handleGetItems(env: Env, headers: Record<string, string>): Promise<Response> {
      const data = await env.MY_KV.get('items', 'json') as string[] | null;
      return Response.json(
        { items: data ?? [] },
        { headers }
      );
    }
    
    // POST /api/items — KV에 아이템 추가
    async function handlePostItem(
      request: Request,
      env: Env,
      headers: Record<string, string>
    ): Promise<Response> {
      const body = await request.json() as { name: string };
    
      if (!body.name) {
        return Response.json(
          { error: 'name 필드가 필요합니다' },
          { status: 400, headers }
        );
      }
    
      const existing = await env.MY_KV.get('items', 'json') as string[] | null;
      const items = existing ?? [];
      items.push(body.name);
    
      await env.MY_KV.put('items', JSON.stringify(items));
    
      return Response.json(
        { success: true, items },
        { status: 201, headers }
      );
    }

    코드 보시면 구조가 꽤 직관적이죠? fetch 함수가 모든 HTTP 요청의 진입점이고, URL 경로와 HTTP 메서드로 분기처리를 합니다. Node.js의 Express 같은 느낌이에요.

    Workers KV 설정하기

    Workers KV는 엣지에서 사용 가능한 분산 키-값 스토리지예요. 데이터베이스를 별도로 운영하지 않아도 간단한 데이터를 저장하고 조회할 수 있거든요. 쓰기는 약간의 전파 지연이 있지만, 읽기는 엣지에서 바로 처리되니까 빠릅니다.

    # KV 네임스페이스(Namespace) 생성
    wrangler kv:namespace create MY_KV
    
    # 로컬 개발용 프리뷰 네임스페이스도 생성
    wrangler kv:namespace create MY_KV --preview

    명령어 실행 후 출력되는 ID를 wrangler.toml에 붙여넣어야 합니다.

    # wrangler.toml
    name = "my-api-worker"
    main = "src/index.ts"
    compatibility_date = "2024-01-01"
    
    [[kv_namespaces]]
    binding = "MY_KV"          # 코드에서 env.MY_KV로 접근
    id = "여기에_실제_KV_ID_입력"
    preview_id = "여기에_프리뷰_KV_ID_입력"

    💡 팁: binding 이름이 TypeScript 인터페이스의 Env에 선언한 이름과 일치해야 해요. 처음에 이게 안 맞아서 에러가 났었는데, 삽질 좀 했습니다 ㅎㅎ

    로컬에서 테스트하기

    # 로컬 개발 서버 실행
    wrangler dev
    
    # 기본적으로 http://localhost:8787 에서 실행됩니다
    
    # 다른 터미널에서 API 테스트
    curl http://localhost:8787/api/hello
    
    # POST 테스트
    curl -X POST http://localhost:8787/api/items \
      -H "Content-Type: application/json" \
      -d '{"name": "테스트 아이템"}'
    
    # GET으로 확인
    curl http://localhost:8787/api/items

    로컬에서 잘 돌아가면 이제 배포할 차례예요!

    # 전 세계 배포 (진짜 이 명령어 하나예요)
    wrangler deploy
    
    # 배포 후 URL이 출력됩니다
    # 예: https://my-api-worker.your-subdomain.workers.dev

    🎉 배포 완료! 이 URL로 전 세계 어디서든 접근 가능합니다. 처음에 이게 진짜로 되는 건지 믿기지 않아서 VPN으로 미국, 일본, 유럽 각각에서 테스트해봤는데 전부 잘 됐더라고요.


    ⚠️ 주의사항 및 실제 겪은 트러블슈팅

    좋은 것만 얘기하면 재미없죠. 실제로 쓰다 보면 꼭 한 번씩 걸리는 것들이 있어요.

    문제 1: Workers KV 쓰기 전파 지연

    KV에 데이터를 쓰고 바로 읽으면 이전 값이 나오는 경우가 있어요. KV는 최종 일관성(Eventual Consistency) 모델이라서, 쓰기 작업이 전 세계 엣지에 전파되는 데 시간이 걸립니다. 공식 문서에 따르면 최대 60초 정도 걸릴 수 있어요. 로컬 개발 환경에서는 이게 즉시 반영되는 것처럼 보이는데, 프로덕션에서는 다를 수 있습니다.

    해결책: 쓰기 직후 즉시 읽기가 필요한 로직은 KV 대신 Durable Objects(내구성 있는 객체, 강한 일관성 보장)를 고려하세요. 단순 캐시나 설정값 저장에는 KV가 딱 좋습니다.

    문제 2: CPU 시간 제한

    Workers는 요청당 CPU 시간 제한이 있어요. 무료 티어는 요청당 10ms, 유료 플랜은 더 길어요. 무거운 연산(이미지 처리, 복잡한 암호화 등)은 Workers에 맞지 않습니다. 저도 처음에 이미지 리사이징 로직을 Workers에 넣으려다 바로 한계에 부딪혔어요.

    해결책: Workers는 가볍고 빠른 로직에 최적화되어 있거든요. 무거운 작업은 백엔드 서버로 위임하고, Workers는 게이트웨이(Gateway, 진입 관문) 역할만 맡기는 패턴이 좋습니다.

    문제 3: Node.js API 호환성 이슈

    Workers는 Node.js 런타임이 아니에요. V8 Isolate 기반이라 Node.js 전용 API(예: fs, path, crypto 일부)가 기본적으로 없어요. npm 패키지 중에도 Node.js 내장 모듈에 의존하는 것들은 Workers에서 안 돌아갈 수 있습니다.

    해결책: wrangler.toml에 node_compat = true를 추가하면 일부 Node.js API 폴리필(Polyfill, 하위 호환 구현체)이 활성화됩니다. 그래도 안 되는 경우엔 Workers 환경에 맞는 대안 라이브러리를 찾아야 해요.

    # wrangler.toml에 추가
    node_compat = true

    문제 4: 환경 변수 관리

    API 키 같은 민감한 정보를 코드에 하드코딩하면 절대 안 되죠. Workers에는 Secrets(시크릿) 기능이 있어요.

    # 시크릿 등록 (값은 프롬프트로 입력)
    wrangler secret put MY_API_KEY
    
    # 등록된 시크릿 목록 확인
    wrangler secret list
    // 코드에서 env로 접근
    export interface Env {
      MY_KV: KVNamespace;
      MY_API_KEY: string;  // 시크릿도 Env에 선언
    }
    
    // 사용 예시
    const apiKey = env.MY_API_KEY;

    결과 확인: 대시보드에서 모니터링하기

    배포 후 Cloudflare 대시보드에서 Workers 섹션을 보면 꽤 유용한 정보들을 확인할 수 있어요.

    ▲ Cloudflare Workers 대시보드 — 요청 수, 에러율, CPU 시간 등 실시간 모니터링이 가능해요

    • ✅ 요청 수(Requests): 분/시간/일별 요청량 확인
    • ✅ 에러율(Error Rate): 4xx, 5xx 에러 비율
    • ✅ CPU 시간: 요청당 평균 CPU 사용 시간
    • ✅ 실시간 로그: wrangler tail 명령으로 실시간 로그 스트리밍 가능
    # 실시간 로그 스트리밍
    wrangler tail
    
    # 특정 Worker 지정
    wrangler tail my-api-worker

    로그에서 요청 경로, 응답 코드, 실행 시간 등이 다 나와요. 디버깅할 때 꽤 유용하더라고요. 처음에 이 기능 몰라서 console.log 찍어가며 고생했는데, 알고 나서 “진작 쓸걸” 했죠.

    커스텀 도메인 연결

    기본 제공되는 *.workers.dev 도메인 말고, 본인 도메인을 연결할 수도 있어요. Cloudflare에 도메인이 등록되어 있다면 Workers 설정에서 Route(라우트, 트래픽 경로)를 추가하면 됩니다.

    # wrangler.toml에 라우트 추가
    [[routes]]
    pattern = "api.yourdomain.com/*"
    zone_name = "yourdomain.com"

    이렇게 하면 api.yourdomain.com으로 들어오는 모든 요청이 Workers로 처리돼요. 깔끔하죠?


    마무리: Workers가 잘 맞는 상황 vs 아닌 상황

    ▲ Cloudflare Workers 적합/비적합 사용 사례 요약 — 어떤 상황에서 Workers를 선택해야 할지 한눈에 보기

    몇 달 써보면서 느낀 건, Workers는 만능이 아니에요. 잘 맞는 상황이 있고, 억지로 쓰면 오히려 복잡해지는 상황도 있더라고요.

    ✅ Workers가 잘 맞는 경우

    • CORS 프록시, API 게이트웨이
    • A/B 테스트, 기능 플래그(Feature Flag) 처리
    • 간단한 인증/인가(Authentication/Authorization) 미들웨어
    • 정적 사이트의 동적 기능 추가 (Jamstack 패턴)
    • 봇 차단, 요청 필터링
    • 지리적으로 분산된 캐시 레이어

    ⚠️ 다른 선택지를 고려해야 할 경우

    • 무거운 연산이나 긴 실행 시간이 필요한 작업
    • 강한 데이터 일관성이 필요한 트랜잭션 처리
    • 파일 시스템 접근이 필요한 경우
    • 기존 Node.js/Python 등 런타임 의존성이 많은 경우

    자주 묻는 질문 (FAQ)

    Q. Workers KV와 일반 데이터베이스의 차이는?
    A. Workers KV는 읽기에 최적화된 분산 스토리지예요. 복잡한 쿼리나 관계형 데이터가 필요하다면 Cloudflare D1(SQLite 기반) 이나 외부 DB를 연동하는 게 낫습니다.
    Q. 무료로 얼마나 쓸 수 있나요?
    A. 공식 문서 기준 무료 티어는 하루 10만 요청까지 제공합니다. 정확한 현행 조건은 Cloudflare 공식 가격 페이지에서 반드시 확인하세요. 자주 바뀌는 편이거든요.
    Q. TypeScript 말고 다른 언어도 되나요?
    A. WebAssembly(웹어셈블리)로 컴파일되는 언어라면 이론상 가능해요. Rust로 Workers를 작성하는 사례도 있습니다. 다만 생태계는 JS/TS가 가장 풍부해요.

    오늘 다룬 내용이 Cloudflare Workers로 서버리스 API와 엣지 컴퓨팅을 시작하는 데 도움이 됐으면 좋겠어요. 다음 글에서는 Cloudflare D1(서버리스 SQLite 데이터베이스)과 Workers를 연동해서 더 완성도 있는 API를 만드는 방법을 다룰 예정입니다. Workers KV만으로는 아쉬울 때 딱 좋은 조합이거든요.

    궁금한 점이나 직접 해보다가 막히는 부분 있으시면 댓글로 남겨주세요. 같이 삽질해봅시다! 😄