Skip to content

Commit 45cf20c

Browse files
committed
test release registry cache approach
1 parent eeefed9 commit 45cf20c

File tree

1 file changed

+33
-233
lines changed

1 file changed

+33
-233
lines changed

src/mdx.ts

Lines changed: 33 additions & 233 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import remarkMdxImages from 'remark-mdx-images';
2626
import getAppRegistry from './build/appRegistry';
2727
import getPackageRegistry from './build/packageRegistry';
2828
import {apiCategories} from './build/resolveOpenAPI';
29-
import {BuildTimer, logBuildInfo, OperationAggregator} from './buildTimer';
3029
import getAllFilesRecursively from './files';
3130
import remarkDefList from './mdx-deflist';
3231
import rehypeOnboardingLines from './rehype-onboarding-lines';
@@ -66,12 +65,6 @@ if (process.env.CI) {
6665
mkdirSync(CACHE_DIR, {recursive: true});
6766
}
6867

69-
// Track MDX compilation performance
70-
const mdxCompilationAggregator = new OperationAggregator('MDX Compilation', {
71-
progressInterval: 500, // log every 500 compilations (less verbose)
72-
slowThreshold: 3000, // 3 seconds (only really slow pages)
73-
});
74-
7568
const md5 = (data: BinaryLike) => createHash('md5').update(data).digest('hex');
7669

7770
async function readCacheFile<T>(file: string): Promise<T> {
@@ -153,108 +146,7 @@ export const getVersionsFromDoc = (frontMatter: FrontMatter[], docPath: string)
153146
return [...new Set(versions)];
154147
};
155148

156-
// Lock file management for cross-worker cache coordination
157-
const lockFile = (cacheFile: string) =>
158-
path.join(root, '.next', 'cache', `${cacheFile}.lock`);
159-
160-
/**
161-
* Try to load frontmatter from disk cache created by first worker
162-
* If cache is being built, wait for it to complete
163-
*/
164-
async function loadFrontmatterFromCache(
165-
cacheFile: string
166-
): Promise<FrontMatter[] | null> {
167-
const cachePath = path.join(root, '.next', 'cache', cacheFile);
168-
const lockPath = lockFile(cacheFile);
169-
170-
// Check if another worker is building the cache
171-
let waitAttempts = 0;
172-
while (waitAttempts < 30) {
173-
// Wait up to 15 seconds
174-
try {
175-
// If lock file exists, another worker is building cache
176-
await access(lockPath);
177-
await new Promise(resolve => setTimeout(resolve, 500));
178-
waitAttempts++;
179-
continue;
180-
} catch {
181-
// No lock file, proceed
182-
break;
183-
}
184-
}
185-
186-
try {
187-
const cacheData = JSON.parse(await readFile(cachePath, 'utf-8'));
188-
const age = Date.now() - cacheData.timestamp;
189-
190-
// Cache is valid for 1 hour (in case of incremental builds)
191-
if (age < 60 * 60 * 1000) {
192-
logBuildInfo(
193-
`✓ Loaded ${cacheData.frontmatter.length} files from worker cache (${(age / 1000).toFixed(0)}s old)`
194-
);
195-
return cacheData.frontmatter;
196-
}
197-
return null;
198-
} catch {
199-
// Cache doesn't exist or is invalid
200-
return null;
201-
}
202-
}
203-
204-
/**
205-
* Save frontmatter to disk cache for other workers
206-
*/
207-
async function saveFrontmatterToCache(
208-
cacheFile: string,
209-
frontmatter: FrontMatter[]
210-
): Promise<void> {
211-
try {
212-
const cachePath = path.join(root, '.next', 'cache', cacheFile);
213-
const cacheData = {
214-
timestamp: Date.now(),
215-
frontmatter,
216-
};
217-
await mkdir(path.dirname(cachePath), {recursive: true});
218-
await import('fs/promises').then(fs =>
219-
fs.writeFile(cachePath, JSON.stringify(cacheData))
220-
);
221-
logBuildInfo(`✓ Saved ${frontmatter.length} files to worker cache for other workers`);
222-
} catch (error) {
223-
// Non-fatal - other workers will just build their own
224-
// eslint-disable-next-line no-console
225-
console.error('Failed to save frontmatter cache:', error);
226-
}
227-
}
228-
229149
async function getDocsFrontMatterUncached(): Promise<FrontMatter[]> {
230-
// Try to load from worker cache first
231-
const cached = await loadFrontmatterFromCache('frontmatter.json');
232-
if (cached) {
233-
return cached;
234-
}
235-
236-
// Create lock file so other workers wait
237-
const lockPath = lockFile('frontmatter.json');
238-
let lockFd: number | null = null;
239-
try {
240-
// Try to create lock file atomically
241-
const {open} = await import('fs/promises');
242-
lockFd = await open(lockPath, 'wx')
243-
.then(fh => fh.fd)
244-
.catch(() => null);
245-
246-
// If we couldn't create lock, another worker is building - wait for them
247-
if (!lockFd) {
248-
const cached2 = await loadFrontmatterFromCache('frontmatter.json');
249-
if (cached2) {
250-
return cached2;
251-
}
252-
// Cache not ready, fall through to build ourselves
253-
}
254-
} catch {
255-
// Lock file issues are non-fatal
256-
}
257-
258150
const frontMatter = await getAllFilesFrontMatter();
259151

260152
const categories = await apiCategories();
@@ -287,50 +179,10 @@ async function getDocsFrontMatterUncached(): Promise<FrontMatter[]> {
287179
}
288180
});
289181

290-
// Save to cache for other workers (if we got the lock)
291-
if (lockFd) {
292-
await saveFrontmatterToCache('frontmatter.json', frontMatter);
293-
// Remove lock file
294-
try {
295-
const {unlink} = await import('fs/promises');
296-
await unlink(lockPath);
297-
} catch {
298-
// Non-fatal
299-
}
300-
}
301-
302182
return frontMatter;
303183
}
304184

305185
export async function getDevDocsFrontMatterUncached(): Promise<FrontMatter[]> {
306-
// Try to load from worker cache first
307-
const cached = await loadFrontmatterFromCache('frontmatter-dev.json');
308-
if (cached) {
309-
return cached;
310-
}
311-
312-
// Create lock file so other workers wait
313-
const lockPath = lockFile('frontmatter-dev.json');
314-
let lockFd: number | null = null;
315-
try {
316-
// Try to create lock file atomically
317-
const {open} = await import('fs/promises');
318-
lockFd = await open(lockPath, 'wx')
319-
.then(fh => fh.fd)
320-
.catch(() => null);
321-
322-
// If we couldn't create lock, another worker is building - wait for them
323-
if (!lockFd) {
324-
const cached2 = await loadFrontmatterFromCache('frontmatter-dev.json');
325-
if (cached2) {
326-
return cached2;
327-
}
328-
// Cache not ready, fall through to build ourselves
329-
}
330-
} catch {
331-
// Lock file issues are non-fatal
332-
}
333-
334186
const folder = 'develop-docs';
335187
const docsPath = path.join(root, folder);
336188
const files = await getAllFilesRecursively(docsPath);
@@ -358,18 +210,6 @@ export async function getDevDocsFrontMatterUncached(): Promise<FrontMatter[]> {
358210
)
359211
).filter(isNotNil);
360212

361-
// Save to cache for other workers (if we got the lock)
362-
if (lockFd) {
363-
await saveFrontmatterToCache('frontmatter-dev.json', frontMatters);
364-
// Remove lock file
365-
try {
366-
const {unlink} = await import('fs/promises');
367-
await unlink(lockPath);
368-
} catch {
369-
// Non-fatal
370-
}
371-
}
372-
373213
return frontMatters;
374214
}
375215

@@ -383,12 +223,10 @@ export function getDevDocsFrontMatter(): Promise<FrontMatter[]> {
383223
}
384224

385225
async function getAllFilesFrontMatter(): Promise<FrontMatter[]> {
386-
const timer = new BuildTimer('getAllFilesFrontMatter');
387226
const docsPath = path.join(root, 'docs');
388227
const files = await getAllFilesRecursively(docsPath);
389228
const allFrontMatter: FrontMatter[] = [];
390229

391-
const readFilesTimer = new BuildTimer('Reading MDX frontmatter');
392230
await Promise.all(
393231
files.map(
394232
limitFunction(
@@ -414,10 +252,8 @@ async function getAllFilesFrontMatter(): Promise<FrontMatter[]> {
414252
)
415253
)
416254
);
417-
readFilesTimer.end(true); // Silent - we'll show in summary
418255

419256
// Add all `common` files in the right place.
420-
const commonFilesTimer = new BuildTimer('Processing common platform files');
421257
const platformsPath = path.join(docsPath, 'platforms');
422258
for await (const platform of await opendir(platformsPath)) {
423259
if (platform.isFile()) {
@@ -561,8 +397,7 @@ async function getAllFilesFrontMatter(): Promise<FrontMatter[]> {
561397
);
562398
}
563399
}
564-
commonFilesTimer.end(true); // Silent - we'll show in summary
565-
timer.end();
400+
566401
return allFrontMatter;
567402
}
568403

@@ -600,8 +435,6 @@ export const addVersionToFilePath = (filePath: string, version: string) => {
600435
};
601436

602437
export async function getFileBySlug(slug: string): Promise<SlugFile> {
603-
const compileStart = Date.now();
604-
605438
// no versioning on a config file
606439
const configPath = path.join(root, slug.split(VERSION_INDICATOR)[0], 'config.yml');
607440

@@ -707,71 +540,54 @@ export async function getFileBySlug(slug: string): Promise<SlugFile> {
707540
source.includes('<LambdaLayerDetail');
708541

709542
if (process.env.CI) {
710-
// Build cache key from: source content + registry data (if needed) + build ID
543+
// Build cache key from source content
711544
const sourceHash = md5(source);
712545

713546
// For files that depend on registry, include registry version in cache key
714-
let registryHash = '';
547+
// This prevents serving stale content when registry is updated
715548
if (dependsOnRegistry) {
716549
try {
717-
// Get registry data (already cached in memory by each worker)
550+
// Get registry data (cached in memory per worker)
718551
const [apps, packages] = await Promise.all([
719552
getAppRegistry(),
720553
getPackageRegistry(),
721554
]);
722555
// Hash the registry data to create a stable version identifier
723-
registryHash = md5(JSON.stringify({apps, packages}));
556+
const registryHash = md5(JSON.stringify({apps, packages}));
557+
cacheKey = `${sourceHash}-${registryHash}`;
724558
} catch (err) {
725-
// If registry fetch fails, skip caching
559+
// If registry fetch fails, skip caching for safety
726560
// eslint-disable-next-line no-console
727-
console.warn(`Failed to fetch registry for cache key: ${err.message}`);
728-
registryHash = 'no-registry';
561+
console.warn(
562+
`Failed to fetch registry for cache key, skipping cache: ${err.message}`
563+
);
564+
cacheKey = null;
729565
}
566+
} else {
567+
// Regular files without registry dependencies
568+
cacheKey = sourceHash;
730569
}
731570

732-
// Use VERCEL_GIT_COMMIT_REF (branch name) for cache key
733-
// This allows cache to persist across commits on the same branch!
734-
// Falls back to VERCEL_DEPLOYMENT_ID for non-git deploys, then BUILD_ID, then 'local'
735-
const cacheVersion =
736-
process.env.VERCEL_GIT_COMMIT_REF ||
737-
process.env.VERCEL_DEPLOYMENT_ID ||
738-
process.env.BUILD_ID ||
739-
'local';
740-
741-
// Cache key: source + registry version + branch/build identifier
742-
// This ensures:
743-
// 1. All workers in the same build share cache (same cacheVersion)
744-
// 2. Cache persists across commits on same branch (same VERCEL_GIT_COMMIT_REF)
745-
// 3. Cache is invalidated when registry changes (registryHash changes)
746-
// 4. Cache is invalidated when source changes (sourceHash changes)
747-
cacheKey = dependsOnRegistry
748-
? `${sourceHash}-${registryHash}-${cacheVersion}`
749-
: `${sourceHash}-${cacheVersion}`;
750-
751-
cacheFile = path.join(CACHE_DIR, `${cacheKey}.br`);
752-
assetsCacheDir = path.join(CACHE_DIR, cacheKey);
571+
if (cacheKey) {
572+
cacheFile = path.join(CACHE_DIR, `${cacheKey}.br`);
573+
assetsCacheDir = path.join(CACHE_DIR, cacheKey);
753574

754-
try {
755-
const [cached, _] = await Promise.all([
756-
readCacheFile<SlugFile>(cacheFile),
757-
cp(assetsCacheDir, outdir, {recursive: true}),
758-
]);
759-
if (dependsOnRegistry) {
760-
// eslint-disable-next-line no-console
761-
console.info(
762-
`✓ Using cached registry-dependent file: ${sourcePath} (branch: ${cacheVersion}, registry: ${registryHash.slice(0, 8)})`
763-
);
764-
}
765-
return cached;
766-
} catch (err) {
767-
if (
768-
err.code !== 'ENOENT' &&
769-
err.code !== 'ABORT_ERR' &&
770-
err.code !== 'Z_BUF_ERROR'
771-
) {
772-
// If cache is corrupted, ignore and proceed
773-
// eslint-disable-next-line no-console
774-
console.warn(`Failed to read MDX cache: ${cacheFile}`, err);
575+
try {
576+
const [cached, _] = await Promise.all([
577+
readCacheFile<SlugFile>(cacheFile),
578+
cp(assetsCacheDir, outdir, {recursive: true}),
579+
]);
580+
return cached;
581+
} catch (err) {
582+
if (
583+
err.code !== 'ENOENT' &&
584+
err.code !== 'ABORT_ERR' &&
585+
err.code !== 'Z_BUF_ERROR'
586+
) {
587+
// If cache is corrupted, ignore and proceed
588+
// eslint-disable-next-line no-console
589+
console.warn(`Failed to read MDX cache: ${cacheFile}`, err);
590+
}
775591
}
776592
}
777593
}
@@ -915,25 +731,9 @@ export async function getFileBySlug(slug: string): Promise<SlugFile> {
915731
});
916732
}
917733

918-
// Track compilation time
919-
const compileDuration = Date.now() - compileStart;
920-
mdxCompilationAggregator.track(slug, compileDuration);
921-
922734
return resultObj;
923735
}
924736

925-
// Log final MDX compilation stats when process exits
926-
if (typeof process !== 'undefined') {
927-
process.on('beforeExit', () => {
928-
const stats = mdxCompilationAggregator.getStats();
929-
if (stats.count > 0) {
930-
logBuildInfo('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
931-
mdxCompilationAggregator.logFinalSummary();
932-
logBuildInfo('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
933-
}
934-
});
935-
}
936-
937737
const fileBySlugCache = new Map<string, Promise<SlugFile>>();
938738

939739
/**

0 commit comments

Comments
 (0)