diff --git a/src/lib/cache/cloudflare-cache.ts b/src/lib/cache/cloudflare-cache.ts index 81f7c9747..7973c3b91 100644 --- a/src/lib/cache/cloudflare-cache.ts +++ b/src/lib/cache/cloudflare-cache.ts @@ -3,11 +3,10 @@ import { Buffer } from 'node:buffer'; import type { CacheStorage, Cache, Response as WorkerResponse } from '@cloudflare/workers-types'; import { CacheBackend, CacheEntry } from './types'; -import { getCacheMaxAge } from './utils'; +import { getCacheMaxAge, isCacheEntryImmutable } from './utils'; import { trace } from '../tracing'; const cacheVersion = 1; -const cacheMaxAge = 2 * 60; /** * Cache implementation using the Cloudflare Cache API. @@ -102,8 +101,16 @@ function serializeEntry(entry: CacheEntry): WorkerResponse { headers.set('Content-Type', 'application/json'); const cacheTags = ['gitbook-open', ...entry.meta.tags]; - // Limit the cloudflare cache to a low fix duration - headers.set('Cache-Control', `public, max-age=${getCacheMaxAge(entry.meta, 10, cacheMaxAge)}`); + // When the entry is immutable, we can cache it for the entire duration. + // If the entry can be invalidated, we cache it for maximum 2 minutes as it could be invalidated in another data center. + headers.set( + 'Cache-Control', + `public, max-age=${getCacheMaxAge( + entry.meta, + 10, + isCacheEntryImmutable(entry.meta) ? 2 * 60 : undefined, + )}`, + ); headers.set('Cache-Tag', cacheTags.join(',')); // @ts-ignore diff --git a/src/lib/cache/memory.ts b/src/lib/cache/memory.ts index 0514468cb..f42d0be7f 100644 --- a/src/lib/cache/memory.ts +++ b/src/lib/cache/memory.ts @@ -1,9 +1,5 @@ import { CacheBackend, CacheEntry } from './types'; - -/** - * In production, we limit the cache to 5 minutes as it can't be invalidated on all instances. - */ -const cacheMaxAge = process.env.NODE_ENV === 'development' ? Infinity : 5 * 60; +import { isCacheEntryImmutable } from './utils'; export const memoryCache: CacheBackend = { name: 'memory', @@ -30,7 +26,16 @@ export const memoryCache: CacheBackend = { ...entry, meta: { ...entry.meta, - expiresAt: Math.min(Date.now() + cacheMaxAge * 1000, entry.meta.expiresAt), + ...(isCacheEntryImmutable(entry.meta) || process.env.NODE_ENV === 'development' + ? {} + : { + // For mutable entries, we limit the cache to 1 minute + // as it could be invalidated at any time. + expiresAt: Math.min( + entry.meta.setAt ?? Date.now() + 60 * 1000, + entry.meta.expiresAt, + ), + }), }, }); }, diff --git a/src/lib/cache/types.ts b/src/lib/cache/types.ts index aaad436d3..097ea95cd 100644 --- a/src/lib/cache/types.ts +++ b/src/lib/cache/types.ts @@ -22,7 +22,8 @@ export interface CacheEntryMeta { revalidatesAt?: number; /** - * Tags associated with the entry. + * Tags associated with the entry, used for revalidation. + * If no tags is present, the entry is considered immutable. */ tags: string[]; diff --git a/src/lib/cache/utils.ts b/src/lib/cache/utils.ts index d47383599..d6a809e3f 100644 --- a/src/lib/cache/utils.ts +++ b/src/lib/cache/utils.ts @@ -12,3 +12,10 @@ export function getCacheMaxAge(meta: CacheEntryMeta, min?: number, max?: number) return maxAge; } + +/** + * Return true if a cache entry can be considered immutable. + */ +export function isCacheEntryImmutable(meta: CacheEntryMeta): boolean { + return !meta.tags || meta.tags.length === 0; +} diff --git a/src/lib/images.ts b/src/lib/images.ts index e6fe20d37..1a22d7337 100644 --- a/src/lib/images.ts +++ b/src/lib/images.ts @@ -50,7 +50,6 @@ export async function getResizedImageURL( return () => input; } - const signature = await generateSignature(input); if (!signature) { return () => input;