Configure the CMAB cache for the Go SDK
How to configure caching behavior for Contextual Multi-Armed Bandit (CMAB) decisions in the Feature Experimentation Java 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
- Java SDK version 4.3.0 or higher.
- A CMAB-enabled experiment in your Optimizely project.
Minimum SDK version
4.3.0
Description
When a user is bucketed into a CMAB experiment, the Java SDK makes an API call to the CMAB service to determine which variation to show. To improve performance and reduce latency, the Java 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 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 when building your CmabService instance using the DefaultCmabService.Builder.
Default configuration
The Java SDK uses the following default values when no custom configuration is provided:
| Setting | Default Value | Description |
|---|---|---|
| Cache Size | 10000 | Maximum number of cached decisions. |
| Cache TTL | 1800 seconds (30 minutes) | Time-to-live for cache entries. |
| Cache Implementation | DefaultLRUCache | Thread-safe in-memory Least Recently Used (LRU) cache. |
Configuration methods
Set cache size
DefaultCmabService.builder().withCmabCacheSize(int cacheSize)Parameters
cacheSize(int) – Maximum number of decisions to cache. Must be a positive integer.
Example
import com.optimizely.ab.cmab.DefaultCmabClient;
import com.optimizely.ab.cmab.service.DefaultCmabService;
// Set cache size to 500 entries
DefaultCmabService cmabService = DefaultCmabService.builder()
.withClient(new DefaultCmabClient())
.withCmabCacheSize(500)
.build();Set cache TTL
DefaultCmabService.builder().withCmabCacheTimeoutInSecs(int timeoutInSecs)Parameters
timeoutInSecs(int) – Time in seconds for cache entries to live. Must be a positive number.
Example
import com.optimizely.ab.cmab.DefaultCmabClient;
import com.optimizely.ab.cmab.service.DefaultCmabService;
// Set cache TTL to 10 minutes (600 seconds)
DefaultCmabService cmabService = DefaultCmabService.builder()
.withClient(new DefaultCmabClient())
.withCmabCacheTimeoutInSecs(600)
.build();Set custom cache
DefaultCmabService.builder().withCustomCache(Cache<CmabCacheValue> cache)Parameters
cache(Cache<CmabCacheValue>) – Custom cache implementation that implements theCacheinterface.
Example
import com.optimizely.ab.cmab.DefaultCmabClient;
import com.optimizely.ab.cmab.service.DefaultCmabService;
import com.optimizely.ab.internal.Cache;
// Provide your own cache implementation
Cache<CmabCacheValue> customCache = new MyCustomCache<>();
DefaultCmabService cmabService = DefaultCmabService.builder()
.withClient(new DefaultCmabClient())
.withCustomCache(customCache)
.build();Examples
Basic configuration
Adjust cache size and TTL for your application's needs.
import com.optimizely.ab.Optimizely;
import com.optimizely.ab.OptimizelyFactory;
import com.optimizely.ab.OptimizelyUserContext;
import com.optimizely.ab.cmab.DefaultCmabClient;
import com.optimizely.ab.cmab.service.DefaultCmabService;
import com.optimizely.ab.config.HttpProjectConfigManager;
import com.optimizely.ab.optimizelydecision.OptimizelyDecision;
// Configure CMAB cache settings
DefaultCmabService cmabService = DefaultCmabService.builder()
.withClient(new DefaultCmabClient())
.withCmabCacheSize(500) // Cache up to 500 decisions
.withCmabCacheTimeoutInSecs(600) // Refresh cache every 10 minutes
.build();
// Create ProjectConfigManager
HttpProjectConfigManager configManager = HttpProjectConfigManager.builder()
.withSdkKey("your-sdk-key")
.build();
// Create Optimizely client with custom CMAB service
Optimizely optimizelyClient = OptimizelyFactory.newDefaultInstance(
configManager,
null, // notification center
null, // event handler
null, // odp api manager
cmabService
);
// Use the client normally
Map<String, Object> attributes = new HashMap<>();
attributes.put("age", 25);
attributes.put("location", "US");
OptimizelyUserContext user = optimizelyClient.createUserContext("user123", attributes);
OptimizelyDecision decision = user.decide("my-cmab-flag");
// Cache will store this decision for 10 minutesAdvanced: custom cache implementation
For multi-instance deployments or distributed systems, you can provide your own cache implementation.
import com.optimizely.ab.Optimizely;
import com.optimizely.ab.OptimizelyFactory;
import com.optimizely.ab.OptimizelyUserContext;
import com.optimizely.ab.cmab.DefaultCmabClient;
import com.optimizely.ab.cmab.service.CmabCacheValue;
import com.optimizely.ab.cmab.service.DefaultCmabService;
import com.optimizely.ab.config.HttpProjectConfigManager;
import com.optimizely.ab.internal.Cache;
import com.optimizely.ab.optimizelydecision.OptimizelyDecision;
import java.util.HashMap;
import java.util.Map;
// Example: Custom cache implementation for distributed systems
// You can use Redis, Memcached, or any other caching system
class CustomCmabCache implements Cache<CmabCacheValue> {
public CustomCmabCache() {
// Initialize your cache backend here
// For example: this.redis = new JedisPool(), this.memcached = new MemcachedClient(), etc.
}
@Override
public CmabCacheValue lookup(String key) {
// Retrieve value from your cache backend
// Return the cached value if found, null otherwise
// Example: String json = redis.get(key); return deserialize(json);
return null;
}
@Override
public void save(String key, CmabCacheValue 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, serialize(value));
}
@Override
public void remove(String key) {
// Remove the key from your cache backend
// Example: redis.del(key);
}
@Override
public void reset() {
// Clear all CMAB cache entries from your cache backend
// Example: redis.flushDB() or delete keys matching a pattern
}
}
// Create and configure your custom cache
Cache<CmabCacheValue> customCache = new CustomCmabCache();
// Configure CMAB service with custom cache
DefaultCmabService cmabService = DefaultCmabService.builder()
.withClient(new DefaultCmabClient())
.withCustomCache(customCache)
.build();
// Create ProjectConfigManager
HttpProjectConfigManager configManager = HttpProjectConfigManager.builder()
.withSdkKey("YOUR_SDK_KEY")
.build();
// Create Optimizely client with custom CMAB service
Optimizely optimizelyClient = OptimizelyFactory.newDefaultInstance(
configManager,
null,
null,
null,
cmabService
);
// CMAB decisions now use your custom cache
Map<String, Object> attributes = new HashMap<>();
attributes.put("device_type", "mobile");
OptimizelyUserContext user = optimizelyClient.createUserContext("user123", attributes);
OptimizelyDecision decision = user.decide("my-cmab-flag");Cache behavior
Automatic cache invalidation
The cache is automatically invalidated when the following occurs:
- The cached entry's TTL expires.
- CMAB attribute values change for a user (detected through MD5 hash comparison).
IGNORE_CMAB_CACHEdecide option is used.INVALIDATE_USER_CMAB_CACHEdecide option is used.RESET_CMAB_CACHEdecide option is used.
CMAB-specific decide options
Control cache behavior on a per-decision basis using decide options.
import com.optimizely.ab.OptimizelyUserContext;
import com.optimizely.ab.optimizelydecision.OptimizelyDecideOption;
import com.optimizely.ab.optimizelydecision.OptimizelyDecision;
import java.util.Arrays;
// Ignore cache for this decision (always fetch fresh)
OptimizelyDecision decision = user.decide(
"my-flag",
Arrays.asList(OptimizelyDecideOption.IGNORE_CMAB_CACHE)
);
// Invalidate cached decision for this user and experiment
decision = user.decide(
"my-flag",
Arrays.asList(OptimizelyDecideOption.INVALIDATE_USER_CMAB_CACHE)
);
// Clear entire CMAB cache
decision = user.decide(
"my-flag",
Arrays.asList(OptimizelyDecideOption.RESET_CMAB_CACHE)
);
// Combine multiple options
decision = user.decide(
"my-flag",
Arrays.asList(
OptimizelyDecideOption.INCLUDE_REASONS,
OptimizelyDecideOption.IGNORE_CMAB_CACHE
)
);Available CMAB decideoOptions
| Decide Option | Description |
|---|---|
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 when
- 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 instance, 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 2 hours ago