Skip to content

Optional stampede protection for CacheManager.get_or_set() #66

Description

@allen0099

Summary

CacheManager.get_or_set() documents that it "does not provide stampede protection: concurrent misses for the same key may each invoke factory". When the factory is expensive (a slow query, an upstream API with rate limits) and the key is hot, an expiry turns into N simultaneous recomputations.

Proposal

An opt-in lock around the miss path, built on CacheLock (#64):

report = await cache.get_or_set(
    "report:daily",
    build_report,
    ttl=300,
    lock_ttl=30,        # enables protection; upper bound on one factory run
    wait_timeout=10,    # how long other callers wait for the winner
)
  1. On a miss, try to acquire a lock for the key (non-blocking).
  2. The winner re-checks the cache, runs factory, stores the value, and releases the lock.
  3. Everyone else polls the cache until the value appears or wait_timeout elapses.
  4. On timeout, fall back to running factory themselves, so a crashed winner degrades to today's behaviour instead of failing requests.

Without lock_ttl the behaviour is unchanged, so this is backward compatible.

Open questions

  • Polling interval and whether to back off.
  • Whether the @cache decorator's miss path should get the same option later (separate issue; it has to deal with response objects, not plain values).

Depends on: #64

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    cache-managerApplication-level CacheManagerenhancementNew feature or request

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions