Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure we chunk revalidate tag requests #70189

Merged
merged 5 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 23 additions & 20 deletions packages/next/src/server/lib/incremental-cache/fetch-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,29 +182,32 @@ export default class FetchCache implements CacheHandler {
return
}

try {
const res = await fetchRetryWithTimeout(
`${this.cacheEndpoint}/v1/suspense-cache/revalidate?tags=${tags
.map((tag) => encodeURIComponent(tag))
.join(',')}`,
{
method: 'POST',
headers: this.headers,
// @ts-expect-error not on public type
next: { internal: true },
}
)
for (let i = 0; i < Math.ceil(tags.length / 64); i++) {
const currentTags = tags.slice(i * 64, i * 64 + 64)
try {
const res = await fetchRetryWithTimeout(
`${this.cacheEndpoint}/v1/suspense-cache/revalidate?tags=${currentTags
.map((tag) => encodeURIComponent(tag))
.join(',')}`,
{
method: 'POST',
headers: this.headers,
// @ts-expect-error not on public type
next: { internal: true },
}
)

if (res.status === 429) {
const retryAfter = res.headers.get('retry-after') || '60000'
rateLimitedUntil = Date.now() + parseInt(retryAfter)
}
if (res.status === 429) {
const retryAfter = res.headers.get('retry-after') || '60000'
rateLimitedUntil = Date.now() + parseInt(retryAfter)
}

if (!res.ok) {
throw new Error(`Request failed with status ${res.status}.`)
if (!res.ok) {
throw new Error(`Request failed with status ${res.status}.`)
}
} catch (err) {
console.warn(`Failed to revalidate tag`, currentTags, err)
}
} catch (err) {
console.warn(`Failed to revalidate tag ${tags}`, err)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { revalidateTag } from 'next/cache'
import { NextRequest, NextResponse } from 'next/server'

export const dynamic = 'force-dynamic'

export function GET(req: NextRequest) {
for (let i = 0; i < 130; i++) {
revalidateTag(`thankyounext-${i}`)
}
return NextResponse.json({ done: true })
}
25 changes: 22 additions & 3 deletions test/production/app-dir/fetch-cache/fetch-cache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ describe('fetch-cache', () => {
let nextInstance: any
let fetchGetReqIndex = 0
let revalidateReqIndex = 0
let revalidateReqShouldTimeout = false
let fetchGetShouldError = false
let fetchCacheServer: http.Server
let fetchCacheRequests: Array<{
Expand Down Expand Up @@ -112,6 +113,7 @@ describe('fetch-cache', () => {
fetchCacheRequests = []
storeCacheItems = false
fetchGetShouldError = false
revalidateReqShouldTimeout = false
fetchCacheServer = http.createServer(async (req, res) => {
console.log(`fetch cache request ${req.url} ${req.method}`, req.headers)
const parsedUrl = new URL(req.url || '/', 'http://n')
Expand All @@ -125,7 +127,8 @@ describe('fetch-cache', () => {
if (parsedUrl.pathname === '/v1/suspense-cache/revalidate') {
revalidateReqIndex += 1
// timeout unless it's 3rd retry
const shouldTimeout = revalidateReqIndex % 3 !== 0
const shouldTimeout =
revalidateReqShouldTimeout && revalidateReqIndex % 3 !== 0

if (shouldTimeout) {
console.log('not responding for', req.url, { revalidateReqIndex })
Expand Down Expand Up @@ -231,10 +234,26 @@ describe('fetch-cache', () => {
})

it('should retry 3 times when revalidate times out', async () => {
await fetchViaHTTP(appPort, '/api/revalidate')
revalidateReqShouldTimeout = true
try {
await fetchViaHTTP(appPort, '/api/revalidate')

await retry(() => {
expect(revalidateReqIndex).toBe(3)
})
expect(cliOuptut).not.toContain('Failed to revalidate')
expect(cliOuptut).not.toContain('Error')
} finally {
revalidateReqShouldTimeout = false
}
})

it('should batch revalidate tag requests if > 64', async () => {
const revalidateReqIndexStart = revalidateReqIndex
await fetchViaHTTP(appPort, '/api/revalidate-alot')

await retry(() => {
expect(revalidateReqIndex).toBe(3)
expect(revalidateReqIndex).toBe(revalidateReqIndexStart + 3)
})
expect(cliOuptut).not.toContain('Failed to revalidate')
expect(cliOuptut).not.toContain('Error')
Expand Down
3 changes: 2 additions & 1 deletion test/turbopack-build-tests-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -15801,7 +15801,8 @@
"fetch-cache should have correct fetchUrl field for fetches and unstable_cache",
"fetch-cache should not retry for failed fetch-cache GET",
"fetch-cache should retry 3 times when revalidate times out",
"fetch-cache should update cache TTL even if cache data does not change"
"fetch-cache should update cache TTL even if cache data does not change",
"fetch-cache should batch revalidate tag requests if > 64"
],
"pending": [],
"flakey": [],
Expand Down
Loading