From 53fa8cc945e4a7329993b9acdb7a05f10586a9b3 Mon Sep 17 00:00:00 2001 From: Sergei Zharinov <zharinov@users.noreply.github.com> Date: Tue, 28 Jan 2025 07:15:19 -0300 Subject: [PATCH] refactor(cache): Utils to calculate soft and hard TTL (#33844) --- lib/config/presets/index.ts | 2 +- lib/util/cache/package/decorator.ts | 27 ++++-------- lib/util/cache/package/ttl.ts | 43 +++++++++++++++++++ .../cache/package-http-cache-provider.spec.ts | 2 +- .../http/cache/package-http-cache-provider.ts | 28 ++++++------ 5 files changed, 69 insertions(+), 33 deletions(-) create mode 100644 lib/util/cache/package/ttl.ts diff --git a/lib/config/presets/index.ts b/lib/config/presets/index.ts index 349db47089..b482521ff4 100644 --- a/lib/config/presets/index.ts +++ b/lib/config/presets/index.ts @@ -7,7 +7,7 @@ import { logger } from '../../logger'; import { ExternalHostError } from '../../types/errors/external-host-error'; import * as memCache from '../../util/cache/memory'; import * as packageCache from '../../util/cache/package'; -import { getTtlOverride } from '../../util/cache/package/decorator'; +import { getTtlOverride } from '../../util/cache/package/ttl'; import { clone } from '../../util/clone'; import { regEx } from '../../util/regex'; import * as template from '../../util/template'; diff --git a/lib/util/cache/package/decorator.ts b/lib/util/cache/package/decorator.ts index 70be6ff6d0..ee7424ffe6 100644 --- a/lib/util/cache/package/decorator.ts +++ b/lib/util/cache/package/decorator.ts @@ -5,6 +5,7 @@ import { logger } from '../../../logger'; import type { Decorator } from '../../decorator'; import { decorate } from '../../decorator'; import { acquireLock } from '../../mutex'; +import { resolveTtlValues } from './ttl'; import type { DecoratorCachedRecord, PackageCacheNamespace } from './types'; import * as packageCache from '.'; @@ -91,17 +92,13 @@ export function cache<T>({ finalKey, ); - const ttlOverride = getTtlOverride(finalNamespace); - const softTtl = ttlOverride ?? ttlMinutes; - - const cacheHardTtlMinutes = GlobalConfig.get( - 'cacheHardTtlMinutes', - 7 * 24 * 60, - ); - let hardTtl = softTtl; - if (methodName === 'getReleases' || methodName === 'getDigest') { - hardTtl = Math.max(softTtl, cacheHardTtlMinutes); - } + const ttlValues = resolveTtlValues(finalNamespace, ttlMinutes); + const softTtl = ttlValues.softTtlMinutes; + const hardTtl = + methodName === 'getReleases' || methodName === 'getDigest' + ? ttlValues.hardTtlMinutes + : // Skip two-tier TTL for any intermediate data fetching + softTtl; let oldData: unknown; if (oldRecord) { @@ -148,11 +145,3 @@ export function cache<T>({ } }); } - -export function getTtlOverride(namespace: string): number | undefined { - const ttl: unknown = GlobalConfig.get('cacheTtlOverride', {})[namespace]; - if (is.number(ttl)) { - return ttl; - } - return undefined; -} diff --git a/lib/util/cache/package/ttl.ts b/lib/util/cache/package/ttl.ts new file mode 100644 index 0000000000..5ed8f0b523 --- /dev/null +++ b/lib/util/cache/package/ttl.ts @@ -0,0 +1,43 @@ +import is from '@sindresorhus/is'; +import { GlobalConfig } from '../../../config/global'; +import type { PackageCacheNamespace } from './types'; + +export function getTtlOverride( + namespace: PackageCacheNamespace, +): number | undefined { + const ttl = GlobalConfig.get('cacheTtlOverride', {})[namespace]; + if (is.number(ttl)) { + return ttl; + } + return undefined; +} + +export interface TTLValues { + /** TTL for serving cached value without hitting the server */ + softTtlMinutes: number; + + /** TTL for serving stale cache when upstream responds with errors */ + hardTtlMinutes: number; +} + +/** + * Apply user-configured overrides and return the final values for soft/hard TTL. + * + * @param namespace Cache namespace + * @param ttlMinutes TTL value configured in Renovate codebase + * @returns + */ +export function resolveTtlValues( + namespace: PackageCacheNamespace, + ttlMinutes: number, +): TTLValues { + const softTtlMinutes = getTtlOverride(namespace) ?? ttlMinutes; + + const cacheHardTtlMinutes = GlobalConfig.get( + 'cacheHardTtlMinutes', + 7 * 24 * 60, + ); + const hardTtlMinutes = Math.max(softTtlMinutes, cacheHardTtlMinutes); + + return { softTtlMinutes, hardTtlMinutes }; +} diff --git a/lib/util/http/cache/package-http-cache-provider.spec.ts b/lib/util/http/cache/package-http-cache-provider.spec.ts index 00c5121ed4..3b01971528 100644 --- a/lib/util/http/cache/package-http-cache-provider.spec.ts +++ b/lib/util/http/cache/package-http-cache-provider.spec.ts @@ -50,7 +50,7 @@ describe('util/http/cache/package-http-cache-provider', () => { }; const cacheProvider = new PackageHttpCacheProvider({ namespace: '_test-namespace', - softTtlMinutes: 0, + ttlMinutes: 0, }); httpMock.scope(url).get('').reply(200, 'new response'); diff --git a/lib/util/http/cache/package-http-cache-provider.ts b/lib/util/http/cache/package-http-cache-provider.ts index 8d36bc110e..6b79e9ce42 100644 --- a/lib/util/http/cache/package-http-cache-provider.ts +++ b/lib/util/http/cache/package-http-cache-provider.ts @@ -1,30 +1,32 @@ import { DateTime } from 'luxon'; import { get, set } from '../../cache/package'; // Import the package cache functions +import { resolveTtlValues } from '../../cache/package/ttl'; import type { PackageCacheNamespace } from '../../cache/package/types'; +import { HttpCacheStats } from '../../stats'; import type { HttpResponse } from '../types'; import { AbstractHttpCacheProvider } from './abstract-http-cache-provider'; import type { HttpCache } from './schema'; export interface PackageHttpCacheProviderOptions { namespace: PackageCacheNamespace; - softTtlMinutes?: number; - hardTtlMinutes?: number; + ttlMinutes?: number; } export class PackageHttpCacheProvider extends AbstractHttpCacheProvider { private namespace: PackageCacheNamespace; - private softTtlMinutes = 15; - private hardTtlMinutes = 24 * 60; - - constructor({ - namespace, - softTtlMinutes, - hardTtlMinutes, - }: PackageHttpCacheProviderOptions) { + + private softTtlMinutes: number; + private hardTtlMinutes: number; + + constructor({ namespace, ttlMinutes = 15 }: PackageHttpCacheProviderOptions) { super(); this.namespace = namespace; - this.softTtlMinutes = softTtlMinutes ?? this.softTtlMinutes; - this.hardTtlMinutes = hardTtlMinutes ?? this.hardTtlMinutes; + const { softTtlMinutes, hardTtlMinutes } = resolveTtlValues( + this.namespace, + ttlMinutes, + ); + this.softTtlMinutes = softTtlMinutes; + this.hardTtlMinutes = hardTtlMinutes; } async load(url: string): Promise<unknown> { @@ -47,9 +49,11 @@ export class PackageHttpCacheProvider extends AbstractHttpCacheProvider { const deadline = cachedAt.plus({ minutes: this.softTtlMinutes }); const now = DateTime.now(); if (now >= deadline) { + HttpCacheStats.incLocalMisses(url); return null; } + HttpCacheStats.incLocalHits(url); return cached.httpResponse as HttpResponse<T>; } } -- GitLab