You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

78 lines
2.0 KiB

"Stampede barrier implementation."
import functools as ft
import math
import random
import tempfile
import time
from .core import Cache, ENOVAL
class StampedeBarrier(object):
"""Stampede barrier mitigates cache stampedes.
Cache stampedes are also known as dog-piling, cache miss storm, cache
choking, or the thundering herd problem.
Based on research by Vattani, A.; Chierichetti, F.; Lowenstein, K. (2015),
Optimal Probabilistic Cache Stampede Prevention,
VLDB, pp. 886?897, ISSN 2150-8097
Example:
```python
stampede_barrier = StampedeBarrier('/tmp/user_data', expire=3)
@stampede_barrier
def load_user_info(user_id):
return database.lookup_user_info_by_id(user_id)
```
"""
# pylint: disable=too-few-public-methods
def __init__(self, cache=None, expire=None):
if isinstance(cache, Cache):
pass
elif cache is None:
cache = Cache(tempfile.mkdtemp())
else:
cache = Cache(cache)
self._cache = cache
self._expire = expire
def __call__(self, func):
cache = self._cache
expire = self._expire
@ft.wraps(func)
def wrapper(*args, **kwargs):
"Wrapper function to cache function result."
key = (args, kwargs)
try:
result, expire_time, delta = cache.get(
key, default=ENOVAL, expire_time=True, tag=True
)
if result is ENOVAL:
raise KeyError
now = time.time()
ttl = expire_time - now
if (-delta * math.log(random.random())) < ttl:
return result
except KeyError:
pass
now = time.time()
result = func(*args, **kwargs)
delta = time.time() - now
cache.set(key, result, expire=expire, tag=delta)
return result
return wrapper