diff --git a/src/app/lib/utilities/getEnvConfig/index.ts b/src/app/lib/utilities/getEnvConfig/index.ts index e4ce70ef114..e4e32de7219 100644 --- a/src/app/lib/utilities/getEnvConfig/index.ts +++ b/src/app/lib/utilities/getEnvConfig/index.ts @@ -28,6 +28,9 @@ export const getProcessEnvAppVariables = () => ({ process.env.SIMORGH_WEBVITALS_REPORTING_ENDPOINT, SIMORGH_WEBVITALS_DEFAULT_SAMPLING_RATE: process.env.SIMORGH_WEBVITALS_DEFAULT_SAMPLING_RATE, + SIMORGH_BFF_CACHE_ITEMS: process.env.SIMORGH_BFF_CACHE_ITEMS, + SIMORGH_BFF_CACHE_MAX_AGE_SECONDS: + process.env.SIMORGH_BFF_CACHE_MAX_AGE_SECONDS, }); export function getEnvConfig(): EnvConfig { diff --git a/src/app/routes/utils/fetchDataFromBFF/index.ts b/src/app/routes/utils/fetchDataFromBFF/index.ts index 61f4af93712..3d08b1273f4 100644 --- a/src/app/routes/utils/fetchDataFromBFF/index.ts +++ b/src/app/routes/utils/fetchDataFromBFF/index.ts @@ -7,6 +7,9 @@ import { BFF_FETCH_ERROR } from '#lib/logger.const'; import { FetchError, GetAgent } from '#models/types/fetch'; import nodeLogger from '#lib/logger.node'; import certsRequired from '#app/routes/utils/certsRequired'; +import type { LRUCache } from 'lru-cache'; + +type BffCache = LRUCache>; const logger = nodeLogger(__filename); @@ -19,6 +22,7 @@ interface FetchDataFromBffParams { disableRadioSchedule?: boolean; page?: string; getAgent?: GetAgent; + cache?: BffCache; } export default async ({ @@ -30,6 +34,7 @@ export default async ({ disableRadioSchedule, page, getAgent, + cache, }: FetchDataFromBffParams) => { const environment = getEnvironment(pathname); @@ -58,6 +63,7 @@ export default async ({ ...(agent && { agent }), ...(optHeaders && { optHeaders }), ...(timeout && { timeout }), + ...(cache && { cache }), }; // @ts-expect-error - Ignore fetchPageData argument types diff --git a/ws-nextjs-app/utilities/pageRequests/getPageData.ts b/ws-nextjs-app/utilities/pageRequests/getPageData.ts index 6bf86314fac..4d2c17a45fa 100644 --- a/ws-nextjs-app/utilities/pageRequests/getPageData.ts +++ b/ws-nextjs-app/utilities/pageRequests/getPageData.ts @@ -1,3 +1,4 @@ +import { LRUCache } from 'lru-cache'; import { BFF_FETCH_ERROR } from '#app/lib/logger.const'; import getToggles from '#app/lib/utilities/getToggles/withCache'; import { FetchError } from '#app/models/types/fetch'; @@ -10,6 +11,25 @@ import { PageTypes, Services, Variants } from '#app/models/types/global'; const logger = nodeLogger(__filename); +// cache holds the raw bff json; keep it loose because payloads vary by page type +type BffCacheValue = Record; + +// read numeric envs with a simple fallback; keeps memory low by default +const BFF_CACHE_MAX_ITEMS = + Number.parseInt(process.env.SIMORGH_BFF_CACHE_ITEMS ?? '', 10) || 200; +// one minute ttl to help quick repeat requests without risking stale data +const BFF_CACHE_TTL_MS = + (Number.parseInt( + process.env.SIMORGH_BFF_CACHE_MAX_AGE_SECONDS ?? '', + 10, + ) || 60) * 1000; + +// cache lives inside this server process +const bffCache = new LRUCache({ + max: BFF_CACHE_MAX_ITEMS, + ttl: BFF_CACHE_TTL_MS, +}); + type Props = { id?: string; page?: string; @@ -36,6 +56,8 @@ const getPageData = async ({ const rendererEnvironment = url.searchParams.get('renderer_env'); const pathname = `${id}${rendererEnvironment ? `?renderer_env=${rendererEnvironment}` : ''}`; + const togglesPromise = getToggles(service); + let message; let status; let json; @@ -49,6 +71,7 @@ const getPageData = async ({ page, getAgent, isAmp, + cache: bffCache, })); } catch (error: unknown) { ({ message, status } = error as FetchError); @@ -72,7 +95,7 @@ const getPageData = async ({ ? { pageData: json.data, status } : { error: message, status }; - const toggles = await getToggles(service); + const toggles = await togglesPromise; return { data, toggles }; };