##// END OF EJS Templates
caches: new cache + archive cache implementation
super-admin -
r1121:2809dfc5 python3
parent child Browse files
Show More
@@ -0,0 +1,72 b''
1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 # Copyright (C) 2014-2020 RhodeCode GmbH
3 #
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
18 import logging
19 import os
20 import diskcache
21
22 log = logging.getLogger(__name__)
23
24 cache_meta = None
25
26
27 def get_archival_config(config):
28 final_config = {
29 'archive_cache.eviction_policy': 'least-frequently-used'
30 }
31
32 for k, v in config.items():
33 if k.startswith('archive_cache'):
34 final_config[k] = v
35
36 return final_config
37
38
39 def get_archival_cache_store(config):
40
41 global cache_meta
42 if cache_meta is not None:
43 return cache_meta
44
45 config = get_archival_config(config)
46
47 archive_cache_dir = config['archive_cache.store_dir']
48 archive_cache_size_gb = config['archive_cache.cache_size_gb']
49 archive_cache_shards = config['archive_cache.cache_shards']
50 archive_cache_eviction_policy = config['archive_cache.eviction_policy']
51
52 log.debug('Initializing archival cache instance under %s', archive_cache_dir)
53
54 # check if it's ok to write, and re-create the archive cache
55 if not os.path.isdir(archive_cache_dir):
56 os.makedirs(archive_cache_dir, exist_ok=True)
57
58 d_cache = diskcache.FanoutCache(
59 archive_cache_dir, shards=archive_cache_shards,
60 cull_limit=0, # manual eviction required
61 size_limit=archive_cache_size_gb * 1024 * 1024 * 1024,
62 eviction_policy=archive_cache_eviction_policy,
63 timeout=30
64 )
65 cache_meta = d_cache
66 return cache_meta
67
68
69 def includeme(config):
70 # init our cache at start, for vcsserver we don't init at runtime
71 # because our cache config is sent via wire on make archive call, this call just lazy-enables the client
72 return
@@ -52,6 +52,10 b' register_backend('
52 52 log = logging.getLogger(__name__)
53 53
54 54
55 CLEAR_DELETE = 'delete'
56 CLEAR_INVALIDATE = 'invalidate'
57
58
55 59 def async_creation_runner(cache, somekey, creator, mutex):
56 60
57 61 def runner():
@@ -125,8 +125,11 b' class FileNamespaceBackend(PickleSeriali'
125 125 def __str__(self):
126 126 return self.__repr__()
127 127
128 def _get_keys_pattern(self, prefix: bytes = b''):
129 return b'%b:%b' % (safe_bytes(self.key_prefix), safe_bytes(prefix))
130
128 131 def list_keys(self, prefix: bytes = b''):
129 prefix = b'%b:%b' % (safe_bytes(self.key_prefix), safe_bytes(prefix))
132 prefix = self._get_keys_pattern(prefix)
130 133
131 134 def cond(dbm_key: bytes):
132 135 if not prefix:
@@ -185,8 +188,11 b' class BaseRedisBackend(redis_backend.Red'
185 188 )
186 189 self.reader_client = self.writer_client
187 190
188 def list_keys(self, prefix=''):
189 prefix = f'{self.key_prefix}:{prefix}*'
191 def _get_keys_pattern(self, prefix: bytes = b''):
192 return b'%b:%b*' % (safe_bytes(self.key_prefix), safe_bytes(prefix))
193
194 def list_keys(self, prefix: bytes = b''):
195 prefix = self._get_keys_pattern(prefix)
190 196 return self.reader_client.keys(prefix)
191 197
192 198 def get_store(self):
@@ -50,11 +50,13 b' class RhodeCodeCacheRegion(CacheRegion):'
50 50 And it's faster in cases we don't ever want to compute cached values
51 51 """
52 52 expiration_time_is_callable = callable(expiration_time)
53 if not namespace:
54 namespace = getattr(self, '_default_namespace', None)
53 55
54 56 if function_key_generator is None:
55 57 function_key_generator = self.function_key_generator
56 58
57 def get_or_create_for_user_func(key_generator, user_func, *arg, **kw):
59 def get_or_create_for_user_func(func_key_generator, user_func, *arg, **kw):
58 60
59 61 if not condition:
60 62 log.debug('Calling un-cached method:%s', user_func.__name__)
@@ -64,7 +66,7 b' class RhodeCodeCacheRegion(CacheRegion):'
64 66 log.debug('un-cached method:%s took %.4fs', user_func.__name__, total)
65 67 return result
66 68
67 key = key_generator(*arg, **kw)
69 key = func_key_generator(*arg, **kw)
68 70
69 71 timeout = expiration_time() if expiration_time_is_callable \
70 72 else expiration_time
@@ -139,36 +141,36 b' def compute_key_from_params(*args):'
139 141 return sha1(safe_bytes("_".join(map(str, args))))
140 142
141 143
144 def custom_key_generator(backend, namespace, fn):
145 func_name = fn.__name__
146
147 def generate_key(*args):
148 backend_pref = getattr(backend, 'key_prefix', None) or 'backend_prefix'
149 namespace_pref = namespace or 'default_namespace'
150 arg_key = compute_key_from_params(*args)
151 final_key = f"{backend_pref}:{namespace_pref}:{func_name}_{arg_key}"
152
153 return final_key
154
155 return generate_key
156
157
142 158 def backend_key_generator(backend):
143 159 """
144 160 Special wrapper that also sends over the backend to the key generator
145 161 """
146 162 def wrapper(namespace, fn):
147 return key_generator(backend, namespace, fn)
163 return custom_key_generator(backend, namespace, fn)
148 164 return wrapper
149 165
150 166
151 def key_generator(backend, namespace, fn):
152 func_name = fn.__name__
153
154 def generate_key(*args):
155 backend_prefix = getattr(backend, 'key_prefix', None) or 'backend_prefix'
156 namespace_pref = namespace or 'default_namespace'
157 arg_key = compute_key_from_params(*args)
158 final_key = f"{backend_prefix}:{namespace_pref}:{func_name}_{arg_key}"
159
160 return final_key
161
162 return generate_key
163
164
165 167 def get_or_create_region(region_name, region_namespace: str = None):
166 168 from vcsserver.lib.rc_cache.backends import FileNamespaceBackend
167 169
168 170 region_obj = region_meta.dogpile_cache_regions.get(region_name)
169 171 if not region_obj:
170 172 reg_keys = list(region_meta.dogpile_cache_regions.keys())
171 raise OSError(f'Region `{region_name}` not in configured: {reg_keys}.')
173 raise EnvironmentError(f'Region `{region_name}` not in configured: {reg_keys}.')
172 174
173 175 region_uid_name = f'{region_name}:{region_namespace}'
174 176
@@ -212,21 +214,29 b' def get_or_create_region(region_name, re'
212 214 log.debug('configuring new region: %s', region_uid_name)
213 215 region_obj = region_meta.dogpile_cache_regions[region_namespace] = new_region
214 216
217 region_obj._default_namespace = region_namespace
215 218 return region_obj
216 219
217 220
218 def clear_cache_namespace(cache_region: str | RhodeCodeCacheRegion, cache_namespace_uid: str, invalidate: bool = False, hard: bool = False):
221 def clear_cache_namespace(cache_region: str | RhodeCodeCacheRegion, cache_namespace_uid: str, method: str):
222 from . import CLEAR_DELETE, CLEAR_INVALIDATE
223
219 224 if not isinstance(cache_region, RhodeCodeCacheRegion):
220 225 cache_region = get_or_create_region(cache_region, cache_namespace_uid)
226 log.debug('clearing cache region: %s with method=%s', cache_region, method)
221 227
222 cache_keys = cache_region.backend.list_keys(prefix=cache_namespace_uid)
223 num_delete_keys = len(cache_keys)
224 if invalidate:
228 num_affected_keys = None
229
230 if method == CLEAR_INVALIDATE:
225 231 # NOTE: The CacheRegion.invalidate() method’s default mode of
226 232 # operation is to set a timestamp local to this CacheRegion in this Python process only.
227 233 # It does not impact other Python processes or regions as the timestamp is only stored locally in memory.
228 cache_region.invalidate(hard=hard)
229 else:
230 if num_delete_keys:
234 cache_region.invalidate(hard=True)
235
236 if method == CLEAR_DELETE:
237 cache_keys = cache_region.backend.list_keys(prefix=cache_namespace_uid)
238 num_affected_keys = len(cache_keys)
239 if num_affected_keys:
231 240 cache_region.delete_multi(cache_keys)
232 return num_delete_keys
241
242 return num_affected_keys
General Comments 0
You need to be logged in to leave comments. Login now