diff --git a/netlify/functions/cleanup-docs-cache-background.ts b/netlify/functions/cleanup-docs-cache-background.ts new file mode 100644 index 000000000..16c96b425 --- /dev/null +++ b/netlify/functions/cleanup-docs-cache-background.ts @@ -0,0 +1,41 @@ +import type { Config } from '@netlify/functions' +import { pruneOldCacheEntries } from '~/utils/github-content-cache.server' + +const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000 + +const handler = async (req: Request) => { + const { next_run } = await req.json() + + console.log('[cleanup-docs-cache] Starting docs cache prune...') + + const startTime = Date.now() + + try { + const { contentDeleted, artifactDeleted, threshold } = + await pruneOldCacheEntries(THIRTY_DAYS_MS) + + const duration = Date.now() - startTime + console.log( + `[cleanup-docs-cache] Completed in ${duration}ms - Deleted ${contentDeleted.toLocaleString()} content rows and ${artifactDeleted.toLocaleString()} artifact rows older than ${threshold.toISOString()}`, + ) + console.log('[cleanup-docs-cache] Next invocation at:', next_run) + } catch (error) { + const duration = Date.now() - startTime + const errorMessage = error instanceof Error ? error.message : String(error) + const errorStack = error instanceof Error ? error.stack : undefined + + console.error( + `[cleanup-docs-cache] Failed after ${duration}ms:`, + errorMessage, + ) + if (errorStack) { + console.error('[cleanup-docs-cache] Stack:', errorStack) + } + } +} + +export default handler + +export const config: Config = { + schedule: '0 3 * * *', +} diff --git a/src/utils/github-content-cache.server.ts b/src/utils/github-content-cache.server.ts index d29e5c450..b03207a4f 100644 --- a/src/utils/github-content-cache.server.ts +++ b/src/utils/github-content-cache.server.ts @@ -1,4 +1,4 @@ -import { and, eq, sql } from 'drizzle-orm' +import { and, eq, lt, sql } from 'drizzle-orm' import { db } from '~/db/client' import { docsArtifactCache, @@ -42,12 +42,6 @@ function isFresh(staleAt: Date) { return staleAt.getTime() > Date.now() } -function queueRefresh(key: string, fn: () => Promise) { - void withPendingRefresh(key, fn).catch((error) => { - console.error(`[GitHub Cache] Failed to refresh ${key}:`, error) - }) -} - function readStoredTextValue(row: GithubContentCache | undefined) { if (!row) { return undefined @@ -166,19 +160,8 @@ async function getCachedGitHubContent(opts: { const cachedRow = await readRow() const storedValue = opts.readStoredValue(cachedRow) - if (storedValue !== undefined) { - if (cachedRow && isFresh(cachedRow.staleAt)) { - return storedValue - } - - if (storedValue !== null) { - queueRefresh(opts.cacheKey, async () => { - const value = await opts.origin() - await persist(value) - }) - - return storedValue - } + if (storedValue !== undefined && cachedRow && isFresh(cachedRow.staleAt)) { + return storedValue } return withPendingRefresh(opts.cacheKey, async () => { @@ -297,16 +280,7 @@ export async function getCachedDocsArtifact(opts: { const storedValue = cachedRow && opts.isValue(cachedRow.payload) ? cachedRow.payload : undefined - if (storedValue !== undefined) { - if (cachedRow && isFresh(cachedRow.staleAt)) { - return storedValue - } - - queueRefresh(cacheKey, async () => { - const payload = await opts.build() - await upsertDocsArtifact({ ...opts, payload }) - }) - + if (storedValue !== undefined && cachedRow && isFresh(cachedRow.staleAt)) { return storedValue } @@ -382,6 +356,27 @@ export async function markGitHubContentStale( return rowCount } +export async function pruneOldCacheEntries(olderThanMs: number) { + const threshold = new Date(Date.now() - olderThanMs) + + const [contentDeleted, artifactDeleted] = await Promise.all([ + db + .delete(githubContentCache) + .where(lt(githubContentCache.updatedAt, threshold)) + .returning({ repo: githubContentCache.repo }), + db + .delete(docsArtifactCache) + .where(lt(docsArtifactCache.updatedAt, threshold)) + .returning({ repo: docsArtifactCache.repo }), + ]) + + return { + contentDeleted: contentDeleted.length, + artifactDeleted: artifactDeleted.length, + threshold, + } +} + export async function markDocsArtifactsStale( opts: { gitRef?: string