-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: assignment cache * bandit assignment cache * LRU assignment cache * Expiring cache * version bump for release
- Loading branch information
Showing
16 changed files
with
609 additions
and
71 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package cloud.eppo.api; | ||
|
||
import cloud.eppo.cache.AssignmentCacheEntry; | ||
import cloud.eppo.cache.AssignmentCacheKey; | ||
import java.util.Map; | ||
|
||
/** | ||
* {@link IAssignmentCache} implementation which takes a map to use as the underlying storage | ||
* mechanism. | ||
*/ | ||
public abstract class AbstractAssignmentCache implements IAssignmentCache { | ||
protected final Map<String, String> delegate; | ||
|
||
protected AbstractAssignmentCache(final Map<String, String> delegate) { | ||
this.delegate = delegate; | ||
} | ||
|
||
@Override | ||
public boolean hasEntry(AssignmentCacheEntry entry) { | ||
String serializedEntry = get(entry.getKey()); | ||
return serializedEntry != null && serializedEntry.equals(entry.getValueKeyString()); | ||
} | ||
|
||
private String get(AssignmentCacheKey key) { | ||
return delegate.get(key.toString()); | ||
} | ||
|
||
@Override | ||
public void put(AssignmentCacheEntry entry) { | ||
delegate.put(entry.getKeyString(), entry.getValueKeyString()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package cloud.eppo.api; | ||
|
||
import cloud.eppo.cache.AssignmentCacheEntry; | ||
|
||
/** | ||
* A cache capable of storing the key components of assignments (both variation and bandit) to | ||
* determine both presence and uniqueness of the cached value. | ||
*/ | ||
public interface IAssignmentCache { | ||
void put(AssignmentCacheEntry entry); | ||
|
||
/** | ||
* Determines whether the entry is present. Implementations must first check for presence by using | ||
* the `{@link AssignmentCacheEntry}.getKey()` method and then whether the cached value matches by | ||
* comparing the `getValueKeyString()` method results. | ||
*/ | ||
boolean hasEntry(AssignmentCacheEntry entry); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package cloud.eppo.cache; | ||
|
||
import cloud.eppo.logging.Assignment; | ||
import cloud.eppo.logging.BanditAssignment; | ||
import java.util.Objects; | ||
import org.jetbrains.annotations.NotNull; | ||
|
||
public class AssignmentCacheEntry { | ||
private final AssignmentCacheKey key; | ||
private final AssignmentCacheValue value; | ||
|
||
public AssignmentCacheEntry( | ||
@NotNull AssignmentCacheKey key, @NotNull AssignmentCacheValue value) { | ||
this.key = key; | ||
this.value = value; | ||
} | ||
|
||
public static AssignmentCacheEntry fromVariationAssignment(Assignment assignment) { | ||
return new AssignmentCacheEntry( | ||
new AssignmentCacheKey(assignment.getSubject(), assignment.getFeatureFlag()), | ||
new VariationCacheValue(assignment.getAllocation(), assignment.getVariation())); | ||
} | ||
|
||
public static AssignmentCacheEntry fromBanditAssignment(BanditAssignment assignment) { | ||
return new AssignmentCacheEntry( | ||
new AssignmentCacheKey(assignment.getSubject(), assignment.getFeatureFlag()), | ||
new BanditCacheValue(assignment.getBandit(), assignment.getAction())); | ||
} | ||
|
||
@NotNull public AssignmentCacheKey getKey() { | ||
return key; | ||
} | ||
|
||
@NotNull public String getKeyString() { | ||
return key.toString(); | ||
} | ||
|
||
@NotNull public String getValueKeyString() { | ||
return value.getValueIdentifier(); | ||
} | ||
|
||
@NotNull public AssignmentCacheValue getValue() { | ||
return value; | ||
} | ||
|
||
@Override | ||
public boolean equals(Object o) { | ||
if (this == o) return true; | ||
if (o == null || getClass() != o.getClass()) return false; | ||
AssignmentCacheEntry that = (AssignmentCacheEntry) o; | ||
return Objects.equals(key, that.key) | ||
&& Objects.equals(value.getValueIdentifier(), that.value.getValueIdentifier()); | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return Objects.hash(key, value); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package cloud.eppo.cache; | ||
|
||
import java.util.Objects; | ||
|
||
/** | ||
* Assignment cache keys are only on the subject and flag level, while a combination of keys and | ||
* fields are used for uniqueness checking. This way, if an assigned variation or bandit action | ||
* changes for a flag, it evicts the old one. Then, if an older assignment is later reassigned, it | ||
* will be treated as new. | ||
*/ | ||
public class AssignmentCacheKey { | ||
private final String subjectKey; | ||
private final String flagKey; | ||
|
||
public AssignmentCacheKey(String subjectKey, String flagKey) { | ||
this.subjectKey = subjectKey; | ||
this.flagKey = flagKey; | ||
} | ||
|
||
public String getSubjectKey() { | ||
return subjectKey; | ||
} | ||
|
||
public String getFlagKey() { | ||
return flagKey; | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return subjectKey + ";" + flagKey; | ||
} | ||
|
||
@Override | ||
public boolean equals(Object o) { | ||
if (this == o) return true; | ||
if (o == null || getClass() != o.getClass()) return false; | ||
AssignmentCacheKey that = (AssignmentCacheKey) o; | ||
return Objects.equals(toString(), that.toString()); | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return Objects.hash(subjectKey, flagKey); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package cloud.eppo.cache; | ||
|
||
import java.io.Serializable; | ||
|
||
public interface AssignmentCacheValue extends Serializable { | ||
String getValueIdentifier(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package cloud.eppo.cache; | ||
|
||
import java.util.Objects; | ||
|
||
public class BanditCacheValue implements AssignmentCacheValue { | ||
private final String banditKey; | ||
private final String actionKey; | ||
|
||
public BanditCacheValue(String banditKey, String actionKey) { | ||
this.banditKey = banditKey; | ||
this.actionKey = actionKey; | ||
} | ||
|
||
@Override | ||
public String getValueIdentifier() { | ||
return banditKey + ";" + actionKey; | ||
} | ||
|
||
@Override | ||
public boolean equals(Object o) { | ||
if (this == o) return true; | ||
if (o == null || getClass() != o.getClass()) return false; | ||
BanditCacheValue that = (BanditCacheValue) o; | ||
return Objects.equals(banditKey, that.banditKey) && Objects.equals(actionKey, that.actionKey); | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return Objects.hash(banditKey, actionKey); | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
src/main/java/cloud/eppo/cache/ExpiringInMemoryAssignmentCache.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package cloud.eppo.cache; | ||
|
||
import cloud.eppo.api.AbstractAssignmentCache; | ||
import java.util.Collections; | ||
import java.util.Map; | ||
import java.util.concurrent.TimeUnit; | ||
import org.apache.commons.collections4.map.PassiveExpiringMap; | ||
|
||
public class ExpiringInMemoryAssignmentCache extends AbstractAssignmentCache { | ||
public ExpiringInMemoryAssignmentCache(int cacheTimeout, TimeUnit timeUnit) { | ||
super(Collections.synchronizedMap(new PassiveExpiringMap<>(cacheTimeout, timeUnit))); | ||
} | ||
|
||
public ExpiringInMemoryAssignmentCache( | ||
Map<String, String> delegate, int cacheTimeout, TimeUnit timeUnit) { | ||
super(Collections.synchronizedMap(new PassiveExpiringMap<>(cacheTimeout, timeUnit, delegate))); | ||
} | ||
} |
Oops, something went wrong.