Skip to content

Commit df4ad94

Browse files
authored
modelcache: Make ModelCache TTL configurable (PROJQUAY-1878) (quay#765)
Adds a configuration option to modify the cache expiry timeout for ModelCache objects Co-authored-by: Syed <syed@apache.org>
1 parent 7f23e58 commit df4ad94

11 files changed

Lines changed: 78 additions & 32 deletions

File tree

config.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,12 @@ def create_transaction(db):
653653
DATA_MODEL_CACHE_CONFIG = {
654654
"engine": "memcached",
655655
"endpoint": ("127.0.0.1", 18080),
656+
"repository_blob_cache_ttl": "60s",
657+
"catalog_page_cache_ttl": "60s",
658+
"namespace_geo_restrictions_cache_ttl": "240s",
659+
"active_repo_tags_cache_ttl": "120s",
660+
"appr_applications_list_cache_ttl": "3600s",
661+
"appr_show_package_cache_ttl": "3600s",
656662
}
657663

658664
# Defines the number of successive failures of a build trigger's build before the trigger is

data/cache/__init__.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ def get_model_cache(config):
1515
engine = cache_config.get("engine", "noop")
1616

1717
if engine == "noop":
18-
return NoopDataModelCache()
18+
return NoopDataModelCache(cache_config)
1919

2020
if engine == "inmemory":
21-
return InMemoryDataModelCache()
21+
return InMemoryDataModelCache(cache_config)
2222

2323
if engine == "memcached":
2424
endpoint = cache_config.get("endpoint", None)
@@ -29,7 +29,9 @@ def get_model_cache(config):
2929
connect_timeout = cache_config.get("connect_timeout")
3030
predisconnect = cache_config.get("predisconnect_from_db")
3131

32-
cache = MemcachedModelCache(endpoint, timeout=timeout, connect_timeout=connect_timeout)
32+
cache = MemcachedModelCache(
33+
cache_config, endpoint, timeout=timeout, connect_timeout=connect_timeout
34+
)
3335
if predisconnect:
3436
cache = DisconnectWrapper(cache, config)
3537

@@ -41,6 +43,7 @@ def get_model_cache(config):
4143
raise Exception("Missing `host` for Redis model cache configuration")
4244

4345
return RedisDataModelCache(
46+
cache_config,
4447
host=host,
4548
port=cache_config.get("port", 6379),
4649
password=cache_config.get("password", None),

data/cache/cache_key.py

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,48 +9,57 @@ class CacheKey(namedtuple("CacheKey", ["key", "expiration"])):
99
pass
1010

1111

12-
def for_repository_blob(namespace_name, repo_name, digest, version):
12+
def for_repository_blob(namespace_name, repo_name, digest, version, cache_config):
1313
"""
1414
Returns a cache key for a blob in a repository.
1515
"""
16-
return CacheKey("repo_blob__%s_%s_%s_%s" % (namespace_name, repo_name, digest, version), "60s")
16+
cache_ttl = cache_config.get("repository_blob_cache_ttl", "60s")
17+
return CacheKey(
18+
"repo_blob__%s_%s_%s_%s" % (namespace_name, repo_name, digest, version), cache_ttl
19+
)
1720

1821

19-
def for_catalog_page(auth_context_key, start_id, limit):
22+
def for_catalog_page(auth_context_key, start_id, limit, cache_config):
2023
"""
2124
Returns a cache key for a single page of a catalog lookup for an authed context.
2225
"""
2326
params = (auth_context_key or "(anon)", start_id or 0, limit or 0)
24-
return CacheKey("catalog_page__%s_%s_%s" % params, "60s")
27+
cache_ttl = cache_config.get("catalog_page_cache_ttl", "60s")
28+
return CacheKey("catalog_page__%s_%s_%s" % params, cache_ttl)
2529

2630

27-
def for_namespace_geo_restrictions(namespace_name):
31+
def for_namespace_geo_restrictions(namespace_name, cache_config):
2832
"""
2933
Returns a cache key for the geo restrictions for a namespace.
3034
"""
31-
return CacheKey("geo_restrictions__%s" % (namespace_name), "240s")
35+
cache_ttl = cache_config.get("namespace_geo_restrictions_cache_ttl", "240s")
36+
return CacheKey("geo_restrictions__%s" % namespace_name, cache_ttl)
3237

3338

34-
def for_active_repo_tags(repository_id, start_pagination_id, limit):
39+
def for_active_repo_tags(repository_id, start_pagination_id, limit, cache_config):
3540
"""
3641
Returns a cache key for the active tags in a repository.
3742
"""
43+
44+
cache_ttl = cache_config.get("active_repo_tags_cache_ttl", "120s")
3845
return CacheKey(
39-
"repo_active_tags__%s_%s_%s" % (repository_id, start_pagination_id, limit), "120s"
46+
"repo_active_tags__%s_%s_%s" % (repository_id, start_pagination_id, limit), cache_ttl
4047
)
4148

4249

43-
def for_appr_applications_list(namespace, limit):
50+
def for_appr_applications_list(namespace, limit, cache_config):
4451
"""
4552
Returns a cache key for listing applications under the App Registry.
4653
"""
47-
return CacheKey("appr_applications_list_%s_%s" % (namespace, limit), "3600s")
54+
cache_ttl = cache_config.get("appr_applications_list_cache_ttl", "3600s")
55+
return CacheKey("appr_applications_list_%s_%s" % (namespace, limit), cache_ttl)
4856

4957

50-
def for_appr_show_package(namespace, package_name, release, media_type):
58+
def for_appr_show_package(namespace, package_name, release, media_type, cache_config):
5159
"""
5260
Returns a cache key for showing a package under the App Registry.
5361
"""
62+
cache_ttl = cache_config.get("appr_show_package_cache_ttl", "3600s")
5463
return CacheKey(
55-
"appr_show_package_%s_%s_%s-%s" % (namespace, package_name, release, media_type), "3600s"
64+
"appr_show_package_%s_%s_%s-%s" % (namespace, package_name, release, media_type), cache_ttl
5665
)

data/cache/impl.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,8 @@
1515
from util.timedeltastring import convert_to_timedelta
1616
from util.workers import get_worker_connections_count
1717

18-
1918
logger = logging.getLogger(__name__)
2019

21-
2220
cache_count = Counter(
2321
"quay_model_cache", "number of attempts to retrieve from the model cache", labelnames=["type"]
2422
)
@@ -38,6 +36,11 @@ class DataModelCache(object):
3836
Defines an interface for cache storing and returning tuple data model objects.
3937
"""
4038

39+
cache_config = None
40+
41+
def __init__(self, cache_config):
42+
self.cache_config = cache_config
43+
4144
@abstractmethod
4245
def retrieve(self, cache_key, loader, should_cache=is_not_none):
4346
"""
@@ -54,7 +57,8 @@ class DisconnectWrapper(DataModelCache):
5457
invoking the cache, in case the cache call takes too long.
5558
"""
5659

57-
def __init__(self, cache, app_config):
60+
def __init__(self, cache_config, cache, app_config):
61+
super(DisconnectWrapper, self).__init__(cache_config)
5862
self.cache = cache
5963
self.app_config = app_config
6064

@@ -77,7 +81,8 @@ class InMemoryDataModelCache(DataModelCache):
7781
Implementation of the data model cache backed by an in-memory dictionary.
7882
"""
7983

80-
def __init__(self):
84+
def __init__(self, cache_config):
85+
super(InMemoryDataModelCache, self).__init__(cache_config)
8186
self.cache = ExpiresDict()
8287

8388
def empty_for_testing(self):
@@ -133,10 +138,12 @@ class MemcachedModelCache(DataModelCache):
133138

134139
def __init__(
135140
self,
141+
cache_config,
136142
endpoint,
137143
timeout=_DEFAULT_MEMCACHE_TIMEOUT,
138144
connect_timeout=_DEFAULT_MEMCACHE_CONNECT_TIMEOUT,
139145
):
146+
super(MemcachedModelCache, self).__init__(cache_config)
140147
max_pool_size = int(
141148
os.environ.get("MEMCACHE_POOL_MAX_SIZE", get_worker_connections_count("registry"))
142149
)
@@ -238,13 +245,15 @@ class RedisDataModelCache(DataModelCache):
238245

239246
def __init__(
240247
self,
248+
cache_config,
241249
host="127.0.0.1",
242250
port=6379,
243251
password=None,
244252
db=0,
245253
ca_cert=None,
246254
ssl=False,
247255
):
256+
super(RedisDataModelCache, self).__init__(cache_config)
248257
self.client = StrictRedis(
249258
host=host,
250259
port=port,

data/cache/test/test_cache.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,15 @@
1313

1414
DATA = {}
1515

16+
TEST_CACHE_CONFIG = {
17+
"repository_blob_cache_ttl": "240s",
18+
"catalog_page_cache_ttl": "240s",
19+
"namespace_geo_restrictions_cache_ttl": "240s",
20+
"active_repo_tags_cache_ttl": "240s",
21+
"appr_applications_list_cache_ttl": "3600s",
22+
"appr_show_package_cache_ttl": "3600s",
23+
}
24+
1625

1726
class MockClient(object):
1827
def __init__(self, **kwargs):
@@ -37,7 +46,7 @@ def close(self):
3746
)
3847
def test_caching(cache_type):
3948
key = CacheKey("foo", "60m")
40-
cache = cache_type()
49+
cache = cache_type(TEST_CACHE_CONFIG)
4150

4251
# Perform two retrievals, and make sure both return.
4352
assert cache.retrieve(key, lambda: {"a": 1234}) == {"a": 1234}
@@ -50,7 +59,7 @@ def test_memcache():
5059

5160
key = CacheKey("foo", "60m")
5261
with patch("data.cache.impl.PooledClient", MockClient):
53-
cache = MemcachedModelCache(("127.0.0.1", "-1"))
62+
cache = MemcachedModelCache(TEST_CACHE_CONFIG, ("127.0.0.1", "-1"))
5463
assert cache.retrieve(key, lambda: {"a": 1234}) == {"a": 1234}
5564
assert cache.retrieve(key, lambda: {"a": 1234}) == {"a": 1234}
5665

@@ -65,7 +74,7 @@ def sc(value):
6574
return value["a"] != 1234
6675

6776
with patch("data.cache.impl.PooledClient", MockClient):
68-
cache = MemcachedModelCache(("127.0.0.1", "-1"))
77+
cache = MemcachedModelCache(TEST_CACHE_CONFIG, ("127.0.0.1", "-1"))
6978
assert cache.retrieve(key, lambda: {"a": 1234}, should_cache=sc) == {"a": 1234}
7079

7180
# Ensure not cached since it was `1234`.
@@ -83,7 +92,7 @@ def test_redis_cache():
8392

8493
key = CacheKey("foo", "60m")
8594
with patch("data.cache.impl.StrictRedis", MockClient):
86-
cache = RedisDataModelCache("127.0.0.1")
95+
cache = RedisDataModelCache(TEST_CACHE_CONFIG, "127.0.0.1")
8796

8897
assert cache.retrieve(key, lambda: {"a": 1234}) == {"a": 1234}
8998
assert cache.retrieve(key, lambda: {"a": 1234}) == {"a": 1234}

data/registry_model/registry_oci_model.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -762,7 +762,7 @@ def load_tags():
762762
return [tag.asdict() for tag in tags]
763763

764764
tags_cache_key = cache_key.for_active_repo_tags(
765-
repository_ref._db_id, start_pagination_id, limit
765+
repository_ref._db_id, start_pagination_id, limit, model_cache.cache_config
766766
)
767767
result = model_cache.retrieve(tags_cache_key, load_tags)
768768

@@ -784,7 +784,9 @@ def load_blacklist():
784784

785785
return [restriction.restricted_region_iso_code for restriction in restrictions]
786786

787-
blacklist_cache_key = cache_key.for_namespace_geo_restrictions(namespace_name)
787+
blacklist_cache_key = cache_key.for_namespace_geo_restrictions(
788+
namespace_name, model_cache.cache_config
789+
)
788790
result = model_cache.retrieve(blacklist_cache_key, load_blacklist)
789791
if result is None:
790792
return None
@@ -811,7 +813,9 @@ def load_blob():
811813

812814
return blob_found.asdict()
813815

814-
blob_cache_key = cache_key.for_repository_blob(namespace_name, repo_name, blob_digest, 2)
816+
blob_cache_key = cache_key.for_repository_blob(
817+
namespace_name, repo_name, blob_digest, 2, model_cache.cache_config
818+
)
815819
blob_dict = model_cache.retrieve(blob_cache_key, load_blob)
816820

817821
try:

data/registry_model/test/test_interface.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
from app import docker_v2_signing_key, storage
1616
from data import model
17+
from data.cache.test.test_cache import TEST_CACHE_CONFIG
1718
from data.database import (
1819
TagManifestLabelMap,
1920
TagManifestToManifest,
@@ -628,7 +629,7 @@ class SomeException(Exception):
628629

629630

630631
def test_get_cached_repo_blob(registry_model):
631-
model_cache = InMemoryDataModelCache()
632+
model_cache = InMemoryDataModelCache(TEST_CACHE_CONFIG)
632633

633634
repository_ref = registry_model.lookup_repository("devtable", "simple")
634635
latest_tag = registry_model.get_repo_tag(repository_ref, "latest")
@@ -914,7 +915,7 @@ def test_lookup_active_repository_tags(test_cached, oci_model):
914915
tag_id = None
915916
while True:
916917
if test_cached:
917-
model_cache = InMemoryDataModelCache()
918+
model_cache = InMemoryDataModelCache(TEST_CACHE_CONFIG)
918919
tags = oci_model.lookup_cached_active_repository_tags(
919920
model_cache, repository_ref, tag_id, 11
920921
)

endpoints/appr/models_cnr.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,9 @@ def _list_applications():
131131
for found in self._list_applications(namespace=namespace, limit=limit)
132132
]
133133

134-
apps_cache_key = cache_key.for_appr_applications_list(namespace, limit)
134+
apps_cache_key = cache_key.for_appr_applications_list(
135+
namespace, limit, model_cache.cache_config
136+
)
135137
return [
136138
ApplicationSummaryView(**found)
137139
for found in model_cache.retrieve(apps_cache_key, _list_applications)

endpoints/appr/registry.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ def _retrieve_package():
153153
return jsonify(_retrieve_package())
154154

155155
show_package_cache_key = cache_key.for_appr_show_package(
156-
namespace, package_name, release, media_type
156+
namespace, package_name, release, media_type, model_cache.cache_config
157157
)
158158

159159
result = model_cache.retrieve(show_package_cache_key, _retrieve_package)

endpoints/v2/catalog.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ def _load_catalog():
4646
]
4747

4848
context_key = get_authenticated_context().unique_key if get_authenticated_context() else None
49-
catalog_cache_key = cache_key.for_catalog_page(context_key, start_id, limit)
49+
catalog_cache_key = cache_key.for_catalog_page(
50+
context_key, start_id, limit, model_cache.cache_config
51+
)
5052
visible_repositories = [
5153
Repository(**repo_dict)
5254
for repo_dict in model_cache.retrieve(catalog_cache_key, _load_catalog)

0 commit comments

Comments
 (0)