##// 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 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
53
53
54
54
55 CLEAR_DELETE = 'delete'
56 CLEAR_INVALIDATE = 'invalidate'
57
58
55 def async_creation_runner(cache, somekey, creator, mutex):
59 def async_creation_runner(cache, somekey, creator, mutex):
56
60
57 def runner():
61 def runner():
@@ -125,8 +125,11 b' class FileNamespaceBackend(PickleSeriali'
125 def __str__(self):
125 def __str__(self):
126 return self.__repr__()
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 def list_keys(self, prefix: bytes = b''):
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 def cond(dbm_key: bytes):
134 def cond(dbm_key: bytes):
132 if not prefix:
135 if not prefix:
@@ -185,8 +188,11 b' class BaseRedisBackend(redis_backend.Red'
185 )
188 )
186 self.reader_client = self.writer_client
189 self.reader_client = self.writer_client
187
190
188 def list_keys(self, prefix=''):
191 def _get_keys_pattern(self, prefix: bytes = b''):
189 prefix = f'{self.key_prefix}:{prefix}*'
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 return self.reader_client.keys(prefix)
196 return self.reader_client.keys(prefix)
191
197
192 def get_store(self):
198 def get_store(self):
@@ -50,11 +50,13 b' class RhodeCodeCacheRegion(CacheRegion):'
50 And it's faster in cases we don't ever want to compute cached values
50 And it's faster in cases we don't ever want to compute cached values
51 """
51 """
52 expiration_time_is_callable = callable(expiration_time)
52 expiration_time_is_callable = callable(expiration_time)
53 if not namespace:
54 namespace = getattr(self, '_default_namespace', None)
53
55
54 if function_key_generator is None:
56 if function_key_generator is None:
55 function_key_generator = self.function_key_generator
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 if not condition:
61 if not condition:
60 log.debug('Calling un-cached method:%s', user_func.__name__)
62 log.debug('Calling un-cached method:%s', user_func.__name__)
@@ -64,7 +66,7 b' class RhodeCodeCacheRegion(CacheRegion):'
64 log.debug('un-cached method:%s took %.4fs', user_func.__name__, total)
66 log.debug('un-cached method:%s took %.4fs', user_func.__name__, total)
65 return result
67 return result
66
68
67 key = key_generator(*arg, **kw)
69 key = func_key_generator(*arg, **kw)
68
70
69 timeout = expiration_time() if expiration_time_is_callable \
71 timeout = expiration_time() if expiration_time_is_callable \
70 else expiration_time
72 else expiration_time
@@ -139,36 +141,36 b' def compute_key_from_params(*args):'
139 return sha1(safe_bytes("_".join(map(str, args))))
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 def backend_key_generator(backend):
158 def backend_key_generator(backend):
143 """
159 """
144 Special wrapper that also sends over the backend to the key generator
160 Special wrapper that also sends over the backend to the key generator
145 """
161 """
146 def wrapper(namespace, fn):
162 def wrapper(namespace, fn):
147 return key_generator(backend, namespace, fn)
163 return custom_key_generator(backend, namespace, fn)
148 return wrapper
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 def get_or_create_region(region_name, region_namespace: str = None):
167 def get_or_create_region(region_name, region_namespace: str = None):
166 from vcsserver.lib.rc_cache.backends import FileNamespaceBackend
168 from vcsserver.lib.rc_cache.backends import FileNamespaceBackend
167
169
168 region_obj = region_meta.dogpile_cache_regions.get(region_name)
170 region_obj = region_meta.dogpile_cache_regions.get(region_name)
169 if not region_obj:
171 if not region_obj:
170 reg_keys = list(region_meta.dogpile_cache_regions.keys())
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 region_uid_name = f'{region_name}:{region_namespace}'
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 log.debug('configuring new region: %s', region_uid_name)
214 log.debug('configuring new region: %s', region_uid_name)
213 region_obj = region_meta.dogpile_cache_regions[region_namespace] = new_region
215 region_obj = region_meta.dogpile_cache_regions[region_namespace] = new_region
214
216
217 region_obj._default_namespace = region_namespace
215 return region_obj
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 if not isinstance(cache_region, RhodeCodeCacheRegion):
224 if not isinstance(cache_region, RhodeCodeCacheRegion):
220 cache_region = get_or_create_region(cache_region, cache_namespace_uid)
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)
228 num_affected_keys = None
223 num_delete_keys = len(cache_keys)
229
224 if invalidate:
230 if method == CLEAR_INVALIDATE:
225 # NOTE: The CacheRegion.invalidate() method’s default mode of
231 # NOTE: The CacheRegion.invalidate() method’s default mode of
226 # operation is to set a timestamp local to this CacheRegion in this Python process only.
232 # operation is to set a timestamp local to this CacheRegion in this Python process only.
227 # It does not impact other Python processes or regions as the timestamp is only stored locally in memory.
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)
234 cache_region.invalidate(hard=True)
229 else:
235
230 if num_delete_keys:
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 cache_region.delete_multi(cache_keys)
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