Next Cache Telemetry (#47939)

### What?

Report additional telemetry related to cached fetches:

- Fetch Index number to group related fetches (cache-get, cache-set,
origin)
- Origin URL map cache key to original upstream URL

### Why?

This is needed for fetch cache telemetry on the Vercel platform.

### How?

Telemetry is provided through optional parameters added to the fetch
call configuration. It is similar to the `next: {revalidate: X}` and
`{next: { internal: true }}` fields.

The origin URL and fetch index are calculated in the patch-fetch
function and are passed down to the caching classes as needed. These
fields are optional and ignored by the `FileSystemCache`.
This commit is contained in:
Nabeel Sulieman 2023-04-07 12:06:04 -07:00 committed by GitHub
parent b5f785aab1
commit 5ea70b85c1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 84 additions and 17 deletions

View file

@ -24,6 +24,8 @@ export interface StaticGenerationStore {
dynamicUsageDescription?: string
dynamicUsageStack?: string
nextFetchId?: number
}
export type StaticGenerationAsyncStorage =

View file

@ -5,6 +5,13 @@ import type { CacheHandler, CacheHandlerContext, CacheHandlerValue } from './'
let memoryCache: LRUCache<string, CacheHandlerValue> | undefined
interface NextFetchCacheParams {
internal?: boolean
fetchType?: string
fetchIdx?: number
originUrl?: string
}
export default class FetchCache implements CacheHandler {
private headers: Record<string, string>
private cacheEndpoint?: string
@ -66,7 +73,12 @@ export default class FetchCache implements CacheHandler {
}
}
public async get(key: string, fetchCache?: boolean) {
public async get(
key: string,
fetchCache?: boolean,
originUrl?: string,
fetchIdx?: number
) {
if (!fetchCache) return null
let data = memoryCache?.get(key)
@ -81,13 +93,18 @@ export default class FetchCache implements CacheHandler {
if (!data && this.cacheEndpoint) {
try {
const start = Date.now()
const fetchParams: NextFetchCacheParams = {
internal: true,
fetchType: 'fetch-get',
originUrl,
fetchIdx,
}
const res = await fetch(
`${this.cacheEndpoint}/v1/suspense-cache/${key}`,
{
method: 'GET',
headers: this.headers,
// @ts-expect-error
next: { internal: true },
next: fetchParams as NextFetchRequestConfig,
}
)
@ -150,7 +167,9 @@ export default class FetchCache implements CacheHandler {
public async set(
key: string,
data: CacheHandlerValue['value'],
fetchCache?: boolean
fetchCache?: boolean,
originUrl?: string,
fetchIdx?: number
) {
if (!fetchCache) return
@ -174,14 +193,19 @@ export default class FetchCache implements CacheHandler {
data.data.headers['cache-control']
}
const body = JSON.stringify(data)
const fetchParams: NextFetchCacheParams = {
internal: true,
fetchType: 'fetch-set',
originUrl,
fetchIdx,
}
const res = await fetch(
`${this.cacheEndpoint}/v1/suspense-cache/${key}`,
{
method: 'POST',
headers: this.headers,
body: body,
// @ts-expect-error
next: { internal: true },
next: fetchParams as NextFetchRequestConfig,
}
)

View file

@ -39,7 +39,9 @@ export class CacheHandler {
public async get(
_key: string,
_fetchCache?: boolean
_fetchCache?: boolean,
_originUrl?: string,
_fetchIdx?: number
): Promise<CacheHandlerValue | null> {
return {} as any
}
@ -47,7 +49,9 @@ export class CacheHandler {
public async set(
_key: string,
_data: IncrementalCacheValue | null,
_fetchCache?: boolean
_fetchCache?: boolean,
_originUrl?: string,
_fetchIdx?: number
): Promise<void> {}
}
@ -266,7 +270,9 @@ export class IncrementalCache {
async get(
pathname: string,
fetchCache?: boolean,
revalidate?: number
revalidate?: number,
originUrl?: string,
fetchIdx?: number
): Promise<IncrementalCacheEntry | null> {
// we don't leverage the prerender cache in dev mode
// so that getStaticProps is always called for easier debugging
@ -279,7 +285,12 @@ export class IncrementalCache {
pathname = this._getPathname(pathname, fetchCache)
let entry: IncrementalCacheEntry | null = null
const cacheData = await this.cacheHandler?.get(pathname, fetchCache)
const cacheData = await this.cacheHandler?.get(
pathname,
fetchCache,
originUrl,
fetchIdx
)
if (cacheData?.value?.kind === 'FETCH') {
revalidate = revalidate || cacheData.value.revalidate
@ -337,7 +348,14 @@ export class IncrementalCache {
curRevalidate,
revalidateAfter,
}
this.set(pathname, entry.value, curRevalidate, fetchCache)
this.set(
pathname,
entry.value,
curRevalidate,
fetchCache,
originUrl,
fetchIdx
)
}
return entry
}
@ -347,7 +365,9 @@ export class IncrementalCache {
pathname: string,
data: IncrementalCacheValue | null,
revalidateSeconds?: number | false,
fetchCache?: boolean
fetchCache?: boolean,
originUrl?: string,
fetchIdx?: number
) {
if (this.dev && !fetchCache) return
// fetchCache has upper limit of 2MB per-entry currently
@ -374,7 +394,13 @@ export class IncrementalCache {
initialRevalidateSeconds: revalidateSeconds,
}
}
await this.cacheHandler?.set(pathname, data, fetchCache)
await this.cacheHandler?.set(
pathname,
data,
fetchCache,
originUrl,
fetchIdx
)
} catch (error) {
console.warn('Failed to update prerender cache for', pathname, error)
}

View file

@ -25,7 +25,7 @@ export function patchFetch({
input: RequestInfo | URL,
init: RequestInit | undefined
) => {
let url
let url: URL | undefined
try {
url = new URL(input instanceof Request ? input.url : input)
url.username = ''
@ -163,6 +163,7 @@ export function patchFetch({
!autoNoCache &&
(typeof staticGenerationStore.revalidate === 'undefined' ||
(typeof revalidate === 'number' &&
typeof staticGenerationStore.revalidate === 'number' &&
revalidate < staticGenerationStore.revalidate))
) {
staticGenerationStore.revalidate = revalidate
@ -222,8 +223,18 @@ export function patchFetch({
}
}
const originUrl = url?.toString() ?? ''
const fetchIdx = staticGenerationStore.nextFetchId ?? 1
staticGenerationStore.nextFetchId = fetchIdx + 1
const doOriginalFetch = async () => {
return originFetch(input, init).then(async (res) => {
// add metadata to init without editing the original
const clonedInit = {
...init,
next: { ...init?.next, fetchType: 'origin', fetchIdx, originUrl },
}
return originFetch(input, clonedInit).then(async (res) => {
if (
res.ok &&
staticGenerationStore.incrementalCache &&
@ -256,7 +267,9 @@ export function patchFetch({
revalidate,
},
revalidate,
true
true,
originUrl,
fetchIdx
)
} catch (err) {
console.warn(`Failed to set fetch cache`, input, err)
@ -277,7 +290,9 @@ export function patchFetch({
: await staticGenerationStore.incrementalCache.get(
cacheKey,
true,
revalidate
revalidate,
originUrl,
fetchIdx
)
if (entry?.value && entry.value.kind === 'FETCH') {