26
26
import org .slf4j .Logger ;
27
27
import org .slf4j .LoggerFactory ;
28
28
29
- import java .util .List ;
30
- import java .util .Map ;
31
-
32
29
import javax .annotation .Nonnull ;
33
30
import javax .annotation .Nullable ;
34
31
import javax .annotation .concurrent .Immutable ;
32
+ import java .util .List ;
33
+ import java .util .Map ;
35
34
36
35
/**
37
36
* Default Optimizely bucketing algorithm that evenly distributes users using the Murmur3 hash of some provided
46
45
public class Bucketer {
47
46
48
47
private final ProjectConfig projectConfig ;
49
-
50
- @ Nullable private final UserProfile userProfile ;
48
+ private final UserProfile userProfile ;
51
49
52
50
private static final Logger logger = LoggerFactory .getLogger (Bucketer .class );
53
51
@@ -112,27 +110,6 @@ private Variation bucketToVariation(@Nonnull Experiment experiment,
112
110
String experimentKey = experiment .getKey ();
113
111
String combinedBucketId = userId + experimentId ;
114
112
115
- // If a user profile instance is present then check it for a saved variation
116
- if (userProfile != null ) {
117
- String variationId = userProfile .lookup (userId , experimentId );
118
- if (variationId != null ) {
119
- Variation savedVariation = projectConfig
120
- .getExperimentIdMapping ()
121
- .get (experimentId )
122
- .getVariationIdToVariationMap ()
123
- .get (variationId );
124
- logger .info ("Returning previously activated variation \" {}\" of experiment \" {}\" "
125
- + "for user \" {}\" from user profile." ,
126
- savedVariation .getKey (), experimentKey , userId );
127
- // A variation is stored for this combined bucket id
128
- return savedVariation ;
129
- } else {
130
- logger .info ("No previously activated variation of experiment \" {}\" "
131
- + "for user \" {}\" found in user profile." ,
132
- experimentKey , userId );
133
- }
134
- }
135
-
136
113
List <TrafficAllocation > trafficAllocations = experiment .getTrafficAllocation ();
137
114
138
115
int hashCode = MurmurHash3 .murmurhash3_x86_32 (combinedBucketId , 0 , combinedBucketId .length (), MURMUR_HASH_SEED );
@@ -146,18 +123,6 @@ private Variation bucketToVariation(@Nonnull Experiment experiment,
146
123
logger .info ("User \" {}\" is in variation \" {}\" of experiment \" {}\" ." , userId , variationKey ,
147
124
experimentKey );
148
125
149
- // If a user profile is present give it a variation to store
150
- if (userProfile != null ) {
151
- boolean saved = userProfile .save (userId , experimentId , bucketedVariationId );
152
- if (saved ) {
153
- logger .info ("Saved variation \" {}\" of experiment \" {}\" for user \" {}\" ." ,
154
- bucketedVariationId , experimentId , userId );
155
- } else {
156
- logger .warn ("Failed to save variation \" {}\" of experiment \" {}\" for user \" {}\" ." ,
157
- bucketedVariationId , experimentId , userId );
158
- }
159
- }
160
-
161
126
return bucketedVariation ;
162
127
}
163
128
@@ -169,6 +134,7 @@ private Variation bucketToVariation(@Nonnull Experiment experiment,
169
134
public @ Nullable Variation bucket (@ Nonnull Experiment experiment ,
170
135
@ Nonnull String userId ) {
171
136
137
+ // ---------- Check whitelist ----------
172
138
// if a user has a forced variation mapping, return the respective variation
173
139
Map <String , String > userIdToVariationKeyMap = experiment .getUserIdToVariationKeyMap ();
174
140
if (userIdToVariationKeyMap .containsKey (userId )) {
@@ -178,12 +144,37 @@ private Variation bucketToVariation(@Nonnull Experiment experiment,
178
144
logger .info ("User \" {}\" is forced in variation \" {}\" ." , userId , forcedVariationKey );
179
145
} else {
180
146
logger .error ("Variation \" {}\" is not in the datafile. Not activating user \" {}\" ." , forcedVariationKey ,
181
- userId );
147
+ userId );
182
148
}
183
149
184
150
return forcedVariation ;
185
151
}
186
152
153
+ // ---------- Check User Profile for Sticky Bucketing ----------
154
+ // If a user profile instance is present then check it for a saved variation
155
+ String experimentId = experiment .getId ();
156
+ String experimentKey = experiment .getKey ();
157
+ if (userProfile != null ) {
158
+ String variationId = userProfile .lookup (userId , experimentId );
159
+ if (variationId != null ) {
160
+ Variation savedVariation = projectConfig
161
+ .getExperimentIdMapping ()
162
+ .get (experimentId )
163
+ .getVariationIdToVariationMap ()
164
+ .get (variationId );
165
+ logger .info ("Returning previously activated variation \" {}\" of experiment \" {}\" "
166
+ + "for user \" {}\" from user profile." ,
167
+ savedVariation .getKey (), experimentKey , userId );
168
+ // A variation is stored for this combined bucket id
169
+ return savedVariation ;
170
+ } else {
171
+ logger .info ("No previously activated variation of experiment \" {}\" "
172
+ + "for user \" {}\" found in user profile." ,
173
+ experimentKey , userId );
174
+ }
175
+ }
176
+
177
+ // ---------- Bucket User ----------
187
178
String groupId = experiment .getGroupId ();
188
179
// check whether the experiment belongs to a group
189
180
if (!groupId .isEmpty ()) {
@@ -198,16 +189,32 @@ private Variation bucketToVariation(@Nonnull Experiment experiment,
198
189
// don't perform further bucketing within the experiment
199
190
if (!bucketedExperiment .getId ().equals (experiment .getId ())) {
200
191
logger .info ("User \" {}\" is not in experiment \" {}\" of group {}." , userId , experiment .getKey (),
201
- experimentGroup .getId ());
192
+ experimentGroup .getId ());
202
193
return null ;
203
194
}
204
195
205
196
logger .info ("User \" {}\" is in experiment \" {}\" of group {}." , userId , experiment .getKey (),
206
- experimentGroup .getId ());
197
+ experimentGroup .getId ());
207
198
}
208
199
}
209
200
210
- return bucketToVariation (experiment , userId );
201
+ Variation bucketedVariation = bucketToVariation (experiment , userId );
202
+
203
+ // ---------- Save Variation to User Profile ----------
204
+ // If a user profile is present give it a variation to store
205
+ if (userProfile != null && bucketedVariation != null ) {
206
+ String bucketedVariationId = bucketedVariation .getId ();
207
+ boolean saved = userProfile .save (userId , experimentId , bucketedVariationId );
208
+ if (saved ) {
209
+ logger .info ("Saved variation \" {}\" of experiment \" {}\" for user \" {}\" ." ,
210
+ bucketedVariationId , experimentId , userId );
211
+ } else {
212
+ logger .warn ("Failed to save variation \" {}\" of experiment \" {}\" for user \" {}\" ." ,
213
+ bucketedVariationId , experimentId , userId );
214
+ }
215
+ }
216
+
217
+ return bucketedVariation ;
211
218
}
212
219
213
220
//======== Helper methods ========//
@@ -224,28 +231,5 @@ int generateBucketValue(int hashCode) {
224
231
return (int )Math .floor (MAX_TRAFFIC_VALUE * ratio );
225
232
}
226
233
227
- @ Nullable
228
- public UserProfile getUserProfile () {
229
- return userProfile ;
230
- }
231
234
232
- /**
233
- * Gives implementations of {@link UserProfile} a chance to remove records
234
- * of experiments that are deleted or not running.
235
- */
236
- public void cleanUserProfiles () {
237
- if (userProfile != null ) {
238
- Map <String , Map <String ,String >> records = userProfile .getAllRecords ();
239
- if (records != null ) {
240
- for (Map .Entry <String ,Map <String ,String >> record : records .entrySet ()) {
241
- for (String experimentId : record .getValue ().keySet ()) {
242
- Experiment experiment = projectConfig .getExperimentIdMapping ().get (experimentId );
243
- if (experiment == null || !experiment .isActive ()) {
244
- userProfile .remove (record .getKey (), experimentId );
245
- }
246
- }
247
- }
248
- }
249
- }
250
- }
251
235
}
0 commit comments