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 Ruby SDK

Configure caching behavior for Contextual Multi-Armed Bandit (CMAB) decisions in the Ruby 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

  • 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:

SettingDefault ValueDescription
Cache ImplementationLRUCacheThread-safe in-memory LRU cache.
Cache Size10000Maximum number of cached decisions.
Cache TTL1800 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 to lookup, save, remove, and reset methods.

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
end

Cache 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_CACHE decide option is used.
  • The INVALIDATE_USER_CMAB_CACHE decide option is used.
  • The RESET_CMAB_CACHE decide 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.