You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Document the recommended pattern for preventing cache stampedes using
CacheLockProvider with the existing cache-aside pattern in both the
caching guide and the Foundatio agent skill gotchas.
Copy file name to clipboardExpand all lines: .agents/skills/foundatio/SKILL.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -302,6 +302,7 @@ public class OrderServiceTests : TestLoggerBase
302
302
-**Dispose streams and locks**: `ILock` is `IAsyncDisposable` -- use `await using`. Streams from `GetFileStreamAsync` are `IDisposable` -- use `using var`.
303
303
-**Cache TTL floor**: Expiration values below 5ms are treated as already-expired and the key is silently removed. If you compute TTL dynamically (e.g., `expiresAt - now`), guard against near-zero values.
304
304
-**Cache `GetAsync` returns `CacheValue<T>`**: Check `result.HasValue` before accessing `result.Value`. A missing key returns `HasValue = false`, not an exception.
305
+
-**Cache stampede (thundering herd)**: The cache-aside pattern (`Get` -> miss -> load -> `Set`) is vulnerable to stampedes when a popular key expires and many callers regenerate simultaneously. Use `CacheLockProvider` to serialize regeneration: acquire a lock keyed on the cache key, double-check the cache after acquiring, and only then call the backing store. See the [Cache Stampede Protection](https://foundatio.readthedocs.io/guide/caching.html#cache-stampede-protection) docs for the full pattern.
305
306
-**Queue auto-complete**: `QueueJobBase<T>` auto-completes entries based on `JobResult` by default. Set `AutoComplete = false` only when you need manual `CompleteAsync()`/`AbandonAsync()` control. Manual `DequeueAsync` does NOT auto-complete.
306
307
-**JobWithLockBase vs manual locking**: Use `JobWithLockBase` when the entire run must be single-instance (leader election). Use manual `ILockProvider.AcquireAsync` inside `JobBase` for finer-grained locking within a job.
307
308
-**JobContext.RenewLockAsync**: Call in long-running jobs (both `JobBase` and `QueueJobBase`) to prevent lock expiration mid-processing.
Copy file name to clipboardExpand all lines: docs/guide/caching.md
+57Lines changed: 57 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -579,6 +579,63 @@ public async Task<User> GetUserAsync(int userId)
579
579
}
580
580
```
581
581
582
+
### Cache Stampede Protection
583
+
584
+
The cache-aside pattern above is vulnerable to **cache stampedes** (also called the thundering herd problem). When a popular key expires, many concurrent requests all see a cache miss simultaneously and each independently loads the same data from the backing store. For an expensive query that takes two seconds to run, 50 concurrent callers means 50 identical database queries instead of one.
585
+
586
+
Use [`CacheLockProvider`](/guide/locks) to serialize cache regeneration so only one caller loads the data while others wait:
1.**Lock on the cache key.** Use a lock name derived from the cache key (e.g., `cache-load:product:42`) so different keys are loaded concurrently while the same key is serialized.
632
+
2.**Double-check after acquiring.** Another caller may have populated the cache while you were waiting for the lock. Always re-read before loading from the backing store.
633
+
3.**Lock expiration as a safety net.** Set `timeUntilExpires` to a value longer than the expected load time. If the loader crashes, the lock auto-expires and the next caller retries.
634
+
635
+
::: tip CacheLockProvider + IMessageBus
636
+
When `CacheLockProvider` is configured with an `IMessageBus`, waiting callers are notified instantly via pub/sub when the lock is released. Without a message bus, lock release falls back to polling. For stampede protection where multiple callers are blocked on the same lock, the message bus significantly reduces wait time.
0 commit comments