Skip to content

Commit 221963c

Browse files
[ODSC-52993] Set up caching for listing models and compartments (#592)
2 parents 9aa4f7c + 367f9ff commit 221963c

File tree

6 files changed

+121
-26
lines changed

6 files changed

+121
-26
lines changed

THIRD_PARTY_LICENSES.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,12 @@ delta
405405
* Source code: https://github.com/delta-io/delta/
406406
* Project home: https://delta.io/
407407

408+
cachetools
409+
* Copyright (c) 2014-2024 Thomas Kemmer
410+
* License: The MIT License (MIT)
411+
* Source code: https://github.com/tkem/cachetools/
412+
* Project home: https://cachetools.readthedocs.io/
413+
408414
=============================== Licenses ===============================
409415
------------------------------------------------------------------------
410416

ads/aqua/extension/model_handler.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
# Copyright (c) 2024 Oracle and/or its affiliates.
44
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
55

6+
from urllib.parse import urlparse
7+
from tornado.web import HTTPError
68
from ads.aqua.decorator import handle_exceptions
79
from ads.aqua.extension.base_handler import AquaAPIhandler
810
from ads.aqua.model import AquaModelApp
@@ -14,7 +16,6 @@ class AquaModelHandler(AquaAPIhandler):
1416
@handle_exceptions
1517
def get(self, model_id=""):
1618
"""Handle GET request."""
17-
1819
if not model_id:
1920
return self.list()
2021
return self.read(model_id)
@@ -24,6 +25,16 @@ def read(self, model_id):
2425
"""Read the information of an Aqua model."""
2526
return self.finish(AquaModelApp().get(model_id))
2627

28+
@handle_exceptions
29+
def delete(self, id=""):
30+
"""Handles DELETE request for clearing cache"""
31+
url_parse = urlparse(self.request.path)
32+
paths = url_parse.path.strip("/")
33+
if paths.startswith("aqua/model/cache"):
34+
return self.finish(AquaModelApp().clear_model_list_cache())
35+
else:
36+
raise HTTPError(400, f"The request {self.request.path} is invalid.")
37+
2738
@handle_exceptions
2839
def list(self):
2940
"""List Aqua models."""

ads/aqua/extension/ui_handler.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@
44
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
55

66
from urllib.parse import urlparse
7-
87
from tornado.web import HTTPError
9-
108
from ads.aqua.decorator import handle_exceptions
119
from ads.aqua.extension.base_handler import AquaAPIhandler
1210
from ads.aqua.ui import AquaUIApp
@@ -54,26 +52,34 @@ def get(self, id=""):
5452
else:
5553
raise HTTPError(400, f"The request {self.request.path} is invalid.")
5654

55+
@handle_exceptions
56+
def delete(self, id=""):
57+
"""Handles DELETE request for clearing cache"""
58+
# todo: added for dev work, to be deleted if there's no feature to refresh cache in Aqua
59+
url_parse = urlparse(self.request.path)
60+
paths = url_parse.path.strip("/")
61+
if paths.startswith("aqua/compartments/cache"):
62+
return self.finish(AquaUIApp().clear_compartments_list_cache())
63+
else:
64+
raise HTTPError(400, f"The request {self.request.path} is invalid.")
65+
5766
@handle_exceptions
5867
def list_log_groups(self, **kwargs):
5968
"""Lists all log groups for the specified compartment or tenancy."""
6069
compartment_id = self.get_argument("compartment_id", default=COMPARTMENT_OCID)
61-
6270
return self.finish(
6371
AquaUIApp().list_log_groups(compartment_id=compartment_id, **kwargs)
6472
)
6573

6674
@handle_exceptions
6775
def list_logs(self, log_group_id: str, **kwargs):
6876
"""Lists the specified log group's log objects."""
69-
7077
return self.finish(AquaUIApp().list_logs(log_group_id=log_group_id, **kwargs))
7178

7279
@handle_exceptions
73-
def list_compartments(self, **kwargs):
80+
def list_compartments(self):
7481
"""Lists the compartments in a compartment specified by ODSC_MODEL_COMPARTMENT_OCID env variable."""
75-
76-
return self.finish(AquaUIApp().list_compartments(**kwargs))
82+
return self.finish(AquaUIApp().list_compartments())
7783

7884
@handle_exceptions
7985
def get_default_compartment(self):

ads/aqua/model.py

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
from dataclasses import dataclass
77
from enum import Enum
88
from typing import List, Union
9+
from datetime import datetime, timedelta
10+
from threading import Lock
11+
from cachetools import TTLCache
912

1013
import oci
11-
1214
from ads.aqua import logger
1315
from ads.aqua.base import AquaApp
1416
from ads.aqua.exception import AquaRuntimeError
@@ -80,13 +82,20 @@ class AquaModelApp(AquaApp):
8082
Retrieves details of an Aqua model by its unique identifier.
8183
list(compartment_id: str = None, project_id: str = None, **kwargs) -> List[AquaModelSummary]:
8284
Lists all Aqua models within a specified compartment and/or project.
85+
clear_model_list_cache()
86+
Allows clear list model cache items from the service models compartment.
8387
8488
Note:
8589
This class is designed to work within the Oracle Cloud Infrastructure
8690
and requires proper configuration and authentication set up to interact
8791
with OCI services.
8892
"""
8993

94+
_service_models_cache = TTLCache(
95+
maxsize=10, ttl=timedelta(hours=5), timer=datetime.now
96+
)
97+
_cache_lock = Lock()
98+
9099
def create(
91100
self, model_id: str, project_id: str, compartment_id: str = None, **kwargs
92101
) -> DataScienceModel:
@@ -134,10 +143,7 @@ def create(
134143
.with_defined_metadata_list(service_model.defined_metadata_list)
135144
.with_provenance_metadata(service_model.provenance_metadata)
136145
# TODO: decide what kwargs will be needed.
137-
.create(
138-
model_by_reference=True,
139-
**kwargs
140-
)
146+
.create(model_by_reference=True, **kwargs)
141147
)
142148
logger.debug(
143149
f"Aqua Model {custom_model.id} created with the service model {model_id}"
@@ -177,8 +183,9 @@ def list(
177183
) -> List["AquaModelSummary"]:
178184
"""Lists all Aqua models within a specified compartment and/or project.
179185
If `compartment_id` is not specified, the method defaults to returning
180-
the service models within the pre-configured default compartment.
181-
186+
the service models within the pre-configured default compartment. By default, the list
187+
of models in the service compartment are cached. Use clear_model_list_cache() to invalidate
188+
the cache.
182189
183190
Parameters
184191
----------
@@ -199,6 +206,11 @@ def list(
199206
logger.info(f"Fetching custom models from compartment_id={compartment_id}.")
200207
models = self._rqs(compartment_id)
201208
else:
209+
if ODSC_MODEL_COMPARTMENT_OCID in self._service_models_cache.keys():
210+
logger.info(
211+
f"Returning service models list in {ODSC_MODEL_COMPARTMENT_OCID} from cache."
212+
)
213+
return self._service_models_cache.get(ODSC_MODEL_COMPARTMENT_OCID)
202214
logger.info(
203215
f"Fetching service models from compartment_id={ODSC_MODEL_COMPARTMENT_OCID}"
204216
)
@@ -213,7 +225,6 @@ def list(
213225
)
214226

215227
aqua_models = []
216-
# TODO: build index.json for service model as caching if needed.
217228

218229
for model in models:
219230
aqua_models.append(
@@ -223,8 +234,35 @@ def list(
223234
)
224235
)
225236

237+
if not compartment_id:
238+
self._service_models_cache.__setitem__(
239+
key=ODSC_MODEL_COMPARTMENT_OCID, value=aqua_models
240+
)
241+
226242
return aqua_models
227243

244+
def clear_model_list_cache(
245+
self,
246+
):
247+
"""
248+
Allows user to clear list model cache items from the service models compartment.
249+
Returns
250+
-------
251+
dict with the key used, and True if cache has the key that needs to be deleted.
252+
"""
253+
res = {}
254+
logger.info(f"Clearing _service_models_cache")
255+
with self._cache_lock:
256+
if ODSC_MODEL_COMPARTMENT_OCID in self._service_models_cache.keys():
257+
self._service_models_cache.pop(key=ODSC_MODEL_COMPARTMENT_OCID)
258+
res = {
259+
"key": {
260+
"compartment_id": ODSC_MODEL_COMPARTMENT_OCID,
261+
},
262+
"cache_deleted": True,
263+
}
264+
return res
265+
228266
def _process_model(
229267
self, model: Union["ModelSummary", "Model", "ResourceSummary"], region: str
230268
) -> dict:

ads/aqua/ui.py

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55

66
from oci.exceptions import ServiceError
77
from oci.identity.models import Compartment
8+
from datetime import datetime, timedelta
9+
from threading import Lock
10+
from cachetools import TTLCache
811

912
from ads.aqua import logger
1013
from ads.aqua.base import AquaApp
@@ -30,6 +33,11 @@ class AquaUIApp(AquaApp):
3033
3134
"""
3235

36+
_compartments_cache = TTLCache(
37+
maxsize=10, ttl=timedelta(hours=2), timer=datetime.now
38+
)
39+
_cache_lock = Lock()
40+
3341
def list_log_groups(self, **kwargs) -> str:
3442
"""Lists all log groups for the specified compartment or tenancy. This is a pass through the OCI list_log_groups
3543
API.
@@ -72,16 +80,10 @@ def list_logs(self, **kwargs) -> str:
7280
log_group_id=log_group_id, **kwargs
7381
).data.__repr__()
7482

75-
def list_compartments(self, **kwargs) -> str:
76-
"""Lists the compartments in a compartment specified by TENANCY_OCID env variable. This is a pass through the OCI list_compartments
83+
def list_compartments(self) -> str:
84+
"""Lists the compartments in a tenancy specified by TENANCY_OCID env variable. This is a pass through the OCI list_compartments
7785
API.
7886
79-
Parameters
80-
----------
81-
kwargs
82-
Keyword arguments, such as compartment_id,
83-
for `list_compartments <https://docs.oracle.com/en-us/iaas/tools/python/2.119.1/api/logging/client/oci.identity.IdentityClient.html#oci.identity.IdentityClient.list_compartments>`_
84-
8587
Returns
8688
-------
8789
str:
@@ -93,6 +95,13 @@ def list_compartments(self, **kwargs) -> str:
9395
f"TENANCY_OCID must be available in environment"
9496
" variables to list the sub compartments."
9597
)
98+
99+
if TENANCY_OCID in self._compartments_cache.keys():
100+
logger.info(
101+
f"Returning compartments list in {TENANCY_OCID} from cache."
102+
)
103+
return self._compartments_cache.get(TENANCY_OCID)
104+
96105
compartments = []
97106
# User may not have permissions to list compartment.
98107
try:
@@ -132,7 +141,13 @@ def list_compartments(self, **kwargs) -> str:
132141
0,
133142
Compartment(id=TENANCY_OCID, name=" ** Root - Name N/A **"),
134143
)
135-
return compartments.__repr__()
144+
# convert the string of the results flattened as a dict
145+
res = compartments.__repr__()
146+
147+
# cache compartment results
148+
self._compartments_cache.__setitem__(key=TENANCY_OCID, value=res)
149+
150+
return res
136151

137152
# todo : update this once exception handling is set up
138153
except ServiceError as se:
@@ -150,6 +165,25 @@ def get_default_compartment(self) -> dict:
150165
logger.error("No compartment id found from environment variables.")
151166
return dict(compartment_id=COMPARTMENT_OCID)
152167

168+
def clear_compartments_list_cache(self) -> dict:
169+
"""Allows caller to clear compartments list cache
170+
Returns
171+
-------
172+
dict with the key used, and True if cache has the key that needs to be deleted.
173+
"""
174+
res = {}
175+
logger.info(f"Clearing list_compartments cache")
176+
with self._cache_lock:
177+
if TENANCY_OCID in self._compartments_cache.keys():
178+
self._compartments_cache.pop(key=TENANCY_OCID)
179+
res = {
180+
"key": {
181+
"tenancy_ocid": TENANCY_OCID,
182+
},
183+
"cache_deleted": True,
184+
}
185+
return res
186+
153187
def list_model_version_sets(self, **kwargs) -> str:
154188
"""Lists all model version sets for the specified compartment or tenancy.
155189

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ pii = [
163163
"spacy==3.6.1",
164164
]
165165
llm = ["langchain>=0.0.295", "evaluate>=0.4.0"]
166-
aqua = ["fire"]
166+
aqua = ["fire", "cachetools"]
167167

168168
[project.urls]
169169
"Github" = "https://github.com/oracle/accelerated-data-science"

0 commit comments

Comments
 (0)