Disclaimer: This website requires Please enable JavaScript in your browser settings for the best experience.

Dev guideRecipesAPI ReferenceChangelog
Dev guideAPI ReferenceRecipesChangelogUser GuideGitHubDev CommunityOptimizely AcademySubmit a ticketLog In
Dev guide

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.

👍

Beta

CMAB 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

CacheSize

int

No

10000

Maximum number of decisions to cache. Increase for applications with many users or experiments.

CacheTTL

time.Duration

No

30 * time.Minute

How long cache entries remain valid. Lower values ensure fresher decisions but increase API calls.

HTTPTimeout

time.Duration

No

10 * time.Second

Timeout for CMAB API requests. Increase for slower network connections.

Cache

cache.CacheWithRemove

No

nil

Custom cache implementation for distributed systems. If nil, the Go SDK uses an in-memory cache.

PredictionEndpointTemplate

https://prediction.cmab.optimizely.com/predict/%s

No

string

URL template for CMAB prediction API. The %s placeholder is replaced with the rule ID. Useful for proxying.

📘

Note

When CacheSize, CacheTTL, or HTTPTimeout are set to 0, 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_CACHE decide option is used.
  • RESET_CMAB_CACHE decide 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).