-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
feat(Vercel) Build cache improvements #15317
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
Changes from 10 commits
91c4f27
ab18bad
eeefed9
45cf20c
ab63b42
662fd31
0186db0
0ec2adb
9fe553c
3031105
a824227
a1fcd87
a643daa
1f3b0aa
c2fc2f9
8510963
c0eae4b
9f19285
3357b00
485c80c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -65,6 +65,55 @@ if (process.env.CI) { | |
| mkdirSync(CACHE_DIR, {recursive: true}); | ||
| } | ||
|
|
||
| // Cache registry hash per worker to avoid recomputing for every file | ||
| let cachedRegistryHash: string | null = null; | ||
| async function getRegistryHash(): Promise<string> { | ||
| if (cachedRegistryHash) { | ||
| return cachedRegistryHash; | ||
| } | ||
| const [apps, packages] = await Promise.all([getAppRegistry(), getPackageRegistry()]); | ||
|
||
| cachedRegistryHash = md5(JSON.stringify({apps, packages})); | ||
| return cachedRegistryHash; | ||
| } | ||
sergical marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // Track cache statistics per worker (silent tracking) | ||
| const cacheStats = { | ||
| registryHits: 0, | ||
| registryMisses: 0, | ||
| uniqueRegistryFiles: new Set<string>(), | ||
| }; | ||
|
|
||
| // Log summary periodically and at end | ||
| let lastSummaryLog = Date.now(); | ||
| function logCacheSummary(force = false) { | ||
| const now = Date.now(); | ||
| // Log every 30 seconds or when forced | ||
|
||
| if (!force && now - lastSummaryLog < 30000) { | ||
| return; | ||
| } | ||
| lastSummaryLog = now; | ||
|
|
||
| const total = cacheStats.registryHits + cacheStats.registryMisses; | ||
| if (total === 0) { | ||
| return; | ||
| } | ||
|
|
||
| const hitRate = ((cacheStats.registryHits / total) * 100).toFixed(1); | ||
| const uniqueFiles = cacheStats.uniqueRegistryFiles.size; | ||
|
|
||
| // eslint-disable-next-line no-console | ||
| console.log( | ||
| `📊 [MDX Cache] ${cacheStats.registryHits}/${total} registry files cached (${hitRate}% hit rate, ${uniqueFiles} unique files)` | ||
| ); | ||
| } | ||
|
|
||
| // Log final summary when worker exits | ||
| if (typeof process !== 'undefined') { | ||
| process.on('beforeExit', () => { | ||
| logCacheSummary(true); | ||
| }); | ||
| } | ||
|
|
||
| const md5 = (data: BinaryLike) => createHash('md5').update(data).digest('hex'); | ||
|
|
||
| async function readCacheFile<T>(file: string): Promise<T> { | ||
|
|
@@ -209,6 +258,7 @@ export async function getDevDocsFrontMatterUncached(): Promise<FrontMatter[]> { | |
| ) | ||
| ) | ||
| ).filter(isNotNil); | ||
|
|
||
| return frontMatters; | ||
| } | ||
|
|
||
|
|
@@ -396,6 +446,7 @@ async function getAllFilesFrontMatter(): Promise<FrontMatter[]> { | |
| ); | ||
| } | ||
| } | ||
|
|
||
| return allFrontMatter; | ||
| } | ||
|
|
||
|
|
@@ -531,22 +582,37 @@ export async function getFileBySlug(slug: string): Promise<SlugFile> { | |
| const outdir = path.join(root, 'public', 'mdx-images'); | ||
| await mkdir(outdir, {recursive: true}); | ||
|
|
||
| // If the file contains content that depends on the Release Registry (such as an SDK's latest version), avoid using the cache for that file, i.e. always rebuild it. | ||
| // This is because the content from the registry might have changed since the last time the file was cached. | ||
| // If a new component that injects content from the registry is introduced, it should be added to the patterns below. | ||
| const skipCache = | ||
| // Check if file depends on Release Registry | ||
| const dependsOnRegistry = | ||
| source.includes('@inject') || | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if this |
||
| source.includes('<PlatformSDKPackageName') || | ||
| source.includes('<LambdaLayerDetail'); | ||
|
|
||
| if (process.env.CI) { | ||
| if (skipCache) { | ||
| // eslint-disable-next-line no-console | ||
| console.info( | ||
| `Not using cached version of ${sourcePath}, as its content depends on the Release Registry` | ||
| ); | ||
| // Build cache key from source content | ||
| const sourceHash = md5(source); | ||
|
|
||
| // For files that depend on registry, include registry version in cache key | ||
| // This prevents serving stale content when registry is updated | ||
| if (dependsOnRegistry) { | ||
| try { | ||
| // Get registry hash (cached per worker to avoid redundant fetches) | ||
| const registryHash = await getRegistryHash(); | ||
| cacheKey = `${sourceHash}-${registryHash}`; | ||
| } catch (err) { | ||
|
||
| // If registry fetch fails, skip caching for safety | ||
| // eslint-disable-next-line no-console | ||
| console.warn( | ||
| `Failed to fetch registry for cache key, skipping cache: ${err.message}` | ||
| ); | ||
| cacheKey = null; | ||
| } | ||
| } else { | ||
| cacheKey = md5(source); | ||
| // Regular files without registry dependencies | ||
| cacheKey = sourceHash; | ||
| } | ||
|
|
||
| if (cacheKey) { | ||
| cacheFile = path.join(CACHE_DIR, `${cacheKey}.br`); | ||
| assetsCacheDir = path.join(CACHE_DIR, cacheKey); | ||
|
|
||
|
|
@@ -555,6 +621,12 @@ export async function getFileBySlug(slug: string): Promise<SlugFile> { | |
| readCacheFile<SlugFile>(cacheFile), | ||
| cp(assetsCacheDir, outdir, {recursive: true}), | ||
| ]); | ||
| // Track cache hit silently | ||
| if (dependsOnRegistry) { | ||
| cacheStats.registryHits++; | ||
| cacheStats.uniqueRegistryFiles.add(sourcePath); | ||
| logCacheSummary(); // Periodically log summary (every 30s) | ||
| } | ||
| return cached; | ||
| } catch (err) { | ||
| if ( | ||
|
|
@@ -570,6 +642,13 @@ export async function getFileBySlug(slug: string): Promise<SlugFile> { | |
| } | ||
| } | ||
|
|
||
| // Track cache miss silently | ||
| if (dependsOnRegistry) { | ||
| cacheStats.registryMisses++; | ||
| cacheStats.uniqueRegistryFiles.add(sourcePath); | ||
| logCacheSummary(); // Periodically log summary (every 30s) | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Cache Miss Tracking Fails in Non-CI EnvironmentsThe cache miss tracking for registry-dependent files occurs unconditionally, even when |
||
|
|
||
| process.env.ESBUILD_BINARY_PATH = path.join( | ||
| root, | ||
| 'node_modules', | ||
|
|
@@ -700,7 +779,8 @@ export async function getFileBySlug(slug: string): Promise<SlugFile> { | |
| }, | ||
| }; | ||
|
|
||
| if (assetsCacheDir && cacheFile && !skipCache) { | ||
| // Save to cache if we have a cache key (we now cache everything, including registry-dependent files) | ||
| if (assetsCacheDir && cacheFile && cacheKey) { | ||
| await cp(assetsCacheDir, outdir, {recursive: true}); | ||
| writeCacheFile(cacheFile, JSON.stringify(resultObj)).catch(e => { | ||
| // eslint-disable-next-line no-console | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| #!/bin/bash | ||
|
|
||
| if [[ $VERCEL_ENV == "production" ]] ; then | ||
| yarn run build | ||
| else | ||
| yarn run build:preview | ||
| fi |
Uh oh!
There was an error while loading. Please reload this page.