Skip to content

Commit 3d970fc

Browse files
Refactor CMAB cache settings and add convenience methods for cache size and TTL configuration
1 parent 0837956 commit 3d970fc

File tree

4 files changed

+96
-20
lines changed

4 files changed

+96
-20
lines changed

optimizely/cmab/cmab_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from optimizely.exceptions import CmabFetchError, CmabInvalidResponseError
2121

2222
# Default constants for CMAB requests
23-
DEFAULT_MAX_RETRIES = 3
23+
DEFAULT_MAX_RETRIES = 1
2424
DEFAULT_INITIAL_BACKOFF = 0.1 # in seconds (100 ms)
2525
DEFAULT_MAX_BACKOFF = 10 # in seconds
2626
DEFAULT_BACKOFF_MULTIPLIER = 2.0

optimizely/cmab/cmab_service.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@
2323
from optimizely.decision.optimizely_decide_option import OptimizelyDecideOption
2424
from optimizely import logger as _logging
2525
from optimizely.lib import pymmh3 as mmh3
26+
2627
NUM_LOCK_STRIPES = 1000
28+
DEFAULT_CMAB_CACHE_TIMEOUT = 30 * 60 # 30 minutes
29+
DEFAULT_CMAB_CACHE_SIZE = 1000
2730

2831

2932
class CmabDecision(TypedDict):
@@ -111,9 +114,13 @@ def _get_decision(self, project_config: ProjectConfig, user_context: OptimizelyU
111114
if self.logger:
112115
self.logger.debug(reason)
113116
reasons.append(reason)
114-
return CmabDecision(variation_id=cached_value['variation_id'], cmab_uuid=cached_value['cmab_uuid']), reasons
117+
return CmabDecision(variation_id=cached_value['variation_id'],
118+
cmab_uuid=cached_value['cmab_uuid']), reasons
115119
else:
116-
reason = f"CMAB cache attributes mismatch for user '{user_context.user_id}' and rule '{rule_id}', fetching new decision."
120+
reason = (
121+
f"CMAB cache attributes mismatch for user '{user_context.user_id}' "
122+
f"and rule '{rule_id}', fetching new decision."
123+
)
117124
if self.logger:
118125
self.logger.debug(reason)
119126
reasons.append(reason)

optimizely/optimizely.py

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,13 @@
4646
from .optimizely_user_context import OptimizelyUserContext, UserAttributes
4747
from .project_config import ProjectConfig
4848
from .cmab.cmab_client import DefaultCmabClient, CmabRetryConfig
49-
from .cmab.cmab_service import DefaultCmabService, CmabCacheValue
49+
from .cmab.cmab_service import DefaultCmabService, CmabCacheValue, DEFAULT_CMAB_CACHE_SIZE, DEFAULT_CMAB_CACHE_TIMEOUT
5050

5151
if TYPE_CHECKING:
5252
# prevent circular dependency by skipping import at runtime
5353
from .user_profile import UserProfileService
5454
from .helpers.event_tag_utils import EventTags
5555

56-
# Default constants for CMAB cache
57-
DEFAULT_CMAB_CACHE_TIMEOUT = 30 * 60 * 1000 # 30 minutes in milliseconds
58-
DEFAULT_CMAB_CACHE_SIZE = 1000
59-
6056

6157
class Optimizely:
6258
""" Class encapsulating all SDK functionality. """
@@ -77,6 +73,7 @@ def __init__(
7773
default_decide_options: Optional[list[str]] = None,
7874
event_processor_options: Optional[dict[str, Any]] = None,
7975
settings: Optional[OptimizelySdkSettings] = None,
76+
cmab_service: Optional[DefaultCmabService] = None,
8077
) -> None:
8178
""" Optimizely init method for managing Custom projects.
8279
@@ -178,16 +175,20 @@ def __init__(
178175
self.event_builder = event_builder.EventBuilder()
179176

180177
# Initialize CMAB components
181-
self.cmab_client = DefaultCmabClient(
182-
retry_config=CmabRetryConfig(),
183-
logger=self.logger
184-
)
185-
self.cmab_cache: LRUCache[str, CmabCacheValue] = LRUCache(DEFAULT_CMAB_CACHE_SIZE, DEFAULT_CMAB_CACHE_TIMEOUT)
186-
self.cmab_service = DefaultCmabService(
187-
cmab_cache=self.cmab_cache,
188-
cmab_client=self.cmab_client,
189-
logger=self.logger
190-
)
178+
if cmab_service:
179+
self.cmab_service = cmab_service
180+
else:
181+
self.cmab_client = DefaultCmabClient(
182+
retry_config=CmabRetryConfig(),
183+
logger=self.logger
184+
)
185+
self.cmab_cache: LRUCache[str, CmabCacheValue] = LRUCache(DEFAULT_CMAB_CACHE_SIZE,
186+
DEFAULT_CMAB_CACHE_TIMEOUT)
187+
self.cmab_service = DefaultCmabService(
188+
cmab_cache=self.cmab_cache,
189+
cmab_client=self.cmab_client,
190+
logger=self.logger
191+
)
191192
self.decision_service = decision_service.DecisionService(self.logger, user_profile_service, self.cmab_service)
192193
self.user_profile_service = user_profile_service
193194

optimizely/optimizely_factory.py

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
from .event_dispatcher import EventDispatcher, CustomEventDispatcher
2323
from .notification_center import NotificationCenter
2424
from .optimizely import Optimizely
25+
from .odp.lru_cache import LRUCache
26+
from .cmab.cmab_client import DefaultCmabClient, CmabRetryConfig
27+
from .cmab.cmab_service import DefaultCmabService, DEFAULT_CMAB_CACHE_TIMEOUT, DEFAULT_CMAB_CACHE_SIZE
2528

2629
if TYPE_CHECKING:
2730
# prevent circular dependenacy by skipping import at runtime
@@ -36,6 +39,9 @@ class OptimizelyFactory:
3639
max_event_flush_interval: Optional[int] = None
3740
polling_interval: Optional[float] = None
3841
blocking_timeout: Optional[int] = None
42+
cmab_cache_size: Optional[int] = None
43+
cmab_cache_ttl: Optional[int] = None
44+
cmab_custom_cache: Optional[LRUCache] = None
3945

4046
@staticmethod
4147
def set_batch_size(batch_size: int) -> int:
@@ -75,6 +81,51 @@ def set_blocking_timeout(blocking_timeout: int) -> int:
7581
OptimizelyFactory.blocking_timeout = blocking_timeout
7682
return OptimizelyFactory.blocking_timeout
7783

84+
@staticmethod
85+
def set_cmab_cache_size(cache_size: int, logger: Optional[optimizely_logger.Logger] = None) -> Optional[int]:
86+
""" Convenience method for setting the maximum number of items in CMAB cache.
87+
Args:
88+
cache_size: Maximum number of items in CMAB cache.
89+
logger: Optional logger for logging messages.
90+
"""
91+
logger = logger or optimizely_logger.NoOpLogger()
92+
93+
if not isinstance(cache_size, int) or cache_size <= 0:
94+
logger.error(
95+
f"CMAB cache size is invalid, setting to default size {DEFAULT_CMAB_CACHE_SIZE}."
96+
)
97+
return None
98+
99+
OptimizelyFactory.cmab_cache_size = cache_size
100+
return OptimizelyFactory.cmab_cache_size
101+
102+
@staticmethod
103+
def set_cmab_cache_ttl(cache_ttl: int, logger: Optional[optimizely_logger.Logger] = None) -> Optional[int]:
104+
""" Convenience method for setting CMAB cache TTL.
105+
Args:
106+
cache_ttl: Time in seconds for cache entries to live.
107+
logger: Optional logger for logging messages.
108+
"""
109+
logger = logger or optimizely_logger.NoOpLogger()
110+
111+
if not isinstance(cache_ttl, (int, float)) or cache_ttl <= 0:
112+
logger.error(
113+
f"CMAB cache TTL is invalid, setting to default TTL {DEFAULT_CMAB_CACHE_TIMEOUT}."
114+
)
115+
return None
116+
117+
OptimizelyFactory.cmab_cache_ttl = int(cache_ttl)
118+
return OptimizelyFactory.cmab_cache_ttl
119+
120+
@staticmethod
121+
def set_cmab_custom_cache(custom_cache: LRUCache) -> LRUCache:
122+
""" Convenience method for setting custom CMAB cache.
123+
Args:
124+
custom_cache: Cache implementation with lookup, save, remove, and reset methods.
125+
"""
126+
OptimizelyFactory.cmab_custom_cache = custom_cache
127+
return OptimizelyFactory.cmab_custom_cache
128+
78129
@staticmethod
79130
def default_instance(sdk_key: str, datafile: Optional[str] = None) -> Optimizely:
80131
""" Returns a new optimizely instance..
@@ -104,9 +155,17 @@ def default_instance(sdk_key: str, datafile: Optional[str] = None) -> Optimizely
104155
notification_center=notification_center,
105156
)
106157

158+
# Initialize CMAB components
159+
cmab_client = DefaultCmabClient(retry_config=CmabRetryConfig(), logger=logger)
160+
cmab_cache = OptimizelyFactory.cmab_custom_cache or LRUCache(
161+
OptimizelyFactory.cmab_cache_size or DEFAULT_CMAB_CACHE_SIZE,
162+
OptimizelyFactory.cmab_cache_ttl or DEFAULT_CMAB_CACHE_TIMEOUT
163+
)
164+
cmab_service = DefaultCmabService(cmab_cache, cmab_client, logger)
165+
107166
optimizely = Optimizely(
108167
datafile, None, logger, error_handler, None, None, sdk_key, config_manager, notification_center,
109-
event_processor
168+
event_processor, cmab_service=cmab_service
110169
)
111170
return optimizely
112171

@@ -174,7 +233,16 @@ def custom_instance(
174233
notification_center=notification_center,
175234
)
176235

236+
# Initialize CMAB components
237+
cmab_client = DefaultCmabClient(retry_config=CmabRetryConfig(), logger=logger)
238+
cmab_cache = OptimizelyFactory.cmab_custom_cache or LRUCache(
239+
OptimizelyFactory.cmab_cache_size or DEFAULT_CMAB_CACHE_SIZE,
240+
OptimizelyFactory.cmab_cache_ttl or DEFAULT_CMAB_CACHE_TIMEOUT
241+
)
242+
cmab_service = DefaultCmabService(cmab_cache, cmab_client, logger)
243+
177244
return Optimizely(
178245
datafile, event_dispatcher, logger, error_handler, skip_json_validation, user_profile_service,
179-
sdk_key, config_manager, notification_center, event_processor, settings=settings
246+
sdk_key, config_manager, notification_center, event_processor, settings=settings,
247+
cmab_service=cmab_service
180248
)

0 commit comments

Comments
 (0)