Configure the CMAB cache for the Go SDK
How to configure caching behavior for Contextual Multi-Armed Bandit (CMAB) decisions in the Feature Experimentation Go 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
- Go SDK version 2.2.0 or higher.
- A CMAB-enabled experiment in your Optimizely Feature Experimentation project.
Minimum SDK version
2.2.0
Description
When a user is bucketed into a CMAB experiment, the Go 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 Go SDK uses an in-memory Least Recently Used (LRU) cache with a maximum size of 10000 entries and a TTL of 30 minutes. You can customize these settings or provide your own cache implementation.
Parameters
Configure CMAB caching by passing a CmabConfig struct to the WithCmabConfig option when creating your Optimizely client.
type CmabConfig struct {
CacheSize int // Maximum number of cached decisions.
CacheTTL time.Duration // Time-to-live for cache entries.
HTTPTimeout time.Duration // Timeout for CMAB API requests.
Cache cache.CacheWithRemove // Optional custom cache implementation.
}Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
|
| No |
| Maximum number of decisions to cache. Increase for applications with many users or experiments. |
|
| No |
| How long cache entries remain valid. Lower values ensure fresher decisions but increase API calls. |
|
| No |
| Timeout for CMAB API requests. Increase for slower network connections. |
|
| No |
| Custom cache implementation for distributed systems. If |
|
| No |
| URL template for CMAB
prediction API. The |
NoteWhen
CacheSize,CacheTTL, orHTTPTimeoutare set to0, the Go SDK uses the default values.
Returns
The WithCmabConfig option returns an OptionFunc that configures the Optimizely client with your CMAB cache settings.
Example
Basic configuration
Adjust the cache size and TTL for your application's needs, like in the following example:
package main
import (
"time"
"github.com/optimizely/go-sdk/v2/pkg/client"
)
func main() {
cmabConfig := client.CmabConfig{
CacheSize: 500, // Cache up to 500 decisions
CacheTTL: 10 * time.Minute, // Refresh cache every 10 minutes
HTTPTimeout: 15 * time.Second, // Allow 15 seconds for CMAB API calls
}
factory := client.OptimizelyFactory{
SDKKey: "YOUR_SDK_KEY",
}
optimizelyClient, err := factory.Client(
client.WithCmabConfig(&cmabConfig),
)
if err != nil {
panic(err)
}
defer optimizelyClient.Close()
// Use the client normally
user := optimizelyClient.CreateUserContext("user123", map[string]interface{}{
"age": 25,
"location": "US",
})
decision := user.Decide("my-cmab-flag", nil)
// Cache will store this decision for 10 minutes
}Optionally, you can customize prediction endpoint proxy or routing scenarios, like in the following example:
cmabConfig := client.CmabConfig{
CacheSize: 500, // Cache up to 500 decisions
CacheTTL: 10 * time.Minute, // Refresh cache every 10 minutes
HTTPTimeout: 15 * time.Second, // Allow 15 seconds for CMAB API calls
// Optional custom endpoint for proxy or routing scenarios
// defaults to: "https://prediction.cmab.optimizely.com/predict/%s"
PredictionEndpointTemplate: "https://proxy.example.com/predict/%s",
}Advanced: Custom cache implementation
For multi-instance deployments, you can provide your own cache implementation that satisfies the cache.CacheWithRemove interface, like in the following example:
package main
import (
"time"
"github.com/optimizely/go-sdk/v2/pkg/client"
"github.com/optimizely/go-sdk/v2/pkg/cache"
)
// Example custom cache implementation
type MyCustomCache struct {
// Your cache implementation fields
}
func (c *MyCustomCache) Lookup(key interface{}) (interface{}, bool) {
// Implement lookup logic
return nil, false
}
func (c *MyCustomCache) Save(key, value interface{}) {
// Implement save logic
}
func (c *MyCustomCache) Remove(key interface{}) {
// Implement remove logic
}
func (c *MyCustomCache) Reset() {
// Implement reset logic
}
func main() {
// Create custom cache
customCache := &MyCustomCache{}
// Use custom cache for CMAB
cmabConfig := client.CmabConfig{
Cache: customCache,
}
factory := client.OptimizelyFactory{
SDKKey: "YOUR_SDK_KEY",
}
optimizelyClient, err := factory.Client(
client.WithCmabConfig(&cmabConfig),
)
if err != nil {
panic(err)
}
defer optimizelyClient.Close()
// CMAB decisions now use your custom cache
}Custom cache interface
To implement a custom cache, your cache must implement the cache.CacheWithRemove interface:
type CacheWithRemove interface {
// Lookup retrieves a value from the cache
// Returns (value, true) if found, (nil, false) if not found
Lookup(key interface{}) (interface{}, bool)
// Save stores a key-value pair in the cache
Save(key, value interface{})
// Remove deletes a key from the cache
Remove(key interface{})
// Reset clears all entries from the cache
Reset()
}Cache behavior
Cache invalidation
The cache is automatically invalidated when:
- The cached entry's TTL expires.
- CMAB attribute values change for a user (detected via attribute hash comparison).
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, like in the following example:
import "github.com/optimizely/go-sdk/v2/pkg/decide"
// Ignore cache for this decision (always fetch fresh).
decision := user.Decide("my-flag", []decide.OptimizelyDecideOptions{
decide.IgnoreCMABCache,
})
// Invalidate cached decision for this user and experiment.
decision := user.Decide("my-flag", []decide.OptimizelyDecideOptions{
decide.InvalidateUserCMABCache,
})
// Clear entire CMAB cache.
decision := user.Decide("my-flag", []decide.OptimizelyDecideOptions{
decide.ResetCMABCache,
})The following are the available decide options with a short description of what they do:
IgnoreCMABCache– Bypass cache and fetch a fresh decision from CMAB service.InvalidateUserCMABCache– Remove cached decision for this user and experiment before deciding.ResetCMABCache– Clear all entries from the CMAB cache before deciding.
Decision reasons
The Go SDK provides detailed reasons for CMAB decisions that help you understand cache behavior. Access reasons using decision.Reasons, like in the following example:
import "github.com/optimizely/go-sdk/v2/pkg/decide"
user := optimizelyClient.CreateUserContext("user123", map[string]interface{}{
"age": 25,
})
decision := user.Decide("my-cmab-flag", []decide.OptimizelyDecideOptions{
decide.IncludeReasons,
})
// Print decision reasons.
for _, reason := range decision.Reasons {
fmt.Println(reason)
}When to use a custom cache
Consider implementing a custom cache in the following situations:
- Multi-instance deployments – Share cache across multiple application instances.
- Distributed systems – Use external caching systems for centralized caching.
- Persistent cache – Maintain cache across application restarts.
- Custom eviction policies – Implement domain-specific cache management.
- Monitoring – Track cache metrics (hit rate, memory usage, and so on).
Updated about 1 hour ago