@@ -376,29 +376,35 @@ export class DistributedCache<T> {
376376 }
377377
378378 /**
379- * Gets a value from the cache, or executes the load function if missing or stale.
380- * Employs both an in-memory Promise lock (L1) and a Redis Mutex (L2) to prevent Cache Stampedes.
379+ * Returns cached data when available, otherwise loads and stores fresh data.
380+ *
381+ * Uses a two-layer coordination strategy to reduce cache stampedes:
382+ * 1. Local Promise deduplication (L1) prevents duplicate fetches within the same instance.
383+ * 2. Redis mutex locking (L2) prevents duplicate fetches across distributed instances.
384+ *
385+ * `loadFn` receives the current cached value (or null) so callers can implement
386+ * stale refresh logic when needed.
381387 *
382388 * @param key - Cache key.
383- * @param loadFn - Async function to fetch the data. Receives the stale cached value if one exists .
384- * @param ttlMs - Time to live in milliseconds.
385- * @param shouldFetch - Optional predicate to force fetching even if a cache value exists (e.g. for stale delta sync) .
389+ * @param loadFn - Async function used to load fresh data.
390+ * @param ttlMs - Cache expiration time in milliseconds.
391+ * @param shouldFetch - Optional predicate that forces refresh even on cache hits .
386392 */
387393 async getOrSet (
388394 key : string ,
389395 loadFn : ( cached : T | null ) => Promise < T > ,
390396 ttlMs : number ,
391397 shouldFetch ?: ( cached : T ) => boolean
392398 ) : Promise < T > {
393- // 1. L1 & L2 Cache Check
399+ // Attempt to retrieve an existing value before triggering a refresh.
394400 const cached = await this . get ( key ) ;
395401
396- // If we have a cache hit and we don't need to force a refresh, return it early.
397402 if ( cached !== null && ( ! shouldFetch || ! shouldFetch ( cached ) ) ) {
398403 return cached ;
399404 }
400405
401- // 2. L1 Promise Deduping (Local Lock)
406+ // Join an existing in-flight request instead of creating duplicate fetches
407+ // within the same runtime instance.
402408 const pendingLocal = this . localLocks . get ( key ) ;
403409 if ( pendingLocal ) return pendingLocal ;
404410
@@ -417,7 +423,8 @@ export class DistributedCache<T> {
417423
418424 while ( Date . now ( ) - start < maxPollTime ) {
419425 try {
420- // Attempt to acquire Redis Mutex
426+ // NX: acquire only if lock doesn't already exist.
427+ // PX 10000: auto-expire lock after 10 seconds to avoid deadlocks.
421428 const lockRes = await fetch ( `${ this . redisUrl } /` , {
422429 method : 'POST' ,
423430 headers : {
@@ -430,12 +437,12 @@ export class DistributedCache<T> {
430437 if ( lockRes . ok ) {
431438 const lockData = await lockRes . json ( ) ;
432439 if ( lockData . result === 'OK' ) {
433- // Lock acquired! Execute loadFn.
434440 try {
435441 const freshData = await loadFn ( cached ) ;
436442 await this . set ( key , freshData , ttlMs ) ;
437443
438- // Release lock early
444+ // Release immediately after caching data instead of waiting
445+ // for lock expiration so other instances can continue sooner.
439446 await fetch ( `${ this . redisUrl } /` , {
440447 method : 'POST' ,
441448 headers : {
@@ -447,7 +454,8 @@ export class DistributedCache<T> {
447454
448455 return freshData ;
449456 } catch ( err ) {
450- // Release lock on error so others can retry
457+ // Remove lock even on failure so other instances don't wait
458+ // for the full lock timeout period.
451459 await fetch ( `${ this . redisUrl } /` , {
452460 method : 'POST' ,
453461 headers : {
@@ -471,7 +479,8 @@ export class DistributedCache<T> {
471479 // Lock not acquired. Wait and poll L2 cache.
472480 await new Promise ( ( resolve ) => setTimeout ( resolve , pollInterval ) ) ;
473481 const doubleCheck = await this . get ( key ) ;
474- // If doubleCheck satisfies the condition, return it
482+
483+ // Another instance may have already populated the cache while waiting.
475484 if ( doubleCheck !== null && ( ! shouldFetch || ! shouldFetch ( doubleCheck ) ) ) {
476485 return doubleCheck ;
477486 }
@@ -483,10 +492,11 @@ export class DistributedCache<T> {
483492 return finalFallback ;
484493 } ;
485494
486- // Execute with local Promise lock
495+ // Ensure local lock cleanup even if request execution fails.
487496 const promise = executeAndLock ( ) . finally ( ( ) => {
488497 this . localLocks . delete ( key ) ;
489498 } ) ;
499+
490500 this . localLocks . set ( key , promise ) ;
491501
492502 return promise ;
0 commit comments