Skip to content

Commit c8a4280

Browse files
committed
perf: dedupe tag revalidations
1 parent aa76535 commit c8a4280

File tree

2 files changed

+22
-1
lines changed

2 files changed

+22
-1
lines changed

src/run/handlers/request-context.cts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export type RequestContext = {
2121
serverTiming?: string
2222
routeHandlerRevalidate?: NetlifyCachedRouteValue['revalidate']
2323
pageHandlerRevalidate?: NetlifyCachedRouteValue['revalidate']
24+
ongoingRevalidations?: Map<string, Promise<void>>
2425
/**
2526
* Track promise running in the background and need to be waited for.
2627
* Uses `context.waitUntil` if available, otherwise stores promises to

src/run/handlers/tags-handler.cts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,10 +214,30 @@ export function markTagsAsStaleAndPurgeEdgeCache(
214214
) {
215215
const tags = getCacheTagsFromTagOrTags(tagOrTags)
216216

217+
// Next.js is calling classic CacheHandler.revalidateTag and 'use cache' CacheHandler expireTags/updateTags separately
218+
// this results in duplicate work being done (it doesn't cause problems, but it is inefficient)
219+
// See https://github.com/vercel/next.js/blob/8cab15c0c947a71eb8606ba29da719a2e121fc88/packages/next/src/server/revalidation-utils.ts#L170-L180
220+
// Deduping those within context of a single request might catch unrelated invalidations, so instead of using just request context
221+
// we will check if they happened in same event loop tick as well.
222+
const revalidationKey = JSON.stringify({ tags, durations })
223+
const requestContext = getRequestContext()
224+
225+
if (requestContext) {
226+
const ongoingRevalidation = requestContext.ongoingRevalidations?.get(revalidationKey)
227+
if (ongoingRevalidation) {
228+
// If we already have an ongoing revalidation for this key, we can use it
229+
return ongoingRevalidation
230+
}
231+
}
232+
217233
const revalidateTagPromise = doRevalidateTagAndPurgeEdgeCache(tags, durations)
218234

219-
const requestContext = getRequestContext()
220235
if (requestContext) {
236+
requestContext.ongoingRevalidations ??= new Map()
237+
requestContext.ongoingRevalidations.set(revalidationKey, revalidateTagPromise)
238+
process.nextTick(() => {
239+
requestContext.ongoingRevalidations?.delete(revalidationKey)
240+
})
221241
requestContext.trackBackgroundWork(revalidateTagPromise)
222242
}
223243

0 commit comments

Comments
 (0)