---
title: Configure the CMAB cache for the Go SDK
slug: feature-experimentation/docs/cmab-cache-config-for-the-go-sdk
docTags: 
createdAt: 2026-08-05T20:40:00.000Z
---

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.

```go
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\` | \`string\`                | No       | \`https\://prediction.cmab.optimizely.com/predict/%s\` | URL template for CMAB prediction API. The \`%s\` placeholder is replaced with the rule ID. Useful for proxying. |

:::hint{type="info"}
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:

```go
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:

```go
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:

```go
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:

```go
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 through 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:

```go
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:

```go
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).
