Как региональный кеш OpenNext снижает нагрузку на CPU Workers при каждом попадании в кеш
Next.js создан для работы на Vercel по умолчанию. Он ожидает файловую систему, службу оптимизации изображений и определенную модель среды выполнения. Чтобы запускать Next.js где-либо еще, например, в Cloudflare Workers, вам нужен адаптер, который связывает эти ожидания с примитивами целевой платформы.
OpenNext — это open-source проект, который делает именно это. Он начинался как адаптер Next.js для AWS Lambda, а теперь также предоставляет официальные адаптеры для Cloudflare и Netlify. Пакет, который нас здесь интересует, — это @opennextjs/cloudflare, который позволяет развернуть приложение Next.js в Cloudflare Workers и подключает слой кэширования Next.js к примитивам хранилища Cloudflare.
Если вы следовали рекомендуемой настройке Cloudflare от OpenNext, ваш файл open-next.config.ts связывает два из этих примитивов. Workers KV хранит кэшированное HTML-содержимое ваших предварительно отрисованных страниц. D1 хранит метаданные тегов кэша, которые записывает revalidateTag(). (По умолчанию defineCloudflareConfig() использует кэши "dummy", которые ничего не делают; KV и D1 являются рекомендуемыми заменами, которые вы настраиваете самостоятельно.)
И KV, и D1 являются удаленными службами. Обе потребляют реальное процессорное время при каждом запросе, даже если страница не менялась в течение нескольких дней. withRegionalCache — это обертка, которая решает эту проблему.
Что это такое
withRegionalCache поставляется с @opennextjs/cloudflare как переопределение, которое вы оборачиваете вокруг вашей существующей реализации инкрементального кэширования. Это изменение, требующее одного импорта и одной обертки в open-next.config.ts.
import { defineCloudflareConfig } from "@opennextjs/cloudflare";
import kvIncrementalCache from "@opennextjs/cloudflare/overrides/incremental-cache/kv-incremental-cache";
import { withRegionalCache } from "@opennextjs/cloudflare/overrides/incremental-cache/regional-cache";
import d1TagCache from "@opennextjs/cloudflare/overrides/tag-cache/d1-next-tag-cache";
export default defineCloudflareConfig({
incrementalCache: withRegionalCache(kvIncrementalCache, {
mode: "long-lived",
bypassTagCacheOnCacheHit: true,
}),
tagCache: d1TagCache,
});Он работает аналогично с r2IncrementalCache или staticAssetsIncrementalCache, если вы выбрали эти бэкенды. Обертка не зависит от используемого базового кэша.
Что он делает
Две вещи, обе из которых стоит понять.
1. Слой Cloudflare Cache API перед KV
Первое поведение — это слой регионального кэширования с использованием caches.default, Cloudflare Cache API. Cache API локален для каждого центра обработки данных Cloudflare. Он не реплицируется глобально, но в этом и заключается вся суть. Чтения из caches.default практически бесплатны с точки зрения ЦП и занимают от 1 до 5 мс.
Таким образом, когда поступает запрос на кэшированную страницу, worker сначала проверяет Cache API локального центра обработки данных. Если запись там есть, он возвращает ее немедленно. Если нет, он обращается к KV (глобальное чтение ~30–100 мс), заполняет локальный Cache API для следующего раза и возвращает ответ.
Опция mode: "long-lived" сохраняет региональные записи до 30 минут по умолчанию, что хорошо работает для страниц ISR/SSG.
2. Опциональный обход D1 tag-cache при попадании
Второе поведение является опциональным: bypassTagCacheOnCacheHit: true.
По умолчанию каждый запрос к кэшированной странице также обращается к D1, чтобы проверить, были ли аннулированы какие-либо теги перевалидации этой страницы. Чтения из D1 занимают ~30–200 мс реального времени и потребляют ЦП при настройке запроса и разборе результатов.
При включенном обходе worker пропускает этот полный цикл D1 при попадании в кэш. Компромисс: если вы вызываете revalidateTag(), региональный кэш может продолжать обслуживать предыдущую версию страницы до истечения срока действия региональной записи. Для контента, который не меняется поминутно, это незаметно для пользователей.
Как это помогает
Без обертки типичное "попадание в кэш" выглядит следующим образом в worker:
- Запуск изолята.
- Запрос к D1 для аннулирования тегов по этому URL.
- Чтение кэшированного HTML из KV.
- Сборка ответа и возврат.
Два удаленных обращения и значительная работа ЦП при каждом запросе.
С оберткой:
- Запуск изолята.
- Чтение из локального Cache API.
- Возврат.
Тот же результат с точки зрения пользователя. Значительно меньше работы для worker.
На небольшом контентном сайте, обрабатывающем примерно 33 000 запросов в день, среднее время ЦП на запрос снизилось с 668 мс до ~40 мс после включения этой функции, что составляет около 94% сокращения. Общее время ЦП, выставленное по счету Cloudflare за $0,02 за миллион миллисекунд ЦП, сократилось с ~22 млн мс/день до ~1,3 млн мс/день. Форма кривой затрат на панели выставления счетов изменилась в тот же день, когда было выпущено развертывание.
Экономия масштабируется линейно с трафиком. Чем более загружен сайт, тем больше разница между "двумя удаленными обращениями за запрос" и "одним локальным чтением Cache API за запрос".
Когда использовать
Если ваша конфигурация OpenNext использует kvIncrementalCache с d1TagCache (рекомендуемая настройка по умолчанию для контентных сайтов на Cloudflare), включите withRegionalCache с bypassTagCacheOnCacheHit: true. Блоги, сайты документации, маркетинговые страницы и аналогичные в основном статические рабочие нагрузки получают немедленную выгоду, а компромисс с устареванием незаметен.
Если вы запускаете приложение, где свежесть данных важна в реальном времени (цены, инвентарь, чат), оставьте bypassTagCacheOnCacheHit: false. Вы по-прежнему получаете слой регионального Cache API, более быстрые чтения из KV, меньшее потребление ЦП на запрос без окна устаревания.
Это изменение на пять строк. Тип изменения, которое окупается в первый же день.