diff --git a/src/cache.js b/src/cache.js index 4da5246161acc9e2e71a4c5a00ad300e0ffeb853..9deb51b83d5d651dfc1ee1a4a19cfcc8dab96a67 100644 --- a/src/cache.js +++ b/src/cache.js @@ -1,8 +1,16 @@ +import { createWritable } from './files.js' import { logger } from './logger.js' import * as redis from './redis.js' +import { getRedisKey } from './util.js' +import { Gauge } from 'prom-client' const cache = {} +export const fileCacheSizeGauge = new Gauge({ + name: 'file_cache_size', + help: 'Number of entries in file cache' +}) + export function set (key, value) { logger.debug(`[Cache] Set ${key}`) if (cache[key] === value) return @@ -16,6 +24,7 @@ export async function clear () { for (const prop of Object.getOwnPropertyNames(cache)) { delete cache[prop] } + fileCacheSizeGauge.reset() } export function get (key, fallback) { @@ -51,4 +60,53 @@ export function get (key, fallback) { return promise } -export function getCache () { return cache } +export function getFile ({ name, version }, fallback) { + const key = getRedisKey({ version, name }) + + // try to get the file synchronously. + const data = cache[key] + if (data) { + logger.debug(`[Cache] Resolve file from memory: ${key}`) + return data + } + + // if synchronously does not work, store the async promise for further requests + const promise = (async () => { + const bodyKey = getRedisKey({ version, name: `${name}:body` }) + const metaKey = getRedisKey({ version, name: `${name}:meta` }) + + if (redis.isEnabled()) { + const [body, meta = '{}'] = await Promise.all([ + redis.client.getBuffer(bodyKey), + redis.client.get(metaKey) + ]) + + if (body) { + logger.debug(`[Cache] Resolve file from redis: ${key}`) + fileCacheSizeGauge.inc() + return (cache[key] = { body, ...JSON.parse(meta) }) + } + } + + const dataFromServer = await fallback({ version, name }).catch(err => { + if (err.status !== 404) delete cache[key] + throw err + }) + + if (redis.isEnabled()) { + logger.debug(`[Cache] Store file in redis: ${key}`) + const { body, ...rest } = dataFromServer + redis.client.set(bodyKey, createWritable(body)) + redis.client.set(metaKey, JSON.stringify(rest)) + } + + // overwrite cache with synchronous data + logger.debug(`[Cache] Store file in memory: ${key}`) + fileCacheSizeGauge.inc() + return (cache[key] = dataFromServer) + })() + + // temporary set to promise + cache[key] = promise + return promise +} diff --git a/src/files.js b/src/files.js index cc9703c909242e077a874aa2b6c87a8153b9185d..7fbdfb91e0d7cfe3e16718180174a55fed750045 100644 --- a/src/files.js +++ b/src/files.js @@ -1,13 +1,11 @@ import { configMap } from './configMap.js' -import { getRedisKey, isJSFile } from './util.js' +import { isJSFile } from './util.js' import { getCSSDependenciesFor, getViteManifests } from './manifests.js' import * as cache from './cache.js' import { logger } from './logger.js' import { NotFoundError } from './errors.js' -import * as redis from './redis.js' import zlib from 'node:zlib' import { promisify } from 'node:util' -import { Gauge } from 'prom-client' const gzip = promisify(zlib.gzip) @@ -15,11 +13,6 @@ const compressFileSize = Number(process.env.COMPRESS_FILE_SIZE) const compressionMimeTypes = (process.env.COMPRESS_FILE_TYPES || '').replace(/([.+*?^$()[\]{}|])/g, '\\$1').split(' ') const compressionWhitelistRegex = new RegExp(`^(${compressionMimeTypes.join('|')})($|;)`, 'i') -export const fileCacheSizeGauge = new Gauge({ - name: 'file_cache_size', - help: 'Number of entries in file cache' -}) - export function createWritable (body) { if (typeof body !== 'string' && !(body instanceof Buffer)) return JSON.stringify(body) return body @@ -81,51 +74,7 @@ export async function fetchFileWithHeaders ({ path, version }) { } export function getFile ({ version, path }) { - const key = getRedisKey({ version, name: `${path}` }) - - // try to get the file synchronously. - const data = cache.getCache()[key] - if (data) { - logger.debug(`[Files] Resolve from memory: ${key}`) - return data - } - - // if synchronously does not work, store the async promise for further requests - const promise = (async () => { - const bodyKey = getRedisKey({ version, name: `${path}:body` }) - const metaKey = getRedisKey({ version, name: `${path}:meta` }) - - if (redis.isEnabled()) { - const [body, meta = '{}'] = await Promise.all([ - redis.client.getBuffer(bodyKey), - redis.client.get(metaKey) - ]) - - if (body) { - logger.debug(`[Files] Resolve from redis: ${key}`) - fileCacheSizeGauge.inc() - return (cache.getCache()[key] = { body, ...JSON.parse(meta) }) - } - } - - const dataFromServer = await fetchFileWithHeaders({ version, path }).catch(err => { - if (err.status !== 404) delete cache.getCache()[key] - throw err - }) - if (redis.isEnabled()) { - logger.debug(`[Files] Store in redis: ${key}`) - const { body, ...rest } = dataFromServer - redis.client.set(bodyKey, createWritable(body)) - redis.client.set(metaKey, JSON.stringify(rest)) - } - - // overwrite cache with synchronous data - logger.debug(`[Files] Store in memory: ${key}`) - fileCacheSizeGauge.inc() - return (cache.getCache()[key] = dataFromServer) - })() - - // temporary set to promise - cache.getCache()[key] = promise - return promise + return cache.getFile({ name: path, version }, ({ name: path, version }) => { + return fetchFileWithHeaders({ version, path }) + }) }