SickGear/lib/diskcache/stampede.py

78 lines
2 KiB
Python

"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