INTEGRITY Документация

Ключи кеша

Каждый кешированный ответ сохраняется под ключ кеша. Когда поступает запрос, Cloudflare вычисляет для него ключ кеша и выполняет поиск по нему: при совпадении возвращается сохранённый ответ, при отсутствии совпадения запускается ваш Worker, и его ответ сохраняется под этим ключом для следующего раза.

Два запроса с одинаковым ключом кеша используют один и тот же кешированный ответ. Два запроса с разными ключами кеша получают независимые записи в кеше.

На этой странице объясняется, что Workers Caching включает в ключ кеша, зачем нужен каждый компонент и как учитывать это при проектировании вашего Worker.

Что входит в ключ кеша

Workers Caching кеширует ответы по следующим ключам:

В качестве меры защиты от отравления кеша ключ также включает:

Об этих трёх пунктах обычно не нужно задумываться. Некоторые фреймворки интерпретируют заголовки переопределения метода и перезаписи URL как замену фактического метода или URL запроса, что может привести к отравление кеша если два запроса отличаются только этими заголовками, но дают существенно разные ответы. Включение их в ключ кеша гарантирует, что отравленная запись затронет только запросы с тем же отравленным заголовком.

Запросы, которые отличаются только заголовками, не входящими в ключ кеша (например, User-Agent, Accept-Language, Cookie, или Authorization) возвращают один и тот же закешированный ответ. Обычно это и требуется: не нужно, чтобы каждая строка user agent или языковое предпочтение создавали отдельную запись в кеше. Если согласование содержимого всё же нужно, задайте Vary к ответу, либо обработать его внутри своего Worker и формировать канонический ответ для каждого URL.

Примечательно, что ключ кеша не включают:

На момент запуска нельзя посмотреть точный ключ кеша, который Cloudflare вычислил для запроса. Основными сигналами для понимания поведения кеша служат Cf-Cache-Status заголовок ответа и информацию о попаданиях в кеш для каждого вызова в Панель наблюдаемости Workers. См. Просмотр ключа кеша.

Кэш принадлежит Worker, а не домену

Worker представляет собой сущность без зоны. Его можно вызвать несколькими различными способами:

Workers Caching считает все это одним и тем же Worker и использует для них общий кеш. Ключ кеша не включает хост, поэтому запрос к /api/users/42 обращается к одной и той же записи кеша независимо от того, поступил ли он через api.example.com, api.example.net, service binding или workers.dev URL.

Именно такое поведение нужно почти всегда. Ответы Worker зависят от его кода и входных данных, а не от того, на какой домен пришел запрос, поэтому кеширование ответа один раз и последующая отдача его по всем путям входа максимизирует долю попаданий в кеш, не нарушая корректность.

Если вам действительно нужны разные закешированные ответы для одного и того же пути на разных хостах (например, для клиентов на white label, где tenant-a.example.com/index и tenant-b.example.com/index должны выдавать разный контент: сам по себе ключ кеша этого не обеспечивает. Вместо этого различайте арендаторов на уровне вашего gateway Worker и передавайте идентификатор арендатора через ctx.props, который является часть ключа кеша.

Аннулирование кэша между развёртываниями

По умолчанию текущая вызываемая версия Worker является часть ключа кеша. У каждой развёрнутой версии свой кеш, поэтому:

Это поведение используется по умолчанию, потому что его проще всего понять. Недостаток в том, что коэффициент попаданий в кеш сбрасывается при каждом развёртывании : первые запросы к новой версии не попадают в кеш, пока он заполняется. Это самая частая причина падения доли попаданий в кеш Worker сразу после деплоя.

Общий доступ к кэшу между версиями

Если вы часто выполняете развертывание, а ответы между развертываниями почти не меняются, сбрасывать прогретый кеш при каждом развертывании нерационально. Задайте cache.cross_version_cache к true чтобы исключить версию из ключа кеша и использовать общие кешированные ответы для всех версий. Ответ, записанный версией A, будет по-прежнему отдаваться после развертывания версии B, пока не истечёт его TTL.

Это максимизирует долю попаданий в кеш ценой более медленного развертывания изменений: поскольку деплой больше не сбрасывает кеш, изменение, влияющее на содержимое ответа, не применится к уже закешированным записям, пока они не истекут или вы не очистите их вручную. Если у вас cross_version_cache включено, и вам нужно, чтобы деплой вступил в силу немедленно, используйте один из двух инструментов ниже.

Помечайте ответы версией и очищайте тег при откате

Если вам нужен точечный контроль, помечайте каждый кэшированный ответ версией Worker, которая его создала. Позже очистка кэша по этой метке версии удалит все записи, созданные этой версией, не затронув кэшированные ответы других версий.

Здесь используется привязка метаданных версии чтобы прочитать текущий идентификатор версии во время запроса и добавить его в начале в качестве Cache-Tag значение. См. Очистка кэша для конкретной версии для полного шаблона с кодом.

Это лучший вариант, если у вас включен cross_version_cache и вам может потребоваться откатиться к определённой версии, не уничтожая кешированное содержимое рабочих версий.

Полная очистка кеша после развёртывания

Более простой подход: после каждого деплоя отправляйте запрос из CI на небольшой эндпоинт Worker, который вызывает ctx.cache.purge({ purgeEverything: true }). Следующий запрос после очистки кэша заново наполняет кэш той версией Worker, которая на тот момент опубликована.

Этот способ грубее, но не требует логики внутри Worker. Используйте его, если у вас включен cross_version_cache но при этом хотите, чтобы отдельные развертывания сбрасывали кеш. При использовании кеша по умолчанию для каждой версии развертывания и так начинаются с холодного кеша, поэтому в этом нет необходимости.

Безопасность мультиарендной среды с ctx.props

Когда ваш Worker вызывается через привязка к сервису или RPC, вызывающей стороны ctx.props является частью ключа кеша. Два вызывающих объекта, обращающихся к вашему Worker с разными ctx.props получить отдельные записи кэша : один вызывающий никогда не получит закешированный ответ другого вызывающего.

Именно этот механизм обеспечивает безопасность кеширования для мультитенантных Workers, вызываемых через service binding. Если вы используете ctx.props чтобы передавать контекст авторизации конкретного вызывающего: ID пользователя, ID тенанта, организацию, роль. В этом случае кеширование по умолчанию безопасно: ответы, логически относящиеся к одному вызывающему, не могут через кеш попасть к другому.

src/backend.js
import { WorkerEntrypoint } from "cloudflare:workers";

export default class Backend extends WorkerEntrypoint {
	async fetch(request) {
		// ctx.props.userId is set by the caller (for example, an auth gateway).
		// Because it is part of the cache key, User A and User B requesting the
		// same URL get separate cache entries — there is no way for one to
		// see the other's response.
		const { userId } = this.ctx.props;
		const data = { userId, timestamp: Date.now() };

		return new Response(JSON.stringify(data), {
			headers: {
				"Content-Type": "application/json",
				"Cache-Control": "public, max-age=300",
			},
		});
	}
}
src/backend.ts
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 set by the caller (for example, an auth gateway).
		// Because it is part of the cache key, User A and User B requesting the
		// same URL get separate cache entries — there is no way for one to
		// see the other's response.
		const { userId } = this.ctx.props;
		const data = { userId, timestamp: Date.now() };

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

URL для Service binding

Вызовы Service binding заслуживают отдельного замечания, поскольку передаваемый URL означает не то, что вы могли бы подумать.

При вызове service binding с fetch(), хост в URL это лишь заглушка. Запрос маршрутизируется через привязку, а не через DNS, поэтому хост никогда не разрешается. А поскольку хост не входит в ключ кеша (как описано в Кэш принадлежит Worker, а не домену), заполнитель также не влияет на кеширование. Только путь (и строка запроса) участвуют в формировании ключа кеша наряду с целевой точкой входа и ctx.props:

src/gateway.js
export default {
	async fetch(request, env, ctx) {
		// "internal" here is just a placeholder — it is not routed anywhere
		// and is not part of the cache key.
		//
		// What identifies this cached response is:
		//   - the BACKEND entrypoint
		//   - the path "/api/users/42"
		//   - whatever ctx.props the gateway passes along
		return env.BACKEND.fetch("http://internal/api/users/42");
	},
};
src/gateway.ts
interface Env {
	BACKEND: Fetcher;
}

export default {
	async fetch(request, env, ctx): Promise<Response> {
		// "internal" here is just a placeholder — it is not routed anywhere
		// and is not part of the cache key.
		//
		// What identifies this cached response is:
		//   - the BACKEND entrypoint
		//   - the path "/api/users/42"
		//   - whatever ctx.props the gateway passes along
		return env.BACKEND.fetch("http://internal/api/users/42");
	},
} satisfies ExportedHandler<Env>;

Если вы хотите, чтобы кэшированные ответы отличались для разных вызывающих сторон, варьируйте ctx.props. Если нужно, чтобы они различались в зависимости от запроса, варьируйте путь или строку запроса. Изменение имени хоста ни на что не влияет.

Просмотр ключа кеша

На момент запуска о поведении кеша можно судить по двум сигналам:

  1. Cf-Cache-Status заголовок ответа. Чаще всего вы будете видеть значения HIT, MISS, EXPIRED, REVALIDATED, UPDATING, STALE, а также BYPASS. HIT означает, что Cloudflare вернул закешированный ответ, не запуская ваш Worker. MISS означает, что ваш Worker выполнился и ответ был сохранён. UPDATING означает, что закешированный ответ устарел, и ваш Worker выполнялся в фоновом режиме, чтобы обновить его. BYPASS означает, что кеширование для этого запроса было отключено. См. Кеширование ответов Cloudflare для полного набора значений.

  2. Попадания в кеш в Панель наблюдаемости Workers. Каждый вызов показывает, был ли ответ получен из кеша, поэтому вы можете фильтровать и агрегировать данные о попаданиях в кеш по всему трафику вашего Worker.

Cloudflare пока не раскрывает саму структуру ключа кэша. Если два запроса, которые должны были использовать общий кэшированный ответ, этого не делают, вам придётся определить, какая часть ключа отличалась, ориентируясь на компоненты, перечисленные в Что входит в ключ кеша. Обзор типичных проблем кэширования и способов их диагностики см. в Отладка.

Custom cache keys

По умолчанию путь и строка запроса URL-адреса запроса формируют компонент URL в ключе кеша. Когда одна точка входа вызывает другую кешируемую точку входа через ctx.exports loopback, вызывающая точка входа может переопределить этот компонент, задав cf.cacheKey к запросу.

В примере ниже Backend точка входа является кешированной. Точка входа по умолчанию перенаправляет запросы к ней через ctx.exports, выбрав сам ключ кэша:

src/index.js
import { WorkerEntrypoint } from "cloudflare:workers";

// Cached entrypoint. Requests routed here through ctx.exports are served
// from cache when possible.
export class Backend extends WorkerEntrypoint {
	async fetch(request) {
		return new Response("Hello from the backend", {
			headers: {
				"Content-Type": "text/html",
				"Cache-Control": "public, max-age=3600",
			},
		});
	}
}

// Gateway entrypoint. Calls the cached Backend entrypoint via ctx.exports,
// which routes through the cache, and chooses the cache key for the call.
export default {
	async fetch(request, env, ctx) {
		const url = new URL(request.url);

		// Strip a tracking parameter so that requests differing only by
		// `utm_source` resolve to the same cached entry.
		url.searchParams.delete("utm_source");

		return ctx.exports.Backend.fetch(request, {
			cf: { cacheKey: url.pathname + url.search },
		});
	},
};
src/index.ts
import { WorkerEntrypoint } from "cloudflare:workers";

// Cached entrypoint. Requests routed here through ctx.exports are served
// from cache when possible.
export class Backend extends WorkerEntrypoint<Env> {
	async fetch(request: Request): Promise<Response> {
		return new Response("Hello from the backend", {
			headers: {
				"Content-Type": "text/html",
				"Cache-Control": "public, max-age=3600",
			},
		});
	}
}

// Gateway entrypoint. Calls the cached Backend entrypoint via ctx.exports,
// which routes through the cache, and chooses the cache key for the call.
export default {
	async fetch(request, env, ctx): Promise<Response> {
		const url = new URL(request.url);

		// Strip a tracking parameter so that requests differing only by
		// `utm_source` resolve to the same cached entry.
		url.searchParams.delete("utm_source");

		return ctx.exports.Backend.fetch(request, {
			cf: { cacheKey: url.pathname + url.search },
		});
	},
} satisfies ExportedHandler<Env>;

Пользовательский ключ кэша заменяет путь и строку запроса в ключе кеша. Всё остальное, описанное в Что входит в ключ кеша по-прежнему применяется:

Задайте cf.cacheKey в пустую строку либо не указывайте его, чтобы использовать ключ по умолчанию, производный от URL.

В этом шаблоне точка входа по умолчанию выступает шлюзом, который должен выполняться при каждом запросе, поэтому отключите для неё кеширование и оставьте его включённым для Backend (см. Кэширование для каждой точки входа):

{
	"name": "my-worker",
	"main": "src/index.ts",
	// Set this to today's date
	"compatibility_date": "2026-08-28",
	"cache": { "enabled": true },
	"exports": {
		"default": { "type": "worker", "cache": { "enabled": false } },
		"Backend": { "type": "worker", "cache": { "enabled": true } },
	},
}
name = "my-worker"
main = "src/index.ts"
# Set this to today's date
compatibility_date = "2026-08-28"

[cache]
enabled = true

[exports.default]
type = "worker"

  [exports.default.cache]
  enabled = false

[exports.Backend]
type = "worker"

  [exports.Backend.cache]
  enabled = true

Что можно сделать с помощью пользовательского ключа кеша

Для изоляции на уровне вызывающей стороны продолжайте использовать ctx.props а не кодирования личности вызывающей стороны в ключе кеша: ctx.props автоматически становится частью ключа, и это нельзя обойти.

Пользовательские ключи применяются только к вызовам в пределах одного аккаунта

cf.cacheKey учитывается только тогда, когда вызов остается в пределах вашего аккаунта. Cloudflare отбрасывает cf объект при каждом запросе, который пересекает границу аккаунта, например при service binding к Worker, принадлежащему другому аккаунту. В этом случае пользовательский ключ игнорируется, и ключ кеша возвращается к URL запроса, поэтому вызывающая сторона в одном аккаунте никогда не сможет повлиять на кеш Worker в другом аккаунте (или проверить его).

Это также означает cf.cacheKey не действует на запросы конечных пользователей. cf объект во входящем запросе от браузера или API клиента заполняется Cloudflare, а не клиентом, поэтому клиент не может задать собственный ключ кеша.