maloja/maloja/database/dbcache.py

135 lines
3.3 KiB
Python
Raw Normal View History

2022-02-15 07:20:27 +03:00
# the more generalized caching for DB queries
# mostly to avoid long loading times for pages that show lots of information
# that changes very infrequently or not at all
import lru
2022-02-15 07:52:44 +03:00
import psutil
2022-02-15 07:20:27 +03:00
import json
from doreah.regular import runhourly
from doreah.logging import log
from ..pkg_global.conf import malojaconfig
2022-02-15 07:20:27 +03:00
2022-02-15 07:52:44 +03:00
HIGH_NUMBER = 1000000
cache = lru.LRU(HIGH_NUMBER)
entitycache = lru.LRU(HIGH_NUMBER)
2022-02-15 07:52:44 +03:00
hits, misses = 0, 0
2022-02-15 07:20:27 +03:00
2022-02-18 10:05:23 +03:00
2022-02-15 07:20:27 +03:00
@runhourly
2022-02-20 06:03:21 +03:00
def maintenance():
2022-02-27 04:51:44 +03:00
if malojaconfig['USE_GLOBAL_CACHE']:
print_stats()
trim_cache()
2022-02-15 07:52:44 +03:00
2022-02-20 06:03:21 +03:00
def print_stats():
2022-03-27 20:52:51 +03:00
log(f"Cache Size: {len(cache)} [{len(entitycache)} E], System RAM Utilization: {psutil.virtual_memory().percent}%, Cache Hits: {hits}/{hits+misses}")
#print("Full rundown:")
#import sys
#for k in cache.keys():
# print(f"\t{k}\t{sys.getsizeof(cache[k])}")
2022-02-20 06:03:21 +03:00
2022-02-15 07:20:27 +03:00
def cached_wrapper(inner_func):
2022-02-15 07:52:44 +03:00
2022-02-27 04:51:44 +03:00
if not malojaconfig['USE_GLOBAL_CACHE']: return inner_func
2022-02-18 10:26:28 +03:00
def outer_func(*args,**kwargs):
2022-02-26 23:30:06 +03:00
if 'dbconn' in kwargs:
conn = kwargs.pop('dbconn')
else:
conn = None
2022-02-15 07:52:44 +03:00
global hits, misses
2022-02-18 10:26:28 +03:00
key = (serialize(args),serialize(kwargs), inner_func, kwargs.get("since"), kwargs.get("to"))
2022-02-15 07:20:27 +03:00
2022-02-18 10:05:23 +03:00
if key in cache:
2022-02-15 07:52:44 +03:00
hits += 1
2022-02-15 07:20:27 +03:00
return cache.get(key)
else:
2022-02-15 07:52:44 +03:00
misses += 1
2022-02-26 23:30:06 +03:00
result = inner_func(*args,**kwargs,dbconn=conn)
2022-02-15 07:20:27 +03:00
cache[key] = result
return result
return outer_func
# cache for functions that call with a whole list of entity ids
# we don't want a new cache entry for every single combination, but keep a common
# cache that's aware of what we're calling
def cached_wrapper_individual(inner_func):
if not malojaconfig['USE_GLOBAL_CACHE']: return inner_func
def outer_func(set_arg,**kwargs):
if 'dbconn' in kwargs:
conn = kwargs.pop('dbconn')
else:
conn = None
2022-03-27 20:52:51 +03:00
#global hits, misses
result = {}
for id in set_arg:
if (inner_func,id) in entitycache:
result[id] = entitycache[(inner_func,id)]
2022-03-27 20:52:51 +03:00
#hits += 1
else:
2022-03-27 23:02:50 +03:00
pass
2022-03-27 20:52:51 +03:00
#misses += 1
remaining = inner_func(set(e for e in set_arg if e not in result),dbconn=conn)
for id in remaining:
entitycache[(inner_func,id)] = remaining[id]
result[id] = remaining[id]
return result
return outer_func
2022-02-15 07:20:27 +03:00
def invalidate_caches(scrobbletime):
2022-02-27 04:51:44 +03:00
if malojaconfig['USE_GLOBAL_CACHE']:
cleared, kept = 0, 0
for k in cache.keys():
# VERY BIG TODO: differentiate between None as in 'unlimited timerange' and None as in 'time doesnt matter here'!
if (k[3] is None or scrobbletime >= k[3]) and (k[4] is None or scrobbletime <= k[4]):
cleared += 1
del cache[k]
else:
kept += 1
log(f"Invalidated {cleared} of {cleared+kept} DB cache entries")
2022-02-15 07:20:27 +03:00
def invalidate_entity_cache():
entitycache.clear()
2022-02-15 07:20:27 +03:00
2022-02-15 07:52:44 +03:00
def trim_cache():
ramprct = psutil.virtual_memory().percent
if ramprct > malojaconfig["DB_MAX_MEMORY"]:
2022-02-15 09:18:26 +03:00
log(f"{ramprct}% RAM usage, reducing caches!")
2022-04-04 17:14:38 +03:00
ratio = 0.6
2022-02-15 07:52:44 +03:00
targetsize = max(int(len(cache) * ratio),100)
2022-02-19 10:02:07 +03:00
cache.set_size(targetsize)
cache.set_size(HIGH_NUMBER)
2022-02-15 09:18:26 +03:00
log(f"New RAM usage: {psutil.virtual_memory().percent}%")
2022-02-15 07:20:27 +03:00
2022-02-18 10:05:23 +03:00
2022-02-15 07:20:27 +03:00
def serialize(obj):
try:
return serialize(obj.hashable())
except:
try:
return json.dumps(obj)
except:
2022-02-18 10:26:28 +03:00
if isinstance(obj, (list, tuple, set)):
2022-02-15 07:20:27 +03:00
return "[" + ",".join(serialize(o) for o in obj) + "]"
elif isinstance(obj,dict):
return "{" + ",".join(serialize(o) + ":" + serialize(obj[o]) for o in obj) + "}"
return json.dumps(obj.hashable())