구독해서 새 게시물에 대한 알림을 받으세요.

이제 Worker 앞에 자체 캐시를 가질 수 있습니다

2026-07-06

17분 읽기
이 게시물은 English日本語로도 이용할 수 있습니다.

본 콘텐츠는 사용자의 편의를 고려해 자동 기계 번역 서비스를 사용하였습니다. 영어 원문과 다른 오류, 누락 또는 해석상의 미묘한 차이가 포함될 수 있습니다. 필요하시다면 영어 원문을 참조하시기를 바랍니다.

오늘 저희는 Workers Cache를 출시합니다. 이는 Worker 앞에 위치하는 계층형 캐시로, 단 한 줄의 Wrangler 구성과 여러분이 이미 알고 있는 동일한 Cache-Control 헤더로 구성됩니다.

Workers Cache를 활성화하면 Worker에 대한 모든 캐시할 수 있는 요청이 Cloudflare의 캐시에 먼저 도달합니다. Cloudflare는 새 캐시된 응답이 있는 경우 이를 직접 반환합니다. Worker가 실행되지 않고 이에 대한 CPU 시간을 지불하지 않아도 됩니다. 누락된 응답이 있으면 Worker가 실행되고, 캐시 가능한 응답인 경우 Cloudflare는 다음 요청을 위해 응답을 저장합니다. 지구상 어디에서나 다음 요청은 캐시에서 바로 처리될 수 있습니다.

BLOG-3262 image1

모든 것이 하나의 config 블록으로 구성됩니다.

{
  "name": "my-worker",
  "main": "src/index.ts",
  "compatibility_date": "2026-05-01",
  "cache": {
    "enabled": true
  }
}

그런 다음 응답에 헤더를 설정하여 HTTP가 항상 원하는 방식으로 캐싱을 제어할 수 있습니다.

return new Response(body, {
  headers: {
    "Cache-Control": "public, max-age=300, stale-while-revalidate=3600",
    "Cache-Tag": "products,product:123",
  },
});

그리고 콘텐츠가 변경되면 Worker가 자체 캐시를 제거합니다.

await ctx.cache.purge({ tags: ["product:123"] });

이것이 API의 전부입니다. 구성할 영역, 규칙 엔진을 설정할 필요가 없으며, 별도의 캐시를 프로비저닝할 필요가 없으며, 보조 제품에 로그인할 필요도 없습니다. Worker의 코드는 구성 표면이며, 캐시는 Worker가 실행되는 어디든(베니티 도메인, workers.dev) 실행됩니다. 미리 보기로 Workers for Platforms 테넌트에서 서비스 바인딩 뒤에 하나의 Worker, 하나의 캐시가 한 번으로 구성됩니다.

표면적입니다. 그 이면에는 많은 것이 있습니다. 전체 네트워크에 걸친 계층형 캐싱, 오래된 응답이 사용자를 차단하지 않도록 stale-while-revalidate 에 대한 완벽한 지원, Vary를 통한 콘텐츠 협상, ctx.props를 통한 다중 테넌트 안전 캐시 키, 그리고 Cloudflare는 태그 또는 경로 접두사를 사용한 프로그래밍 방식의 제거를 지원합니다. Cloudflare가 가장 큰 잠금 해제 기능이라고 생각하는 캐시는 공개된 Worker 진입점뿐만 아니라 모든 Worker 진입점 앞에 위치하여 어떤 항목을 캐시하고 어떤 항목을 캐시하지 않는지 진입점별로 제어하는 기능을 갖추고 있습니다. . 마지막으로 언급한 점은 캐싱 기능을 애플리케이션 구조 내에 직접 구성할 수 있다는 것을 의미합니다. 즉, 원하는 위치에 캐시 단계를 삽입하여 엔트리포인트(entrypoint)를 하나의 체인으로 연결하고, 그 앞뒤에 위치한 코드를 통해 이를 설정할 수 있습니다. 아래에서 자세히 살펴보겠습니다.

Workers Cache는 Wrangler에 활성화된 요금제를 이용하는 모든 Worker에게 오늘부터 제공됩니다.

이것이 바로 Cloudflare가 항상 Workers를 갖고 싶어 했던 캐싱 API입니다. Cloudflare Zero Trust가 달성되기까지 왜 이렇게 오랜 시간이 걸렸는지, 그로 인해 무엇이 가능해졌는지, 그리고 앞으로 어떤 변화가 있는지 알아보겠습니다.

서버에서 렌더링된 앱 앞에 캐시가 필요한 이유

2017년에 Workers를 소개했을 때, Cloudflare 네트워크에서 코드를 실행하여 원본으로 향하는 요청을 변환할 수 있다는 것이 핵심이었습니다. 작업자는 캐시와 원본 앞에 앉았습니다:

BLOG-3262 image5

Cloudflare는 우리가 목표로 하는 사용 사례에 적합한 모델이었습니다. 모든 요청에 Worker 헤더를 추가하거나, URL을 재작성하거나, A/B 분할을 수행하거나, 트래픽이 원본에 도달하기 전에 트래픽을 필터링하고 싶은 경우, Worker를 캐시 앞에 배치하고 원본을 배치하여 캐시할 내용을 완전히 제어할 수 있었습니다. 고객은 클라우드에서 놀라운 일을 해냈습니다.

하지만 세상이 바뀌었습니다. Workers는 더 이상 오리진에 연결한 것에서 벗어나 오리진 이 되기 시작했습니다. Astro, TanStack Start, Next.js와 같은 프레임워크, RemixSvelteKit 모두 앱을 Worker로 빌드하는 Cloudflare 어댑터와 함께 제공됩니다. 그 배후에는 출처가 없습니다. Worker 서버입니다.

Worker가 원본인 경우 원래 아키텍처에는 캐시할 것이 없습니다. 응답이 이전에 반환했던 응답과 바이트 단위로 동일하더라도, 모든 요청으로 코드가 실행됩니다. Workers 런타임은 이 작업이 가능할 만큼 충분히 빠릅니다. — 일상적으로 어려움 없이 초당 수천만 건의 요청을 처리하지만, "모든 요청을 렌더링할 만큼 충분히 빠른" 경우에도 여전히 모든 페이지 로드 시 대기 시간과 모든 호출 시 CPU 시간을 발생시킵니다. 그리고 서버에서 렌더링된 앱에서는 모든 페이지 로드가 정의상 렌더링입니다.

Workers Cache는 아키텍처를 뒤집습니다. Cloudflare의 캐시는 이제 Worker 앞에 위치합니다.

BLOG-3262 image9

캐시 적중 시 Worker가 전혀 실행되지 않습니다. Cloudflare가 캐시된 응답을 반환하고 CPU 요금은 0으로 유지됩니다. 누락되면 Worker가 한 번 실행되고 캐시가 채워지며, 코드를 호출하지 않고도 어디에서나 다음 요청이 캐시에서 처리됩니다.

이는 Workers의 서버 측 렌더링에서 누락되었던 기능입니다. 두 가지 불만족스러운 옵션 중 하나를 선택해야 했다면,

  • 빌드 시 모든 것을 미리 렌더링합니다 ("정적 사이트 생성"). 페이지 로드는 빠르지만 변경 사항이 있을 경우 전체 재구축 및 재배포 과정이 필요합니다. 페이지가 수천 개에 달하는 문서 사이트의 경우 5~10분이면 됩니다. 대규모 전자 상거래 사이트의 경우 더 안 좋은 일이며, 무언가를 건드릴 때마다 빌드가 실행됩니다.

  • 모든 요청에 대해 모든 페이지를 렌더링하세요. 최신 콘텐츠로 유지하지만, 페이지가 로드될 때마다 렌더링 비용이 발생하고 모든 방문자는 대기 시간을 비용을 지불하게 됩니다.

Workers Cache는 세 번째 선택지를 제공합니다. 바로 요청 시 서버에서 렌더링하고, 렌더링된 응답을 캐시한 뒤, 사용자가 설정한 TTL(Time-to-Live)에 맞춰 이를 갱신하는 방식입니다. 새 페이지에 대한 첫 번째 요청은 여전히 렌더링됩니다. 캐시가 만료될 때까지 모든 후속 요청은 페이지가 정적인 것처럼 처리됩니다. 캐시가 만료되면 다음 요청이 재렌더링을 트리거하며, stale-while-revalidate를 사용하면 해당 요청조차 기다리지 않습니다.

구축 시간이 없는 정적 사이트의 속도와 비용 없이 서버 렌더링의 신선함을 얻을 수 있습니다. Incremental Static Regeneration과 같은 프레임워크 전용 메커니즘 없음. 원본이 되도록 설계된 코드 앞에서 설계된 방식으로 작동하는 HTTP 캐싱만 있으면 됩니다.

stale-while-revalidate는 즉각적으로 느껴지는 부분입니다.

stale-while-revalidate 지시어는 캐시된 응답이 만료되었을 때, 백그라운드에서 응답을 갱신하는 동안 즉시 만료된(stale) 사본을 제공해도 된다고 Cloudflare에 알립니다. Cloudflare는 올해 초 stale-while-revalidate를 완벽하게 지원하기 시작했으며, 이는 "Worker를 캐시합니다"를 "Worker 사이트가 정적으로 느껴집니다"로 바꾸는 지침입니다.

주소가 없으면 캐시 항목이 만료된 후 첫 번째 요청은 Worker가 페이지를 처음부터 렌더링할 때까지 기다려야 합니다. 사용자는 이러한 대기 시간을 보게 됩니다. 이 옵션을 사용하면 만료 후 첫 번째 요청이 부실 페이지를 즉시 가져오고(Cf-Cache-Status: UPDATING 헤더와 함께), Worker 백그라운드에서 실행되어 캐시를 다시 채웁니다. 새로 고침을 트리거한 사용자를 포함하여 모든 사용자가 캐시 속도 응답을 받습니다.

BLOG-3262 image3

실제로는 다음과 같습니다.

 export default {
  async fetch(request) {
    const html = await renderPage(request);
    return new Response(html, {
      headers: {
        "Content-Type": "text/html; charset=utf-8",
        // Treat as fresh for 5 minutes; serve stale for up to an hour
        // while a background refresh runs.
        "Cache-Control": "public, max-age=300, stale-while-revalidate=3600",
      },
    });
  },
};

클릭을 유발하는 멘탈 모델은 다음과 같습니다.

  • 새로운 창 (최대 사용 기간): Cloudflare가 캐시된 응답을 제공합니다. Worker가 실행되지 않습니다.

  • 오래된 창 (stale-while-revalidate): Cloudflare가 캐시된 응답을 제공합니다. Worker가 백그라운드에서 실행되어 새로 고침합니다. 사용자가 기다리지 않습니다.

  • 두 창 외부: Cloudflare는 Worker를 실행하여 새로운 응답을 생성하고, 사용자는 해당 렌더링을 기다립니다.

기간을 선택하세요. 몇 분마다 업데이트되는 제품 카탈로그의 경우, max-age=300, stale-while-revalidate=3600 이면 방문자가 기본적으로 기다리지 않고 Worker가 콘텐츠를 최신 상태로 유지할 수 있을 만큼 자주 실행됩니다. 거의 변경되지 않는 블로그 아카이브의 경우, max-age=86400, stale-while-revalidate=2592000 은 Worker가 페이지당 하루에 한 번 실행됨을 의미합니다.

새 페이지에 대한 첫 번째 요청은 전체 렌더링 비용을 지불하는 유일한 요청입니다. 그 후에는 페이지가 방문자에 대한 정적 출력처럼 작동하는 반면, Worker는 여전히 페이지가 생성되는 방식을 소유합니다.

하나의 URL, 다양한 표현: Vary 작동

실제 앱이 모든 클라이언트에 동일한 바이트를 반환하는 경우는 거의 없습니다. 동일한 제품 페이지가 브라우저의 경우 HTML, API 클라이언트의 경우 JSON일 수 있습니다. 동일한 이미지가 WebP를 지원하는 클라이언트에서는 WebP일 수 있고, 지원하지 않는 클라이언트에서는 JPEG일 수 있습니다. 사용자에 따라 동일한 홈페이지가 영어, 프랑스어, 일본어로 다시 표시될 수 있습니다.

캐시 없이 이 작업은 쉽게 수행할 수 있습니다. Worker가 요청 헤더를 읽고 올바른 것을 반환하기만 하면 됩니다. 캐시로 이 작업을 수행하는 것은 일반적으로 추악해지는 곳입니다. 대부분의 캐시는 두 가지 나쁜 옵션을 제공합니다. 여러 표현이 있는 URL에 캐시하지 않음과 하나의 표현을 캐시하여 모든 사람에게 제공하는 것입니다.

Workers Cache는 이 문제를 해결하기 위한 올바른 방법인 표준 HTTP Vary 헤더를 지원합니다. Worker가 Vary: Accept-Encoding(또는 Accept, Accept-Language, 혹은 기타 다른 요청 헤더)가 포함된 응답을 반환하면, Cloudflare는 해당 헤더들의 각 고유 조합별로 별도의 캐시된 변형(variant)을 저장하며, 저장된 값이 들어오는 요청과 일치하는 변형만 반환합니다.

export default {
  async fetch(request) {
    const accept = request.headers.get("Accept") ?? "";
    const wantsWebp = accept.includes("image/webp");

    const body = wantsWebp ? await fetchWebpImage() : await fetchJpegImage();

    return new Response(body, {
      headers: {
        "Content-Type": wantsWebp ? "image/webp" : "image/jpeg",
        "Cache-Control": "public, max-age=3600",
        // Cache a separate variant per distinct Accept header value.
        Vary: "Accept",
      },
    });
  },
};

하나의 URL, 두 개의 캐시된 변형. Accept: image/webp,*/*를 전송하는 브라우저는 WebP를 가져옵니다. Accept: image/jpeg를 전송하는 브라우저는 JPEG를 가져옵니다. 둘 다 캐시에서 가져옵니다. Worker는 첫 번째 요청에서 두 변형을 모두 작성하고, 그 이후에는 0번 실행됩니다.

이는 콘텐츠 협상에 대해 잘 알려진 HTTP 표준이며, Workers Cache는 RFC 9110RFC 9111 에 설명된 방식으로 이를 구현합니다. 연결 전환이 가능한 헤더에 대한 허용 목록은 없습니다. 필요한 모든 것을 열거하기만 하면 Cloudflare는 그 말 그대로 변형된 값을 키링합니다. 문서는 특이한 사례, 즉 게이트웨이 Worker에서 헤더를 정규화하여 변종 팬아웃을 계속 제어하는 방법, 제거가 URL의 모든 변종을 한꺼번에 무효화하는 이유, 캐싱을 완전히 비활성화하는 한 가지 사례(Vary: *)를 살펴봅니다.

이는 사용자 영역의 캐시가 아니라 Worker's

이것으로 무엇이 가능해졌는지를 다루기 전에, 가치가 있는 개념적 변화가 있었습니다.

Cloudflare에는 영원한 캐시가 있습니다. 캐시 규칙, Page Rules, 캐시된 파일 확장자 목록, Cache Reserve, Tiered Cache 토폴로지, 사용자 지정 캐시 키와 같은 영역 수준에서 구성됩니다. 모든 것은 영역별로 설정되며, 이전에는 해당 영역의 구성에 맞추거나 이를 우회해야 했습니다.

Workers Cache는 다릅니다. 이는 Worker의 캐시 입니다. 영역이 아닌 Worker에 속합니다. 이는 중요한 여러 가지 결과를 초래합니다.

  • 영역 구성을 관리할 필요가 없습니다. 캐시 규칙, 캐시 수준 설정, 파일 확장자 목록, Page Rules — 이들 중 그 어떤 것도 Workers Cache에 적용되지 않습니다. The Worker's Cache-Control headers are the configuration.

  • 캐시는 호스트 이름이 아니라 Worker를 따릅니다. api.example.com에 바인딩된 Worker api.example.net, 서비스 바인딩을 통해 호출된 캐시는 세 가지 캐시 모두에서 하나의 캐시를 공유합니다. /users/42 에 대한 요청은 어떤 경로로 들어왔는지에 관계없이 동일한 캐시된 항목에 도달합니다.

  • 캐시는 workers.dev에서 작동합니다. 이는 미리 보기 URL 에서 작동합니다(각 미리 보기는 자체 캐시를 가지므로 변경 사항을 테스트해도 프로덕션에 지장을 주지 않습니다). 이는 Workers for Platforms 에서 작동합니다(각 사용자 Worker는 디스패처 및 다른 테넌트로부터 격리된 자체 캐시를 가집니다). 이전에는 이들 모두가 캐싱에 대해 2등이었습니다. 이제는 그렇지 않습니다.

  • 제거 범위는 Worker의 진입점으로 지정됩니다. ctx.cache.purge({ purgeEverything: true })를 호출하면, Worker 진입점의 캐시만 제거됩니다. 영역의 다른 콘텐츠를 누크화할 위험이 없습니다. 한 Worker의 배포가 다른 Worker의 데이터를 무효화할 위험이 없습니다.

캐싱 관련 설정은 모두 코드로 구현합니다. 즉, 어떤 경로에 더 긴 TTL을 적용할지(경로에 따라 분기하여 서로 다른 max-age 설정), 어떤 요청이 캐시를 우회하게 할지(Cache-Control: private 반환), 그리고 캐시 키를 어떻게 구성할지(ctx.props에 포함될 내용 제어, 요청 전달 전 게이트웨이 Worker에서 URL 정규화 등)를 코드로 정의하는 것입니다. 이미 작성한 Worker는 구성 표면입니다.

전체 문서에서 Workers Cache: Worker의 캐시에 대해 자세히 설명합니다.

두 계층, 모든 Worker, 구성 없음

Workers Cache는 기본적으로 지역에 따라 계층화됩니다. 두 개의 계층이 있습니다.

  • 사용자에게 가장 가까운 Cloudflare 데이터 센터의 더 낮은 계층. Worker로 향하는 트래픽을 수신하는 모든 데이터 센터에는 자체 하위 계층 캐시가 있습니다.

  • 전체 네트워크에서 필을 집계하는상위 계층. 이러한 수가 적으며, 모든 하위 계층에서 누락되면 상위 계층에 문의합니다.

요청이 하위 계층에 먼저 도달합니다. 적중 시 응답이 제공되고 이것으로 끝입니다. 실패하면 하위 계층에서 상위 계층에 요청합니다. 거기에서 적중이 발생하면 응답이 반환되며, 돌아오는 도중에 하위 계층에도 저장됩니다. 두 계층 모두가 누락된 경우에만 Worker가 실제로 실행되고 해당 실행의 응답이 두 계층에 모두 저장됩니다.

BLOG-3262 image2

이것이 중요한 이유는 전 세계에서 첫 번째 요청 이 상위 계층을 채우기 때문입니다. 어떤 데이터 센터의 모든 후속 요청은 Worker를 실행하지 않고도 상위 계층에서 처리될 수 있습니다. 이는 해당 데이터 센터의 하위 계층에서 이전에 요청을 본 적이 없더라도 마찬가지입니다. 캐시 적중률은 단일 플랫 캐시 계층을 사용할 때보다 극적으로 높으며, 이는 Worker가 오리진일 때 원하는 것과 정확히 같습니다.

이는 구성하지 않는다는 점을 제외하면 현재 영역용 계층형 캐시 를 구동하는 토폴로지와 동일합니다. "Worker에 계층형 캐시 켜기" 대화 상자가 없습니다. 캐싱이 활성화된 모든 Worker는 무료로 계층화됩니다.

Worker가 Smart Placement를 사용하는 경우, 캐시가 이와 깔끔하게 구성됩니다. 계층이 먼저 참조되며, 두 계층 모두 누락된 경우에만 Smart Placement가 원본에 가깝게 실행을 라우팅합니다. 매끄럽게 만들 계획인 몇 가지 거친 가장자리를 포함하여 이러한 레이어가 상호 작용하는 방식에 대해 할 말이 더 많습니다.

사용자 데이터 근처에서 앱 실행

웹 성능에는 반복되는 문제가 있는데, 이 문제를 누구도 완전히 해결할 수 없었습니다. 코드가 사용자 가까이에서 실행되고(사용자와 서버 간의 왕복이 주요 경로에 있으므로), 코드가 사용자와 가까운 곳에서 실행되고 (모든 데이터베이스 쿼리도 왕복이므로) 하나를 선택하면, 다른 하나는 느려집니다.

우리는 몇 년 동안 이 두 가지 모두를 추구했습니다. Cloudflare 네트워크는 전 세계 인터넷 사용자의 약 95%로부터 50ms 이내 에 위치합니다. Smart PlacementPlacement Hints를 사용하면 클라우드 지역에 대해 고민할 필요 없이 코드를 데이터 가까이에 배치할 수 있습니다. 그러나 지금까지는 두 조각이 완전히 구성되지 않았습니다. 사용자 근처 또는 데이터 근처에서 처리하는 방식을 선택할 수 있었지만, 애플리케이션의 두 구성 요소 모두를 동시에 적절한 위치에 배치하려면 Cloudflare 전문가가 되어야 했습니다. 더 잘할 수 있다는 걸 알고 있었죠.

Workers Cache는 이 격차를 메우는 역할을 합니다. 캐시가 영역이 아닌 Worker에 속하고, Workers 간의 서비스 바인딩ctx.exports 호출이 캐시를 통과하기 때문에, 캐시를 이음새로 하여 각 Worker가 실행되어야 할 곳에서 실행되는 Worker 체인으로 앱을 구축할 수 있습니다.

아키텍처의 모습은 다음과 같습니다.

BLOG-3262 image8
  • Worker A가 사용자 근처에서 달립니다. 인증, 레이트 리미팅, 라우팅, 헤더 정규화, 데이터에 의존하지 않는 HTML 페이지의 외부 "셸" 렌더링 등 모든 요청에서 저렴하고 대기 시간에 민감한 부분을 처리합니다.

  • Worker B는 Smart Placement 또는 명시적 배치 힌트를 통해 데이터 근처에서 실행됩니다. 데이터를 가져오는 서버 렌더링 페이지, 제품 카탈로그 읽기, 검색 결과 생성, API 집계, 값비싼 변환 등 힘든 작업을 수행합니다.

  • Workers Cache는 Worker B 앞에 위치합니다. Worker A가 서비스 바인딩을 통해 Worker B를 호출하면 Cloudflare는 Worker B의 캐시를 먼저 확인합니다. 적중이 발생하면 Worker A는 응답을 받지만 Worker B는 전혀 실행되지 않습니다. 데이터 센터 호핑, 데이터베이스 쿼리, 렌더링 작업이 필요하지 않습니다.

캐시 적중 경로는 다음과 같습니다. 사용자 → Worker A 근처에 있는 사용자 → Worker B에 대한 캐시 적중 → 응답. 데이터 홉은 누락된 경우에만 지급됩니다. 핫 페이지는 사용자 전면 코드의 속도로 실행되고 콜드 페이지는 실행될 때 데이터 근처에서 실행되므로 여전히 이점이 있습니다.

이를 구현하기 위해 특별한 아키텍처를 구축할 필요는 없습니다. 앱을 두 개의 Worker로 작성하고, 서비스 바인딩으로 하나가 다른 하나를 가리키고, Worker B의 wrangler.jsonc 파일에서 캐싱을 켜면 완료됩니다.

BLOG-3262 image7

기본적으로 ctx.props를 사용하는 다중 테넌트

사용자별 데이터를 반환하는 Worker를 캐싱하는 경우(예: 로그인한 사용자마다 다른 콘텐츠를 제공하는 API) 한 사용자가 다른 사용자의 캐시된 응답을 절대 볼 수 없도록 하는 방법이 필요합니다. 표준 솔루션은 "인증된 요청을 캐시하지 않음"이며, Cloudflare의 자동 우회 는 Authorization 헤더에 대해 정확히 이를 수행합니다. 그러나 "아무 것도 캐시하지 않음"은 전체 성능 향상을 포기합니다.

Workers Cache는 호출자의 ctx.props를 캐시 키의 일부로 만들어 이 문제를 해결합니다. 한 Worker가 서비스 바인딩을 통해 다른 Worker를 호출하고 사용자 ID, 테넌트 ID, 기타 식별자와 함께 ctx.props를 전달하면 다른 props를 가진 호출자는 별도의 캐시 항목을 갖게 됩니다. 한 사용자의 응답이 다른 사용자의 캐시로 유출되면 안 됩니다.

import { WorkerEntrypoint } from "cloudflare:workers";

interface Props { userId: string; }

export default class Backend extends WorkerEntrypoint<Env, Props> {
  async fetch(request: Request): Promise<Response> {
    // ctx.props.userId is part of the cache key. User A and User B
    // requesting the same URL get separate cached entries.
    const { userId } = this.ctx.props;
    const data = await loadUserData(userId);

    return new Response(JSON.stringify(data), {
      headers: {
        "Content-Type": "application/json",
        "Cache-Control": "public, max-age=300",
      },
    });
  }
}

일반적인 패턴은 게이트웨이 Worker에서 요청을 인증하고, Authorization 헤더를 제거하고, 인증된 사용자의 ID를 ctx.props에 설정하는 것입니다. 캐시된 백엔드 Worker를 호출합니다. 게이트웨이는 모든 요청(인증을 위해)마다 실행되지만, 백엔드 비용은 해당 사용자에 대한 캐시 항목이 아직 없을 때만 실행되는 것입니다. 인증된 APIs가 '캐시할 수 없음'에서 '사용자별로 안전하게 캐시됨'으로 바뀌면 캐시 키로 격리가 수행됩니다. 해당 문서에서는 ctx.props를 사용한 다중 테넌트 안전 및 사용자별 인증 응답의 예에서 이에 대해 자세히 설명합니다.

다른 CDN은 정확성과 적중률 사이에서 선택하게 합니다. 각 사용자의 토큰으로 캐시를 입력하거나, 모든 요청을 원본으로 보내 승인을 받습니다. Workers Cache를 사용하면 요청별 인증 경계를 보존하는 동시에 에지에서 캐시된 API 응답을 공유할 수 있습니다. 인증된 다중 테넌트 API의 기본 제공 모델로 이를 제공하는 CDN은 또 없습니다. 저희는 이를 매우 자랑스럽게 생각합니다.

모든 Worker 진입점 사이의 캐시

다음은 Workers Cache에서 가장 큰 잠금 해제 기능이라고 생각하는 부분이며, "Workers 앞에서 작동하는 CDN 캐시"라고 생각하면 가장 확인하기 어려운 부분입니다.

Workers Cache는 모든 Worker 진입점 앞에 위치합니다 — 기본 내보내기, 모든 명명된 WorkerEntrypoint, 그리고 ctx.exports를 통한 동일한 Worker 내 진입점 간의 모든 호출. 마지막 절은 여러분이 구축할 수 있는 것을 변경하는 절입니다.

한 진입점에서 ctx.exports를 통해 다른 진입점을 호출하면 캐시는 브라우저의 요청을 평가하는 것과 동일한 방식으로 해당 호출을 평가합니다. 적중은 캐시된 응답을 반환하고 수신자는 실행되지 않습니다. 누락은 수신자를 실행하고 수신자의 진입점, 경로, 쿼리 문자열, ctx.props로 구성된 자체 캐시 키 아래에 응답을 저장합니다. 호출자는 여전히 모든 요청에 대해 실행되지만, 수신자에게 전달되는 모든 내용은 독립적으로 메모이화됩니다.

진입점별로 캐시할 항목을 결정합니다. Wrangler 구성에서 내보내기 맵을 사용하면 이름별로 각 진입점에 대해 캐싱을 켜거나 끌 수 있습니다("기본값" 이 기본 내보내기). in에 있는 진입점을 선택하면 해당 진입점에서 생성된 응답이 캐시에 저장되며, out에 있는 진입점을 선택하면 모든 요청에 대해 해당 진입점이 계속 실행됩니다. 게이트웨이나 라우터 진입점(인증, 정규화, 전송하는 모든 작업)은 항상 실행되고 자체 출력이 캐시에서 제공되지 않도록 반드시 제외해야 합니다.

이렇게 하면 구성할 수 있는 기본 요소가 생깁니다. 인증, 정규화, 라우팅, 비용이 많이 드는 읽기 작업, 데이터 계층 등 일련의 작은 진입점들로 워커(Worker)를 구성하고, 원하는 위치에 Workers Cache를 적용할 수 있습니다. 각 캐시된 진입점은 자체 키, 자체 Time To Live, 제거를 위한 자체 태그 네임스페이스를 가진 메모이제이션의 단위입니다. 캐싱과 관련하여 설정하고 싶은 모든 사항(실행 시점, 키(key) 기준, 무효화 시점 등)은 일반적인 Worker 코드로 구현됩니다. 즉, 어떤 진입점(entrypoint)을 호출할지, 어떤 요청을 전달할지, 어떤 ctx.props를 넘길지, 그리고 어떤 Cache-Control을 설정할지 등을 코드로 정의하는 것입니다.

이를 구체적으로 설명하기 위해, 여기 하나의 Worker를 통해 다른 플랫폼에서는 쉽게 함께 수행할 수 없는 세 가지 작업을 수행할 수 있습니다. 모든 요청을 인증하고, 다중 테넌트 안전 캐시 키 뒤에 있는 값비싼 백엔드를 캐시하며, 데이터가 변경될 때 해당 캐시를 무효화합니다.

캐싱은 진입점별로 구성됩니다. 인증을 위해서도, 그리고 캐시된 게이트웨이 응답이 인증 확인을 건너뛰기 때문에 모든 요청에서 게이트웨이를 실행해야 합니다. 따라서 기본 진입점에서는 캐싱을 비활성화하고 내부 진입점에서만 캐싱을 활성화합니다.

{
  "name": "my-worker",
  "main": "src/index.ts",
  "compatibility_date": "2026-05-01",
  "cache": { "enabled": true },
  "exports": {
    // The gateway runs on every request — don't cache it.
    "default": { "type": "worker", "cache": { "enabled": false } },
    // Cache the expensive inner entrypoint.
    "CachedBackend": { "type": "worker", "cache": { "enabled": true } }
  }
}
import { WorkerEntrypoint } from "cloudflare:workers";

interface Env { API_TOKEN: string; }
interface Props { userId: string; }

// Inner entrypoint: the expensive work. Workers Cache sits in front
// of this — on a hit, this code never runs.
export class CachedBackend extends WorkerEntrypoint<Env, Props> {
  async fetch(request: Request): Promise<Response> {
    // ctx.props.userId is part of the cache key, so this is cached
    // separately for every user.
    const { userId } = this.ctx.props;
    const data = await loadExpensiveData(userId);

    return new Response(JSON.stringify(data), {
      headers: {
        "Content-Type": "application/json",
        "Cache-Control": "public, max-age=300, stale-while-revalidate=3600",
        "Cache-Tag": `user:${userId}`,
      },
    });
  }

  // Invalidate a user's cached response. purge() is scoped to the
  // entrypoint that calls it, so it must run inside CachedBackend —
  // the entrypoint that owns the cached response.
  async invalidate(userId: string): Promise<void> {
    await this.ctx.cache.purge({ tags: [`user:${userId}`] });
  }
}

// Outer entrypoint: runs on every request to authenticate and route.
// Caching is disabled for it in Wrangler config (above), so it always
// runs and the auth check is never skipped by a cache hit.
export default {
  async fetch(request, env, ctx): Promise<Response> {
    const userId = await authenticate(request, env);
    if (!userId) return new Response("Unauthorized", { status: 401 });

    // Invalidate this user's cache on writes, from the entrypoint that
    // owns it.
    if (request.method === "POST") {
      await handleWrite(request, userId);
      await ctx.exports.CachedBackend.invalidate(userId);
      return new Response("OK");
    }

    // For reads: strip Authorization (otherwise Cloudflare's automatic
    // bypass fires and nothing caches), then dispatch to the cached
    // backend with the authenticated user's identity in ctx.props.
    const forwarded = new Request(request);
    forwarded.headers.delete("Authorization");

    return ctx.exports.CachedBackend.fetch(forwarded, {
      props: { userId },
    });
  },
} satisfies ExportedHandler<Env>;

모든 것이 하나의 Worker입니다. 하나의 소스 파일. 한 번에 배포합니다. 그러나 두 가지 실행 단계가 있습니다. 하나의 작은 exports 블록에서 게이트웨이에 대한 캐싱은 꺼져 있고 백엔드에 대한 캐싱은 켜져 있습니다. 그리고 그들 사이에는 사용자별로 키가 지정되고, 쓰기 경로에 의해 무효화되며, 백그라운드 새로 고침 중에 오래된 데이터를 제공하는 캐시기 있습니다. 캐시 단계는 여러분이 고정한 것이 아닙니다. 이는 코드로 작성된 프로그램의 계층입니다.

이로 인해 형성되는 패턴은 끝이 없습니다. 동일한 형태가 다음과 같은 경우에도 효과가 있습니다.

  • Durable Object 캐싱. Durable Object를 진입점 뒤에 래핑하고, 응답에 Cache-Control을 설정하며, 조회 시 Durable Object에 더 이상 접근하지 않습니다. 쓰기 작업은 DO로 직접 이동하여 태그를 통해 캐시를 제거합니다. DO는 캐싱이 발생한다는 사실을 인식하지 못합니다.

  • 정규화 수락-인코딩Vary. 외부 진입점은 request.cf.clientAcceptEncoding 에서 원래 인코딩을 복원하고(Cloudflare의 최전선에서는 캐시 효율성을 위해 이를 정규화합니다), 실제 값에 따라 변화하는 캐시된 진입점으로 전달합니다. 높은 적중률 유지. 적절한 인코딩을 얻을 수 있습니다.

  • 캐싱 전에 추적 매개변수를 제거합니다. 외부 진입점은 URL을 정규화하거나 ctx.exports 호출에서 cf.cacheKey사용하여 사용자 지정 캐시 키 를 설정합니다. 따라서 캐시된 내부 진입점은 정규 형식만 보게 되며,?utm_source=anything 은 단일 캐시 항목으로 축소됩니다.

쌓아두세요. 단일 Worker는 인증하고 라우팅하는 외부 진입점, 추적 매개변수를 제거하고 인코딩 헤더를 복원하는 정규화 진입점, Durable Object 앞에 있는 캐시된 진입점, 인증되지 않은 공개 API를 위한 별도의 캐시된 진입점을 가질 수 있으며, 각각은 캐시로 연결되어 있습니다 구성하지 않았고, 배치할 위치만 결정했습니다. 문서의 예제 페이지 에서 이러한 몇 가지 엔드투엔드 예시를 자세히 설명합니다.

이 작업을 수행할 수 있는 다른 플랫폼은 없습니다. CDN 캐시는 원본 앞에 위치합니다. 함수 플랫폼은 함수를 실행합니다. 우리는 애플리케이션의 각 부분 사이에 있는 배포 가능한 단일 장치 내부에 위치하는 캐시를 제공하는 또 다른 플랫폼을 알지 못합니다. 각 캐시 단계는 그 양쪽의 코드로 구성됩니다. 이것이 바로 Workers Cache입니다. 또한 Smart Placement, Durable Objects, 서비스 바인딩, ctx.props, ctx.exports 등 플랫폼에서 이미 제공하는 다른 모든 기능과 결합되므로 구축할 수 있는 패턴은 무궁무진합니다. 이 게시물에서는 아주 표면적인 부분만 다루었습니다.

프레임워크 내 최고의 지원

Astro로 구축하는 경우, Cloudflare 어댑터가 Workers Cache를 연결합니다. 구성 파일에 cacheCloudflare 공급자를 추가하기만 하면 됩니다.

// astro.config.mjs
import { defineConfig } from "astro/config";
import cloudflare from "@astrojs/cloudflare";
import { cacheCloudflare } from "@astrojs/cloudflare/cache";

export default defineConfig({
  adapter: cloudflare(),
  output: "server",
  experimental: {
    cache: { provider: cacheCloudflare() },
    routeRules: {
      "/products/*": { maxAge: 300, swr: 3600, tags: ["products"] },
      "/blog/*":     { maxAge: 60,  swr: 86400, tags: ["blog"] },
    },
  },
});

이 어댑터는 캐시를 활성화하고, Astro가 생성한 응답에서 올바른 헤더를 설정하며, 무효화를 위해 Cache-Tag 값을 첨부하고, cache.invalidate() 를 제공합니다. 콘텐츠 변경 시 태그를 제거하기 위한 도우미. 서버 렌더링을 선택한 Astro 페이지에는 위에서 설명한 "한 번 렌더링하고, 캐시하고, 백그라운드에서 새로 고침"하는 흐름이 자동으로 적용되므로, 경로당 구성이 필요하지 않고, 프레임워크별 런타임 계층을 학습할 필요가 없습니다.

우리는 동일한 통합을 제공하기 위해 다른 프레임워크의 유지 관리자와 협력하고 있습니다. Cloudflare용 프레임워크 어댑터를 구축하는 경우 여러분이 원하는 헤더 기반 구성, 프로그래밍 방식 제거, 모델링의 플랫폼별 개념이 없는 Workers 캐시 APIs를 제공합니다.

Worker와 동일한 대시보드에서 캐시를 확인하세요

캐싱은 무엇을 하고 있는지 볼 수 있는 경우에만 유용합니다. 이제 Workers Observability 대시보드 에 호출당 캐시 적중 정보가 표시됩니다.

BLOG-3262 image6

Worker별로 다음을 확인할 수 있습니다.

  • 시간 경과에 따른 캐시 적중률. 캐싱을 활성화한 후 추세를 높이려는 수치.

  • 히트, 누락, 업데이트, 우회를 자세히 분석했습니다. 적중률이 낮은 경우, 여기에서 그 이유를 확인할 수 있습니다. 너무 많은 BYPASS 응답(쿠키를 설정하고 있기 때문인가요?), 너무 많은 MISS 응답(캐시 키가 생각보다 많이 분할되고 있기 때문인가요?), 너무 많은 UPDATING 응답(max-age가 트래픽 간격보다 짧기 때문인가요?).

이 모든 것은 Worker의 다른 로그, 예외, CPU 시간, 요청 수 등 관찰 가능성과 동일한 대시보드에서 모두 반영되므로, 어떤 일이 일어나고 있는지 이해하기 위해 영역과 Worker 사이를 전환하며 컨텍스트를 전환할 필요가 없습니다.

요금 청구

캐시 적중은 Worker를 실행하지 않으며 CPU 시간을 청구하지 않습니다. 다른 호출과 마찬가지로 표준 Workers 요청률의 요청으로 간주됩니다. 캐시 누락 및 바이패스는 캐싱 없이도 요청 + CPU 시간으로 정상적으로 청구됩니다.

결과

요금 요청

CPU 시간 요금

캐시 HIT (Worker가 실행되지 않음)

표준 요율

청구되지 않음

캐시 MISS (Worker 실행)

표준 요율

청구

캐시 BYPASS (Worker 실행)

표준 요율

청구

정적 자산 요청

표준 요율

청구되지 않음

Worker 간 호출

표준 요율

Worker 실행 시 요금 청구

별도의 Workers Cache SKU가 없으며 GB당 캐시 스토리지 요금도 없습니다. 계층형 캐싱, 제거, stale-while-revalidate, 위에서 설명한 분석이 모두 포함됩니다.  요청이 Worker를 실행하고 Workers Cache가 대신 이를 적중으로 제공하는 경우에도 표준 요청 요율을 지불하지만 해당 요청에 대한 CPU 시간은 지불하지 않습니다. 따라서 해당 캐시 적중 비용은 Worker에서 동일한 응답을 렌더링하는 것보다 저렴합니다.

한 가지 주의할 점은, 캐싱이 활성화되면 정적 자산 요청, 서비스 바인딩 또는 ctx.exports통한 작업자 간 호출과 같이 일반적으로 무료인 요청에는 표준 요청 요율로 청구된다는 것입니다. Worker 전면에서 지원합니다.

다음 단계

다음에 하고 싶은 일:

  • Smart Placement로 더 스마트한 코로케이션. 현재 Cloudflare는 상위 계층 캐시와 Smart Placement 대상을 별도로 선택합니다. 완전히 누락된 경우에는 요청이 두 번 Cloudflare 로케이션 간에 이동할 수 있습니다. 한 번은 상위 계층을 확인하기 위한 것이며, 다시 한 번 데이터 근처에서 Worker를 실행하기 위한 것입니다. 선택 사항을 조정하기 위해 노력하고 있으므로 장거리 여행은 한 번만 진행됩니다.

  • 응답 크기 제한이 커졌습니다. 실행 시, 모든 응답은 계정 유형과 관계없이 Free 요금제의 캐시 가능한 크기 제한 (512MB)을 따릅니다. 이는 일시적이며, Cloudflare에서 몇 가지 롤아웃 단계를 완료하면 요금제별 표준 캐시 제한이 적용됩니다.

  • 더 많은 프레임워크 통합. Astro에는 Workers Cache와의 기본 통합 기능이 있습니다. 저희는 TanStack StartVinext를 통한 Next.js를 포함한 다른 프레임워크에 유사한 통합 기능을 추가하기 위해 관리자들과 협력하고 있습니다.

  • 캐시된 응답을 오래된 것으로 표시하는 API. ctx.cache.purge() 캐시에서 일치하는 응답을 제거합니다. 저희는 ctx.cache.invalidate()를 보고 있습니다. 일치하는 응답이 만료된 것처럼 작동하게 만드는 API이므로, 다음 요청은 Worker가 백그라운드에서 캐시를 새로 고치는 동안 stale-while-revalidate를 통해 여전히 빠른 부실 응답을 받을 수 있습니다.

체험하기

Workers Cache는 오늘부터 모든 요금제를 이용하는 모든 Worker에게 제공됩니다.

시작하려면 "cache": { "enabled": true }wrangler.jsonc에 추가하고, 다시 배포하고 Cache-Control 헤더 설정을 시작하세요. Workers Cache 문서 에서는 빠른 시작, 캐시 키, 제거, 구성 패턴 및 예시, 디버깅을 포함한 전체 기능을 안내합니다.

과거에는 Workers가 캐시 앞에서 실행되었습니다. 이제 이들은 그 뒤에서 활동할 수도 있습니다. 필요한 측면을 사용하거나 서비스 바인딩의 경우 두 측면 모두를 동시에 사용할 수 있습니다.

여러분이 무엇을 구축해나갈지 정말 기대됩니다.

Cloudflare에서는 전체 기업 네트워크를 보호하고, 고객이 인터넷 규모의 애플리케이션을 효과적으로 구축하도록 지원하며, 웹 사이트와 인터넷 애플리케이션을 가속화하고, DDoS 공격을 막으며, 해커를 막고, Zero Trust로 향하는 고객의 여정을 지원합니다.

어떤 장치로든 1.1.1.1에 방문해 인터넷을 더 빠르고 안전하게 만들어 주는 Cloudflare의 무료 애플리케이션을 사용해 보세요.

더 나은 인터넷을 만들기 위한 Cloudflare의 사명을 자세히 알아보려면 여기에서 시작하세요. 새로운 커리어 경로를 찾고 있다면 채용 공고를 확인해 보세요.
Cloudflare Workers캐시Tiered Cache성능개발자서버리스

X에서 팔로우하기

Cloudflare|@cloudflare

관련 게시물