|
private readLocalWithResolvedConfig<T>(key: DialCacheKey, layerConfig: ResolvedLayerConfig): CacheGetResult<T> { |
|
const start = performance.now(); |
|
try { |
|
const result = this.localCache.getWithResolvedConfig<T>(key, layerConfig); |
|
this.metrics?.request(labelsFor(key, CacheLayer.LOCAL)); |
|
this.metrics?.observeGet(labelsFor(key, CacheLayer.LOCAL), elapsedSeconds(start)); |
|
if (result.status === "miss") { |
|
this.metrics?.miss(labelsFor(key, CacheLayer.LOCAL)); |
|
} |
|
return result; |
|
} catch (error) { |
|
this.logger.error("Error getting value from local cache", error); |
|
this.recordError(key, CacheLayer.LOCAL, "cache_read"); |
|
this.metrics?.disabled({ ...labelsFor(key, CacheLayer.LOCAL), reason: "config_error" }); |
|
return { status: "disabled", reason: "config_error" } as const; |
|
} |
|
} |
|
|
|
private async resolveRemoteLayerConfig(key: DialCacheKey, keyConfig: DialCacheKeyConfig | null) { |
|
try { |
|
const result = await resolveLayerConfigResult({ |
|
config: keyConfig, |
|
key, |
|
layer: CacheLayer.REMOTE, |
|
rampSampler: this.rampSampler, |
|
}); |
|
if (result.status === "disabled") { |
|
this.metrics?.disabled({ ...labelsFor(key, CacheLayer.REMOTE), reason: result.reason }); |
|
} |
|
return result; |
|
} catch (error) { |
|
this.logger.warn("Error resolving Redis cache config", error); |
|
this.recordError(key, CacheLayer.REMOTE, "config_resolution"); |
|
return { status: "disabled", reason: "config_error", ...(key.trackForInvalidation ? { skipCacheWrite: true } : {}) } as const; |
|
} |
|
} |
|
|
|
private async readRemoteWithResolvedConfig<T>(redisCache: RedisCache, key: DialCacheKey, layerConfig: ResolvedLayerConfig) { |
|
const start = performance.now(); |
|
try { |
|
const result = await redisCache.getWithResolvedConfig<T>(key, layerConfig); |
|
this.metrics?.request(labelsFor(key, CacheLayer.REMOTE)); |
|
this.metrics?.observeGet(labelsFor(key, CacheLayer.REMOTE), elapsedSeconds(start)); |
|
if (result.status === "miss") { |
|
this.metrics?.miss(labelsFor(key, CacheLayer.REMOTE)); |
|
} |
|
return result; |
|
} catch (error) { |
|
this.logger.warn("Error getting value from Redis cache", error); |
|
return { |
|
status: "disabled", |
|
reason: "config_error", |
|
...(key.trackForInvalidation ? { skipCacheWrite: true } : {}), |
|
} as const; |
|
} |
|
} |
|
|
|
private async putLocalFailOpen<T>(key: DialCacheKey, value: T, config?: { readonly ttlSec: number }): Promise<void> { |
|
try { |
|
await this.localCache.put(key, value, config); |
|
} catch (error) { |
|
this.logger.warn("Error putting value in local cache", error); |
|
this.recordError(key, CacheLayer.LOCAL, "cache_write"); |
|
} |
|
} |
|
|
|
private async callFallback<T>(labels: CacheMetricLabels, fallback: () => Promise<T>): Promise<T> { |
|
const start = performance.now(); |
|
try { |
|
return await fallback(); |
|
} catch (error) { |
|
this.metrics?.error({ ...labels, error: "fallback", inFallback: true }); |
|
throw error; |
|
} finally { |
|
this.metrics?.observeFallback(labels, elapsedSeconds(start)); |
|
} |
Priority
P2 — Medium — benchmark-led hot-path optimization after production safety guardrails.
Current state
Each enabled invocation still constructs a key, resolves configuration, allocates its fallback closure, and takes clocks on measured cache/fallback paths. With metrics omitted, optional chaining skips adapter calls and most label construction, but the clocks and built-in provider call/await remain.
#69 expanded the committed benchmark to five semantic scenarios and reported an informational base-versus-head overhead of about 0.10 microseconds per process-local hit. There is still no stable local-hit budget, environment-normalized baseline, or regression threshold.
Current evidence:
DialCache/src/dialcache.ts
Lines 252 to 309 in 34a45fa
DialCache/src/dialcache.ts
Lines 508 to 583 in 34a45fa
Scope
Profile first, then remove only measured hot-path work:
Out of scope
Acceptance criteria