|
1 | | -# Sticky Resolve Documentation |
| 1 | +# Materialization Store Documentation |
2 | 2 |
|
3 | 3 | ## Overview |
4 | 4 |
|
5 | | -Sticky Resolve ensures users receive the same variant throughout an experiment, even if their targeting attributes change or you pause new assignments. |
| 5 | +The Materialization Store provides persistent storage for flag resolution data, supporting two key use cases: |
6 | 6 |
|
7 | | -**Two main use cases:** |
8 | | -1. **Consistent experience** - User moves countries but keeps the same variant |
9 | | -2. **Pause intake** - Stop new assignments while maintaining existing ones |
| 7 | +1. **Sticky Assignments** - Maintain consistent variant assignments across evaluations even when targeting attributes change. This enables pausing intake (stopping new users from entering an experiment) while keeping existing users in their assigned variants. |
10 | 8 |
|
11 | | -**Default behavior:** Sticky assignments are managed by Confidence servers with automatic 90-day TTL. When needed, the provider makes a network call to Confidence. No setup required. |
| 9 | +2. **Custom Targeting via Materialized Segments** - Precomputed sets of identifiers from datasets that should be targeted. Instead of evaluating complex targeting rules at runtime, materializations allow efficient lookup of whether a unit (user, session, etc.) is included in a target segment. |
| 10 | + |
| 11 | +**Default behavior:** Materializations are managed by Confidence servers with automatic 90-day TTL. When needed, the provider makes a network call to Confidence. No setup required. |
12 | 12 |
|
13 | 13 | ## How It Works |
14 | 14 |
|
15 | | -### Default: Server-Side Storage (RemoteResolverFallback) |
| 15 | +### Default: Server-Side Storage (UnsupportedMaterializationStore) |
16 | 16 |
|
17 | 17 | **Flow:** |
18 | 18 | 1. Local WASM resolver attempts to resolve |
19 | | -2. If sticky data needed → network call to Confidence |
20 | | -3. Confidence checks its sticky repository, returns variant |
21 | | -4. Assignment stored server-side with 90-day TTL (auto-renewed on access) |
| 19 | +2. If materialization data needed → `UnsupportedMaterializationStore` throws exception |
| 20 | +3. Provider falls back to remote gRPC resolution via Confidence |
| 21 | +4. Confidence checks its materialization repository, returns variant/inclusion data |
| 22 | +5. Data stored server-side with 90-day TTL (auto-renewed on access) |
22 | 23 |
|
23 | 24 | **Server-side configuration (in Confidence UI):** |
24 | 25 | - Optionally skip targeting criteria for sticky assignments |
25 | 26 | - Pause/resume new entity intake |
26 | 27 | - Automatic TTL management |
27 | 28 |
|
28 | | -### Custom: Local Storage (MaterializationRepository) |
| 29 | +### Custom: Local Storage (MaterializationStore) |
29 | 30 |
|
30 | | -Implement `MaterializationRepository` to store assignments locally and eliminate network calls. |
| 31 | +Implement `MaterializationStore` to store materialization data locally and eliminate network calls. |
31 | 32 |
|
32 | 33 | **Interface:** |
33 | 34 | ```java |
34 | | -public interface MaterializationRepository extends StickyResolveStrategy { |
35 | | - // Load assignments for a unit (e.g., user ID) |
36 | | - CompletableFuture<Map<String, MaterializationInfo>> loadMaterializedAssignmentsForUnit( |
37 | | - String unit, String materialization); |
38 | | - |
39 | | - // Store new assignments |
40 | | - CompletableFuture<Void> storeAssignment( |
41 | | - String unit, Map<String, MaterializationInfo> assignments); |
| 35 | +public interface MaterializationStore { |
| 36 | + // Batch read of materialization data |
| 37 | + CompletionStage<List<ReadResult>> read(List<? extends ReadOp> ops); |
| 38 | + |
| 39 | + // Batch write of materialization data (optional) |
| 40 | + default CompletionStage<Void> write(Set<? extends WriteOp> ops) { |
| 41 | + throw new UnsupportedOperationException("Unimplemented method 'write'"); |
| 42 | + } |
| 43 | +} |
| 44 | +``` |
| 45 | + |
| 46 | +**Key Concepts:** |
| 47 | +- **Materialization** - Identifier for a materialization context (experiment, flag, or materialized segment) |
| 48 | +- **Unit** - Entity identifier (user ID, session ID, etc.) |
| 49 | +- **Rule** - Targeting rule identifier within a flag |
| 50 | +- **Variant** - Assigned variant name for the unit+rule combination |
| 51 | + |
| 52 | +**Operation Types:** |
| 53 | + |
| 54 | +Read operations (`ReadOp`): |
| 55 | +```java |
| 56 | +// Check if unit is in materialized segment |
| 57 | +sealed interface ReadOp { |
| 58 | + record Inclusion(String materialization, String unit) implements ReadOp {} |
| 59 | + record Variant(String materialization, String unit, String rule) implements ReadOp {} |
| 60 | +} |
| 61 | +``` |
| 62 | + |
| 63 | +Read results (`ReadResult`): |
| 64 | +```java |
| 65 | +sealed interface ReadResult { |
| 66 | + // Result for segment membership check |
| 67 | + record Inclusion(String materialization, String unit, boolean included) implements ReadResult {} |
| 68 | + |
| 69 | + // Result for sticky variant assignment |
| 70 | + record Variant(String materialization, String unit, String rule, Optional<String> variant) |
| 71 | + implements ReadResult {} |
42 | 72 | } |
43 | 73 | ``` |
44 | 74 |
|
45 | | -**MaterializationInfo structure:** |
| 75 | +Write operations (`WriteOp`): |
46 | 76 | ```java |
47 | | -record MaterializationInfo( |
48 | | - boolean isUnitInMaterialization, |
49 | | - Map<String, String> ruleToVariant // rule ID -> variant name |
50 | | -) |
| 77 | +sealed interface WriteOp { |
| 78 | + // Store sticky variant assignment |
| 79 | + record Variant(String materialization, String unit, String rule, String variant) |
| 80 | + implements WriteOp {} |
| 81 | +} |
51 | 82 | ``` |
52 | 83 |
|
53 | 84 | ## Implementation Examples |
54 | 85 |
|
55 | | -### In-Memory (Testing/Development) |
| 86 | +### In-Memory (Testing/Development Only) |
| 87 | + |
| 88 | +**Warning: Do not use in-memory storage in production.** In-memory implementations lose all materialization data on restart, breaking sticky assignments and materialized segments. Production systems require persistent storage (Redis, database, etc.). |
56 | 89 |
|
57 | | -[Here is an example](src/test/java/com/spotify/confidence/InMemoryMaterializationRepoExample.java) on how to implement a simple in-memory `MaterializationRepository`. The same approach can be used with other more persistent storages (like Redis or similar) which is highly recommended for production use cases. |
| 90 | +See `InMemoryMaterializationStoreExample` for a reference implementation. Use this pattern with persistent storage backends for production. |
58 | 91 |
|
59 | 92 | #### Usage |
60 | 93 |
|
61 | 94 | ```java |
62 | | -MaterializationRepository repository = new InMemoryMaterializationRepoExample(); |
| 95 | +MaterializationStore store = new InMemoryMaterializationStore(); |
63 | 96 |
|
64 | 97 | OpenFeatureLocalResolveProvider provider = new OpenFeatureLocalResolveProvider( |
65 | 98 | clientSecret, |
66 | | - repository |
| 99 | + store |
67 | 100 | ); |
68 | 101 | ``` |
69 | 102 |
|
70 | 103 | ## Best Practices |
71 | 104 |
|
72 | | -1. **Fail gracefully** - Storage errors shouldn't fail flag resolution |
73 | | -2. **Use 90-day TTL** - Match Confidence's default behavior, renew on read |
74 | | -3. **Connection pooling** - Use pools for Redis/DB connections |
75 | | -4. **Monitor metrics** - Track cache hit rate, storage latency, errors |
76 | | -5. **Test both paths** - Missing assignments (cold start) and existing assignments |
| 105 | +1. **Thread-safe implementation** - Implementations must be thread-safe for concurrent flag resolution |
| 106 | +2. **Fail gracefully** - Storage errors shouldn't fail flag resolution; throw `MaterializationNotSupportedException` to trigger remote fallback |
| 107 | +3. **Use 90-day TTL** - Match Confidence's default behavior, renew on read |
| 108 | +4. **Idempotent writes** - Write operations should be idempotent |
| 109 | +5. **Batch operations** - Both `read` and `write` accept batches for efficiency |
| 110 | +6. **Connection pooling** - Use pools for Redis/DB connections |
| 111 | +7. **Monitor metrics** - Track cache hit rate, storage latency, errors |
| 112 | +8. **Test both paths** - Missing assignments (cold start) and existing assignments |
77 | 113 |
|
78 | 114 | ## When to Use Custom Storage |
79 | 115 |
|
80 | | -| Strategy | Best For | Trade-offs | |
| 116 | +| Implementation | Best For | Trade-offs | |
81 | 117 | |----------|----------|------------| |
82 | | -| **RemoteResolverFallback** (default) | Most apps | Simple, managed by Confidence. Network calls when needed. | |
83 | | -| **MaterializationRepository** (in-memory) | Single-instance apps, testing | Fast, no network. Lost on restart. | |
84 | | -| **MaterializationRepository** (Redis/DB) | Distributed/Multi instance apps | No network calls. Requires storage infra. | |
| 118 | +| **UnsupportedMaterializationStore** (default) | Most apps | Simple, managed by Confidence. Network calls when needed. | |
| 119 | +| **MaterializationStore** (in-memory) | Testing/development only | Fast, no network. **Lost on restart - do not use in production.** | |
| 120 | +| **MaterializationStore** (Redis/DB) | Production apps needing local storage | No network calls. Requires persistent storage infrastructure. | |
| 121 | + |
| 122 | +**Start with the default.** Only implement custom storage with persistent backends (Redis, database) if you need to eliminate network calls or work offline. |
| 123 | + |
| 124 | +## Error Handling |
85 | 125 |
|
86 | | -**Start with the default.** Only implement custom storage if you need to eliminate network calls or work offline. |
| 126 | +When implementing `read()`, throw `MaterializationNotSupportedException` to trigger fallback to remote gRPC resolution. This allows graceful degradation when local storage is unavailable. |
87 | 127 |
|
88 | 128 | ## Additional Resources |
89 | 129 |
|
|
0 commit comments