Skip to content

Latest commit

 

History

History
117 lines (90 loc) · 4.31 KB

File metadata and controls

117 lines (90 loc) · 4.31 KB

Behavior Semantics

This document defines runtime behavior for TTL/default TTL, stale and refresh-ahead flows, and lock/rate-limit guarantees.

Configuration references in this document use the current layout:

  • cache.NewCacheWithTTL(...) for cache-level default TTL
  • root store constructors with config (NewMemoryStoreWithConfig, NewFileStoreWithConfig, NewNullStoreWithConfig)
  • optional driver configs that embed cachecore.BaseConfig (for example rediscache.Config, natscache.Config)

TTL And Default TTL Matrix

Cache resolves TTL with:

  • if ttl > 0: use ttl
  • otherwise: use Cache.defaultTTL (configured by NewCacheWithTTL or the store's cachecore.BaseConfig.DefaultTTL)
API TTL Input Effective TTL Written
Set / SetContext ttl > 0 ttl
Set / SetContext ttl <= 0 defaultTTL
SetString / SetStringContext same as Set same as Set
SetJSON / SetJSONContext same as Set same as Set
Add / AddContext ttl > 0 ttl
Add / AddContext ttl <= 0 defaultTTL
Increment / IncrementContext ttl > 0 ttl
Increment / IncrementContext ttl <= 0 defaultTTL
Decrement / DecrementContext ttl > 0 ttl
Decrement / DecrementContext ttl <= 0 defaultTTL
BatchSetBytes / BatchSetBytesContext each key uses same rule as Set per-key resolved TTL
Remember* (non-stale) forwards ttl to SetContext resolved as above
RememberStale* primary key forwards ttl to SetContext resolved as above
RateLimit* bucket key uses window as TTL exactly window (must be > 0)
TryLock* lock key uses provided ttl exactly provided ttl (must be > 0)
RefreshAhead* primary key requires ttl > 0 exactly ttl
RefreshAhead* metadata key requires ttl > 0 exactly ttl

Notes:

  • RefreshAhead* does not allow fallback TTL; it returns an error when ttl <= 0.
  • RateLimit* and lock helpers enforce positive TTL/window and return errors otherwise.

Stale And Refresh-Ahead Semantics

RememberStale*

  • Read order:
    • read fresh key (key)
    • if hit: return it (usedStale=false)
    • if miss: call loader
  • Loader success path:
    • write fresh key with ttl
    • write stale key (key + ":__stale") with staleTTL
  • Loader error path:
    • attempt stale key read
    • if stale exists: return stale (usedStale=true, err=nil)
    • else: return loader error (and stale read error joined when present)

staleTTL behavior:

  • if staleTTL <= 0, code sets staleTTL = ttl
  • stale key is written only if resulting staleTTL > 0
  • practical implication: if both input ttl <= 0 and staleTTL <= 0, fresh key still uses defaultTTL, but stale key is not written

RefreshAhead*

  • Requires ttl > 0 and refreshAhead > 0.
  • On cache miss:
    • synchronously compute via loader
    • write value key
    • write metadata key (key + ":__refresh_exp") storing expiration timestamp
  • On cache hit:
    • return current value immediately
    • optionally trigger async refresh if key is near expiry

Async refresh trigger conditions:

  • metadata key exists and parses
  • time.Until(expiry) <= refreshAhead
  • per-key refresh lock acquired ("__refresh_lock:" + key)

Refresh-ahead caveats:

  • Uses goroutine with background context for async refresh.
  • Metadata key drives near-expiry detection; keys not populated by RefreshAhead* will not auto-refresh until metadata exists.

Lock And Rate-Limit Guarantees

Locking (TryLock, Lock, Unlock)

  • Mechanism: lock key ("__lock:"+key) using Add (set-if-absent).
  • Scope:
    • distributed only when backend is shared/distributed
    • process-local when backend is process/local only (for example memory)
  • Guarantee:
    • mutual exclusion best-effort based on backend atomic Add
    • lock expires by TTL
  • Important caveat:
    • Unlock deletes lock key without owner token validation
    • use short TTLs and keep critical sections bounded

Rate limiting (RateLimit, RateLimitContext)

  • Algorithm: fixed-window counter.
  • Keying: bucketKey = key + ":" + floor(now/window).
  • Each call increments bucket counter with TTL=window.
  • Returns RateLimitStatus:
    • Allowed (count <= limit)
    • Count (current bucket count)
    • Remaining
    • ResetAt

Scope and semantics:

  • Global/shared only when backend is shared/distributed.
  • Process-local when backend is local/in-process.
  • Boundary behavior: fixed-window rollover at window boundary (not sliding-window smoothing).