# Server Persistent Assignment

Persistent assignment lets you ensure that a user's variant stays consistent while an experiment is running, regardless of changes to allocation or targeting.

## Persistent storage adapter

The persistent storage adapter lets you plug in your own storage solution that the Statsig SDK uses to persist user assignments. The storage interface provides a `load` and `save` API for read/write operations.

{% callout type="info" %}
Statsig supports this only in `Go`, `Ruby`, `Legacy Node`, `Node Core`, `Java Core`, `Kotlin`, `.Net`, `Python Core`, `PHP Core`, `Rust Core`
{% /callout %}

### Persistent storage logic

- Providing a storage adapter at Statsig initialization gives the SDK access to read and write to your custom storage.
- Providing user persisted values to `get_experiment` tells the SDK to:
  - **save** the current user's evaluation **on first evaluation** (only when the experiment or layer **is active**)
  - **load** the previously saved evaluation **on subsequent evaluations**
- Statsig deletes persisted values when:
  - You call `getExperiment` with `user_persisted_values=None`
  - The experiment isn't active

### Persistent assignment options (limited SDK support)

- **Enforce Targeting**: `boolean`, default: `false`
  - Whether to enforce targeting rules before assigning persisted values

{% tabs %}
{% tab title="Kotlin" %}

```kotlin
val options = GetExperimentOptions(
  ...
  persistentAssignmentOptions = PersistentAssignmentOptions(
    enforceTargeting = true,
  )
)
```

{% /tab %}

{% tab title="Node JS" %}

```ts
const options: GetExperimentOptions = {
  ...
  persistentAssignmentOptions: {
    enforceTargeting: true,
  }
}

```

{% /tab %}
{% /tabs %}

### Example usage

{% tabs %}
{% tab title="Ruby" %}

```ruby
Statsig.initialize(
  'secret-key',
  StatsigOptions.new(
    user_persistent_storage: DummyPersistentStorageAdapter.new
  )
)
persisted_user = StatsigUser.new({ 'userID' => 'test-123' })
exp = Statsig.get_experiment( # User gets saved to persisted storage
  persisted_user,
  'active_experiment',
  Statsig::GetExperimentOptions.new(
    user_persisted_values: Statsig.get_user_persisted_values(persisted_user, 'userID')
  )
)
puts exp.group_name # 'Control'
exp = Statsig.get_experiment( # User evaluates using values from persisted storage
  StatsigUser.new({'userID' => 'unknown'}),
  'active_experiment',
  Statsig::GetExperimentOptions.new(
    user_persisted_values: Statsig.get_user_persisted_values(persisted_user, 'userID')
  )
)
puts exp.group_name # 'Control'
```

{% /tab %}

{% tab title="Python (Python Core)" %}

```python
from statsig_python_core import Statsig, StatsigUser, StatsigOptions, ExperimentEvaluationOptions, PersistentStorage

options = StatsigOptions(persistent_storage = MyPersistentStorage())
statsig = Statsig.initialize(options).wait
user = StatsigUser("a-user")
exp = statsig.get_experiment(StatsigUser("a-user"), ExperimentEvaluationOptions(user_persisted_values= PersistentStorage.get_user_persisted_value(user, "user_id")))
print(f"{exp.group_name}") # control
```

{% /tab %}

{% tab title="Java Core" %}

```java
PersistentStorage persistentStorage = new MyPersistentStorage(); // See /server-core/java-core/#persistent-storage on how to implement it
StatsigOptions options = new StatsigOptions.Builder()
    .setPersistentStorage(persistentStorage)
    .build();

Statsig statsig = new Statsig("secret-key", options);
statsig.initialize().get();

StatsigUser persistedUser = new StatsigUser.Builder().setUserID("test-123").build();
Map<String, StickyValues> persistedValues = persistentStorage.getValuesForUser(persistedUser, "userID");
if (persistedValues == null) {
    persistedValues = new HashMap<>();
}

Experiment exp = statsig.getExperiment(
    persistedUser,
    "active_experiment",
    new GetExperimentOptions(persistedValues)
);
System.out.println(exp.getGroupName()); // "Control"

StatsigUser unknownUser = new StatsigUser.Builder().setUserID("unknown").build();
Map<String, StickyValues> valuesForPersistedUser = persistentStorage.getValuesForUser(persistedUser, "userID");
Experiment persistedExp = statsig.getExperiment(
    unknownUser,
    "active_experiment",
    new GetExperimentOptions(valuesForPersistedUser)
);
System.out.println(persistedExp.getGroupName()); // "Control"
```

{% /tab %}

{% tab title="Node Core" %}

```typescript
let persistedStorage = new MyPersistentStorage(); // See /server-core/node-core/#persistent-storage on how to implement it
let options = new StatsigOptions((persistentStorage = persistedStorage));
let statsig = new Statsig(secretKye, options);
let user = new StatsigUser("a-user");
let exp = statsig.getExperiment(
  user,
  ExperimentEvaluationOptions(
    (userPersistentValues = persistedStorage.getUserPersistedValues(
      user,
      "user_id",
    )),
  ),
);
```

{% /tab %}

{% tab title="PHP Core" %}

```php
$persistent_storage = new MyPersistentStorage(); // See /server-core/php-core/#persistent-storage on how to implement it
$options = new StatsigOptions(persistent_storage: $persistent_storage);

$statsig = new Statsig("secret-key", $options);
$statsig->initialize();

$persisted_user = new StatsigUser("test-123");
$persisted_values = $persistent_storage->getValuesForUser($persisted_user, "userID") ?? [];
$exp = $statsig->getExperiment(
    $persisted_user,
    "active_experiment",
    ["user_persisted_values" => $persisted_values],
);
echo $exp->groupName; // "Control"

$unknown_user = new StatsigUser("unknown");
$values_for_persisted_user = $persistent_storage->getValuesForUser($persisted_user, "userID");
$persisted_exp = $statsig->getExperiment(
    $unknown_user,
    "active_experiment",
    ["user_persisted_values" => $values_for_persisted_user],
);
echo $persisted_exp->groupName; // "Control"
```

{% /tab %}

{% tab title="Rust Core" %}

```rust
let persistent_storage = Arc::new(MyPersistentStorage::new()); // See /server-core/rust-core/#persistent-storage on how to implement it
let options = StatsigOptions::builder()
    .persistent_storage(Some(persistent_storage.clone()))
    .build();

let statsig = Statsig::new("secret-key", Some(Arc::new(options)));
statsig.initialize().await;

let persisted_user = StatsigUser::with_user_id("test-123");
let persisted_values = persistent_storage
    .get_values_for_user(&persisted_user, "userID")
    .unwrap_or_default();
let exp = statsig.get_experiment_with_options(
    &persisted_user,
    "active_experiment",
    ExperimentEvaluationOptions {
        user_persisted_values: Some(persisted_values),
        ..Default::default()
    },
);
println!("{}", exp.group_name.as_deref().unwrap_or("")); // "Control"

let unknown_user = StatsigUser::with_user_id("unknown");
let values_for_persisted_user = persistent_storage.get_values_for_user(&persisted_user, "userID");
let persisted_exp = statsig.get_experiment_with_options(
    &unknown_user,
    "active_experiment",
    ExperimentEvaluationOptions {
        user_persisted_values: values_for_persisted_user,
        ..Default::default()
    },
);
println!("{}", persisted_exp.group_name.as_deref().unwrap_or("")); // "Control"
```

{% /tab %}

{% tab title="Kotlin" %}

```kotlin
runBlocking {
  Statsig.initialize(
    "secret-key",
    StatsigOptions(userPersistentStorage = MyPersistentStorageAdapter())
  )
}
val persistedUser = StatsigUser("test-123")
var exp = Statsig.getExperimentSync(
  persistedUser,
  "active_experiment",
  GetExperimentOptions(
    userPersistedValues = Statsig.getUserPersistedValues(persistedUser, "userID"),
  ),
)
println(exp.groupName) // "Control"
exp = Statsig.getExperimentSync(
  StatsigUser("unknown"),
  "active_experiment",
  GetExperimentOptions(
    userPersistedValues = Statsig.getUserPersistedValues(persistedUser, "userID"),
  ),
)
println(exp.groupName) // "Control"
```

{% /tab %}

{% tab title="Node JS" %}

```ts
await Statsig.initialize("secret-key", {
  userPersistentStorage: new MyPersistentStorageAdapter(),
});
const persistedUser: StatsigUser = { userID: "123" };
let exp = Statsig.getExperimentSync(persistedUser, "active_experiment", {
  userPersistedValues: Statsig.getUserPersistedValues(user, "userID"),
});
console.log(exp.getGroupName()); // "Control"
exp = Statsig.getExperimentSync({ userID: "unknown" }, "active_experiment", {
  userPersistedValues: Statsig.getUserPersistedValues(user, "userID"),
});
console.log(exp.getGroupName()); // "Control"
```

{% /tab %}

{% tab title="Go" %}

```go
InitializeWithOptions(
  "secret-key",
  &Options{
    UserPersistentStorage: persistentStorage,
  }
)
persistedUser := User{UserID: "123"}
exp := GetExperimentWithOptions(
  persistedUser,
  "active_experiment",
  &GetExperimentOptions{
    PersistedValues: GetUserPersistedValues(persistedUser, "userID")
  }
)
fmt.Println(exp.GroupName) // "Control"
exp = GetExperimentWithOptions(
  User{UserID: "unknown"},
  "active_experiment",
  &GetExperimentOptions{
    PersistedValues: GetUserPersistedValues(persistedUser, "userID")
  }
)
fmt.Println(exp.GroupName) // "Control"
```

{% /tab %}

{% tab title=".Net" %}

```csharp
var options = new StatsigServerOptions();
options.UserPersistentStorage = new MyPersistentStorageAdapter()
await StatsigServer.Initialize("server-secret-key", options);

var persistedUser = new StatsigUser { UserID = "123" };
var values = await StatsigServer.GetUserPersistedValues(persistedUser, "userID");
var getExpOptions = new StatsigGetExperimentOptions(values);
var exp = StatsigServer.GetExperimentSync(persistedUser, "active_experiment", getExpOptions);
Console.WriteLine(exp.GroupName); // "Control"

var newValues = await StatsigServer.GetUserPersistedValues(persistedUser, "userID");
var newGetExpOptions = StatsigGetExperimentOptions(newValues);

var newExp = StatsigServer.GetExperimentSync(new StatsigUser {UserID = "unknown"}, "active_experiment", newGetExpOptions);
Console.WriteLine(newExp.GroupName); // "Control"

```

{% /tab %}
{% /tabs %}
