Configure the CMAB cache for the Ruby SDK
Configure caching behavior for Contextual Multi-Armed Bandit (CMAB) decisions in the Ruby SDK.
BetaCMAB for Feature Experimentation is in beta. Contact your Customer Success Manager for more information.
The CMAB cache stores variation assignments to reduce latency and minimize API calls to the CMAB service.
Prerequisites
- Ruby SDK version 5.2.0 or higher.
- A CMAB-enabled experiment in your Optimizely Feature Experimentation project.
Minimum SDK version
5.2.0
Description
When a user is bucketed into a CMAB experiment, the Ruby SDK makes an API call to the CMAB service to determine which variation to show. To improve performance and reduce latency, the SDK caches these decisions based on the following:
- User ID.
- Experiment ID.
- CMAB attribute values.
The cache is automatically invalidated when CMAB attributes change for a user, ensuring fresh decisions when context changes.
By default, the Ruby SDK uses an in-memory Least Recently Used (LRU) cache with a maximum size of 10000 entries and a TTL of 30 minutes (1800 seconds). You can customize these settings or provide your own cache implementation.
Configuration options
Configure CMAB caching using the OptimizelyFactory class methods before creating your Optimizely client instance.
Default configuration
The SDK uses the following default values when no custom configuration is provided:
| Setting | Default Value | Description |
|---|---|---|
| Cache Implementation | LRUCache | Thread-safe in-memory LRU cache. |
| Cache Size | 10000 | Maximum number of cached decisions. |
| Cache TTL | 1800 seconds (30 minutes) | Time-to-live for cache entries. |
Configuration methods
Set cache size
OptimizelyFactory.cmab_cache_size(cache_size, logger = NoOpLogger.new)Parameters
cache_size– Maximum number of decisions to cache. Must be a positive integer.- (Optional)
logger– Logger for logging validation errors.
Example
require 'optimizely'
require 'optimizely/logger'
logger = Optimizely::SimpleLogger.new
# Set cache size to 500 entries
Optimizely::OptimizelyFactory.cmab_cache_size(500, logger)Set cache TTL
OptimizelyFactory.cmab_cache_ttl(cache_ttl, logger = NoOpLogger.new)Parameters
cache_ttl– Time in seconds for cache entries to live. Must be a positive number.- (Optional)
logger– Logger for logging validation errors.
Example
require 'optimizely'
require 'optimizely/logger'
logger = Optimizely::SimpleLogger.new
# Set cache TTL to 10 minutes (600 seconds)
Optimizely::OptimizelyFactory.cmab_cache_ttl(600, logger)Set custom cache
OptimizelyFactory.cmab_custom_cache(custom_cache)Parameters
custom_cache– Custom cache implementation that responds tolookup,save,remove, andresetmethods.
Example
# Provide your own cache implementation
Optimizely::OptimizelyFactory.cmab_custom_cache(my_custom_cache)Examples
Basic configuration
Adjust cache size and TTL for your application's needs, like the following example:
require 'optimizely'
require 'optimizely/logger'
logger = Optimizely::SimpleLogger.new
# Configure CMAB cache settings
Optimizely::OptimizelyFactory.cmab_cache_size(500, logger) # Cache up to 500 decisions.
Optimizely::OptimizelyFactory.cmab_cache_ttl(600, logger) # Refresh cache every 10 minutes.
# Create Optimizely client
optimizely_client = Optimizely::OptimizelyFactory.custom_instance(
'YOUR_SDK_KEY',
nil, # datafile (optional)
nil, # event_dispatcher (optional)
logger # logger
)
# Use the client normally
user = optimizely_client.create_user_context('user123', {
'age' => 25,
'location' => 'US'
})
decision = user.decide('my-cmab-flag')
# Cache stores this decision for 10 minutes.Advanced: Custom cache implementation
For multi-instance deployments or distributed systems, you can provide your own cache implementation, like the following example:
require 'optimizely'
# Example: Custom cache implementation for distributed systems.
# You can use Redis, Memcached, or any other caching system.
class CustomCmabCache
def initialize
# Initialize your cache backend here
# For example: @redis = Redis.new, @memcached = Dalli::Client.new, etc.
end
def lookup(key)
# Retrieve value from your cache backend.
# Return the cached value if found, nil otherwise
# Example: @redis.get(key)
end
def save(key, value)
# Store the key-value pair in your cache backend.
# Consider setting an expiration/TTL if your backend supports it.
# Example: @redis.setex(key, ttl, value)
end
def remove(key)
# Remove the key from your cache backend.
# Example: @redis.del(key)
end
def reset
# Clear all CMAB cache entries from your cache backend.
# Example: @redis.flushdb or clear specific key patterns
end
end
# Create and configure your custom cache.
custom_cache = CustomCmabCache.new
# Configure Optimizely with a custom cache.
Optimizely::OptimizelyFactory.cmab_custom_cache(custom_cache)
# Create Optimizely client.
optimizely_client = Optimizely::OptimizelyFactory.custom_instance(
'your-sdk-key',
nil,
nil,
Optimizely::SimpleLogger.new
)
# CMAB decisions now use your custom cache.
user = optimizely_client.create_user_context('user123', {
'device_type' => 'mobile'
})
decision = user.decide('my-cmab-flag')Custom cache interface
To implement a custom cache, your cache must implement the following four methods:
class CustomCmabCache
# Retrieve a value from the cache.
# @param key [String] The cache key
# @return [Object, nil] The cached value if found, nil otherwise
def lookup(key)
# Your implementation here.
end
# Store a key-value pair in the cache.
# @param key [String] The cache key
# @param value [Object] The value to cache
def save(key, value)
# Your implementation here
end
# Remove a key from the cache.
# @param key [String] The cache key to remove
def remove(key)
# Your implementation here.
end
# Clear all entries from the cache.
def reset
# Your implementation here.
end
endCache behavior
Automatic cache invalidation
The cache is automatically invalidated when
- The cached entry's TTL expires.
- CMAB attribute values change for a user (detected with MD5 hash comparison).
- The
IGNORE_CMAB_CACHEdecide option is used. - The
INVALIDATE_USER_CMAB_CACHEdecide option is used. - The
RESET_CMAB_CACHEdecide option is used.
CMAB-specific decide options
Control cache behavior on a per-decision basis using decide options, like the following example:
require 'optimizely/decide/optimizely_decide_option'
# Ignore cache for this decision (always fetch fresh).
decision = user.decide('my-flag', [
Optimizely::OptimizelyDecideOption::IGNORE_CMAB_CACHE
])
# Invalidate cached decision for this user and experiment.
decision = user.decide('my-flag', [
Optimizely::OptimizelyDecideOption::INVALIDATE_USER_CMAB_CACHE
])
# Clear entire CMAB cache.
decision = user.decide('my-flag', [
Optimizely::OptimizelyDecideOption::RESET_CMAB_CACHE
])
# Combine multiple options.
decision = user.decide('my-flag', [
Optimizely::OptimizelyDecideOption::INCLUDE_REASONS,
Optimizely::OptimizelyDecideOption::IGNORE_CMAB_CACHE
])Available CMAB decide options
The following are the available decide options with a short description of what they do:
IGNORE_CMAB_CACHE– Bypass cache and fetch a fresh decision from CMAB service.INVALIDATE_USER_CMAB_CACHE– Remove cached decision for this user and experiment before deciding.RESET_CMAB_CACHE– Clear all entries from the CMAB cache before deciding.
When to use a custom cache
Consider implementing a custom cache in the following scenarios:
- Multi-instance deployments – Share cache across multiple application instances using Redis, Memcached, or similar.
- Distributed systems – Use external caching systems for centralized caching across services
- Persistent cache – Maintain cache across application restarts.
- Custom eviction policies – Implement domain-specific cache management (for example, user-based eviction).
- Monitoring – Track cache metrics (hit rate, memory usage, and so on) with your existing monitoring tools.
- Compliance – Store cached data in specific locations or with specific encryption requirements.
Updated about 1 hour ago