Persistent DataStore
Persist bucketing decisions across sessions and requests
By default, the SDK keeps bucketing decisions in memory only. This means each new session (JavaScript) or HTTP request (PHP) recalculates which variation a visitor sees. While the MurmurHash algorithm ensures deterministic bucketing for the same visitor ID and configuration, changes to experience settings (adding/removing variations, adjusting traffic allocation) can shift a returning visitor to a different variation.
A persistent DataStore solves this by saving bucketing decisions to durable storage. Once a visitor is bucketed, subsequent sessions or requests read the stored assignment instead of recalculating it, keeping the visitor's experience consistent even when the project configuration changes.
Persistence also enables cross-request conversion attribution in PHP. For example, a visitor bucketed on page 1 can have a purchase tracked on page 3 and the SDK will correctly link the conversion to the original variation.
The DataStore Interface
Both SDKs expect a DataStore with two methods:
| Method | Signature | Description |
|---|---|---|
get | get(key): value | Retrieve a value by key. When called without a key (JS only), return all stored data. |
set | set(key, value): void | Store a value under the given key. |
In the JavaScript SDK, you pass any object implementing these two methods via the dataStore configuration option.
In the PHP SDK, the DataStore defaults to the PSR-16 CacheInterface provided via the cache option. This cache serves a dual purpose: it stores both the fetched project configuration (with a TTL controlled by dataRefreshInterval) and visitor bucketing data. You can optionally pass a separate dataStore to decouple visitor data from config caching.
Implementing a Custom DataStore
import type {ConvertInterface, ConvertConfig} from '@convertcom/js-sdk';
import ConvertSDK from '@convertcom/js-sdk';
class CustomDataStore {
#data = {};
get(key) {
if (!key) return this.#data;
return this.#data[key.toString()];
}
set(key, value) {
if (!key) throw new Error('Invalid CustomDataStore key!');
this.#data[key.toString()] = value;
}
}
const dataStore = new CustomDataStore();
const convertSDK: ConvertInterface = new ConvertSDK({
sdkKey: 'xxx',
dataStore
} as ConvertConfig);use ConvertSdk\ConvertSDK;
use Symfony\Component\Cache\Adapter\RedisAdapter;
use Symfony\Component\Cache\Psr16Cache;
// Create a PSR-16 cache backed by Redis
$redisConnection = RedisAdapter::createConnection('redis://localhost:6379');
$psr6Cache = new RedisAdapter($redisConnection);
$cache = new Psr16Cache($psr6Cache);
$sdk = ConvertSDK::create([
'sdkKey' => 'your-sdk-key',
'cache' => $cache, // PSR-16 CacheInterface
]);The JavaScript example above uses an in-memory object for illustration. In production, replace the backing store with Redis, a database, or any persistent layer appropriate for your environment. For Cloudflare Workers, use the KVDataStore from @convertcom/js-sdk-cloudflare.
The PHP example uses Symfony's Redis adapter wrapped in a PSR-16 interface. Any PSR-16 implementation works -- Memcached, filesystem, database, or a custom adapter.
Using Memcached (PHP)
use Symfony\Component\Cache\Adapter\MemcachedAdapter;
use Symfony\Component\Cache\Psr16Cache;
$memcached = MemcachedAdapter::createConnection('memcached://localhost:11211');
$psr6Cache = new MemcachedAdapter($memcached);
$cache = new Psr16Cache($psr6Cache);
$sdk = ConvertSDK::create([
'sdkKey' => 'your-sdk-key',
'cache' => $cache,
]);Separating Config Cache from Visitor Store (PHP)
If you need a different storage backend for visitor data than for config caching, pass the dataStore option separately:
$sdk = ConvertSDK::create([
'sdkKey' => 'your-sdk-key',
'cache' => $configCache, // Used for config caching only
'dataStore' => $visitorStore, // Used for visitor data persistence
]);When dataStore is provided, it takes precedence over cache for visitor data.
Configuring the DataStore at Initialization
Pass the DataStore when creating the SDK instance:
import ConvertSDK from '@convertcom/js-sdk';
const convertSDK = new ConvertSDK({
sdkKey: 'xxx',
dataStore: myDataStoreInstance
});
convertSDK.onReady().then(() => {
const context = convertSDK.createContext('user-123');
const variation = context.runExperience('experience-key');
// Bucketing decision is persisted via the DataStore
});use ConvertSdk\ConvertSDK;
$sdk = ConvertSDK::create([
'sdkKey' => 'your-sdk-key',
'cache' => $cache,
]);
// Request 1: Visitor is bucketed
$context = $sdk->createContext('visitor-123', ['country' => 'US']);
$variation = $context->runExperience('homepage-redesign');
// Bucketing decision is persisted to Redis
// --- later, in a separate HTTP request ---
// Request 2: Conversion is attributed to the correct variation
$context = $sdk->createContext('visitor-123');
$context->trackConversion('purchase-completed');
// SDK retrieves bucketing from Redis -> conversion is linked to the variationVisitor ID Continuity (PHP)
The SDK identifies visitors by the $visitorId you pass to createContext(). You are responsible for providing the same ID across requests. Common approaches:
- Session ID --
session_id()(works for web apps with PHP sessions) - Cookie -- a persistent cookie with a unique visitor token
- Authenticated user ID -- for logged-in users
How It Works Internally
Without a DataStore, bucketing decisions live only in memory. With a DataStore, the SDK reads from and writes to it via a DataStoreManager wrapper. On each bucketing call the SDK checks the DataStore first; if a stored decision exists for that visitor and experience, it is returned directly. Otherwise, the SDK calculates a new bucketing decision and writes it to the DataStore for future use.
In PHP, the default in-memory ArrayCache is replaced on each request, so bucketing is recalculated every time. Swapping in a persistent PSR-16 cache (Redis, Memcached, etc.) makes the first request calculate and store the decision, while subsequent requests for the same visitor read it from the cache and skip the computation.
See the SDK configuration options (JS | PHP) for the full list of initialization parameters, and the visitor context guide for details on creating and managing visitor contexts.
Updated about 1 month ago