2626import org .slf4j .Logger ;
2727import org .slf4j .LoggerFactory ;
2828
29- import java .util .List ;
30- import java .util .Map ;
31-
3229import javax .annotation .Nonnull ;
3330import javax .annotation .Nullable ;
3431import javax .annotation .concurrent .Immutable ;
32+ import java .util .List ;
33+ import java .util .Map ;
3534
3635/**
3736 * Default Optimizely bucketing algorithm that evenly distributes users using the Murmur3 hash of some provided
4645public class Bucketer {
4746
4847 private final ProjectConfig projectConfig ;
49-
50- @ Nullable private final UserProfile userProfile ;
48+ private final UserProfile userProfile ;
5149
5250 private static final Logger logger = LoggerFactory .getLogger (Bucketer .class );
5351
@@ -112,27 +110,6 @@ private Variation bucketToVariation(@Nonnull Experiment experiment,
112110 String experimentKey = experiment .getKey ();
113111 String combinedBucketId = userId + experimentId ;
114112
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-
136113 List <TrafficAllocation > trafficAllocations = experiment .getTrafficAllocation ();
137114
138115 int hashCode = MurmurHash3 .murmurhash3_x86_32 (combinedBucketId , 0 , combinedBucketId .length (), MURMUR_HASH_SEED );
@@ -146,18 +123,6 @@ private Variation bucketToVariation(@Nonnull Experiment experiment,
146123 logger .info ("User \" {}\" is in variation \" {}\" of experiment \" {}\" ." , userId , variationKey ,
147124 experimentKey );
148125
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-
161126 return bucketedVariation ;
162127 }
163128
@@ -169,6 +134,7 @@ private Variation bucketToVariation(@Nonnull Experiment experiment,
169134 public @ Nullable Variation bucket (@ Nonnull Experiment experiment ,
170135 @ Nonnull String userId ) {
171136
137+ // ---------- Check whitelist ----------
172138 // if a user has a forced variation mapping, return the respective variation
173139 Map <String , String > userIdToVariationKeyMap = experiment .getUserIdToVariationKeyMap ();
174140 if (userIdToVariationKeyMap .containsKey (userId )) {
@@ -178,12 +144,37 @@ private Variation bucketToVariation(@Nonnull Experiment experiment,
178144 logger .info ("User \" {}\" is forced in variation \" {}\" ." , userId , forcedVariationKey );
179145 } else {
180146 logger .error ("Variation \" {}\" is not in the datafile. Not activating user \" {}\" ." , forcedVariationKey ,
181- userId );
147+ userId );
182148 }
183149
184150 return forcedVariation ;
185151 }
186152
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 ----------
187178 String groupId = experiment .getGroupId ();
188179 // check whether the experiment belongs to a group
189180 if (!groupId .isEmpty ()) {
@@ -198,16 +189,32 @@ private Variation bucketToVariation(@Nonnull Experiment experiment,
198189 // don't perform further bucketing within the experiment
199190 if (!bucketedExperiment .getId ().equals (experiment .getId ())) {
200191 logger .info ("User \" {}\" is not in experiment \" {}\" of group {}." , userId , experiment .getKey (),
201- experimentGroup .getId ());
192+ experimentGroup .getId ());
202193 return null ;
203194 }
204195
205196 logger .info ("User \" {}\" is in experiment \" {}\" of group {}." , userId , experiment .getKey (),
206- experimentGroup .getId ());
197+ experimentGroup .getId ());
207198 }
208199 }
209200
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 ;
211218 }
212219
213220 //======== Helper methods ========//
@@ -224,28 +231,5 @@ int generateBucketValue(int hashCode) {
224231 return (int )Math .floor (MAX_TRAFFIC_VALUE * ratio );
225232 }
226233
227- @ Nullable
228- public UserProfile getUserProfile () {
229- return userProfile ;
230- }
231234
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- }
251235}
0 commit comments