Skip to content

Added inmemory storage classes #540

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,12 @@ public int hashCode() {
result = 31 * result + _matcher.hashCode();

int partitionsHashCode = 17;
for (Partition p : _partitions) {
partitionsHashCode = 31 * partitionsHashCode + p.treatment.hashCode();
partitionsHashCode = 31 * partitionsHashCode + p.size;
if (_partitions != null) {
for (Partition p : _partitions) {
partitionsHashCode = 31 * partitionsHashCode + p.treatment.hashCode();
partitionsHashCode = 31 * partitionsHashCode + p.size;
}
}

result = 31 * result + partitionsHashCode;
return result;
}
Expand All @@ -75,7 +76,9 @@ public boolean equals(Object obj) {
if (!result) {
return result;
}

if (_partitions == null) {
return result & (_partitions == other._partitions);
}
if (_partitions.size() != other._partitions.size()) {
return result;
}
Expand All @@ -97,6 +100,9 @@ public String toString() {
bldr.append(_matcher);
bldr.append(" then split ");
boolean first = true;
if (_partitions == null) {
return bldr.toString();
}
for (Partition partition : _partitions) {
if (!first) {
bldr.append(',');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package io.split.engine.experiments;

import com.google.common.collect.ImmutableList;
import io.split.engine.matchers.AttributeMatcher;
import io.split.engine.matchers.UserDefinedSegmentMatcher;

import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class ParsedRuleBasedSegment {

private final String _ruleBasedSegment;
private final ImmutableList<ParsedCondition> _parsedCondition;
private final String _trafficTypeName;
private final long _changeNumber;
private final List<String> _excludedKeys;
private final List<String> _excludedSegments;

public static ParsedRuleBasedSegment createParsedRuleBasedSegmentForTests(
String ruleBasedSegment,
List<ParsedCondition> matcherAndSplits,
String trafficTypeName,
long changeNumber,
List<String> excludedKeys,
List<String> excludedSegments
) {
return new ParsedRuleBasedSegment(
ruleBasedSegment,
matcherAndSplits,
trafficTypeName,
changeNumber,
excludedKeys,
excludedSegments
);
}

public ParsedRuleBasedSegment(
String ruleBasedSegment,
List<ParsedCondition> matcherAndSplits,
String trafficTypeName,
long changeNumber,
List<String> excludedKeys,
List<String> excludedSegments
) {
_ruleBasedSegment = ruleBasedSegment;
_parsedCondition = ImmutableList.copyOf(matcherAndSplits);
_trafficTypeName = trafficTypeName;
_changeNumber = changeNumber;
_excludedKeys = excludedKeys;
_excludedSegments = excludedSegments;
}

public String ruleBasedSegment() {
return _ruleBasedSegment;
}

public List<ParsedCondition> parsedConditions() {
return _parsedCondition;
}

public String trafficTypeName() {return _trafficTypeName;}

public long changeNumber() {return _changeNumber;}

public List<String> excludedKeys() {return _excludedKeys;}
public List<String> excludedSegments() {return _excludedSegments;}

@Override
public int hashCode() {
int result = 17;
result = 31 * result + _ruleBasedSegment.hashCode();
result = 31 * result + _parsedCondition.hashCode();
result = 31 * result + (_trafficTypeName == null ? 0 : _trafficTypeName.hashCode());
result = 31 * result + (int)(_changeNumber ^ (_changeNumber >>> 32));
return result;
}

@Override
public boolean equals(Object obj) {
if (obj == null) return false;
if (this == obj) return true;
if (!(obj instanceof ParsedRuleBasedSegment)) return false;

ParsedRuleBasedSegment other = (ParsedRuleBasedSegment) obj;

return _ruleBasedSegment.equals(other._ruleBasedSegment)
&& _parsedCondition.equals(other._parsedCondition)
&& _trafficTypeName == null ? other._trafficTypeName == null : _trafficTypeName.equals(other._trafficTypeName)
&& _changeNumber == other._changeNumber;
}

@Override
public String toString() {
StringBuilder bldr = new StringBuilder();
bldr.append("name:");
bldr.append(_ruleBasedSegment);
bldr.append(", parsedConditions:");
bldr.append(_parsedCondition);
bldr.append(", trafficTypeName:");
bldr.append(_trafficTypeName);
bldr.append(", changeNumber:");
bldr.append(_changeNumber);
return bldr.toString();

}

public Set<String> getSegmentsNames() {
return parsedConditions().stream()
.flatMap(parsedCondition -> parsedCondition.matcher().attributeMatchers().stream())
.filter(ParsedRuleBasedSegment::isSegmentMatcher)
.map(ParsedRuleBasedSegment::asSegmentMatcherForEach)
.map(UserDefinedSegmentMatcher::getSegmentName)
.collect(Collectors.toSet());
}

private static boolean isSegmentMatcher(AttributeMatcher attributeMatcher) {
return ((AttributeMatcher.NegatableMatcher) attributeMatcher.matcher()).delegate() instanceof UserDefinedSegmentMatcher;
}

private static UserDefinedSegmentMatcher asSegmentMatcherForEach(AttributeMatcher attributeMatcher) {
return (UserDefinedSegmentMatcher) ((AttributeMatcher.NegatableMatcher) attributeMatcher.matcher()).delegate();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package io.split.storages;

public interface RuleBasedSegmentCache extends RuleBasedSegmentCacheConsumer, RuleBasedSegmentCacheProducer {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.split.storages;

import io.split.engine.experiments.ParsedRuleBasedSegment;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

public interface RuleBasedSegmentCacheConsumer {
ParsedRuleBasedSegment get(String name);
Collection<ParsedRuleBasedSegment> getAll();
List<String> ruleBasedSegmentNames();
long getChangeNumber();
Set<String> getSegments();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.split.storages;

import io.split.engine.experiments.ParsedRuleBasedSegment;

import java.util.List;

public interface RuleBasedSegmentCacheProducer {
boolean remove(String name);
void setChangeNumber(long changeNumber);
void update(List<ParsedRuleBasedSegment> toAdd, List<String> toRemove, long changeNumber);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package io.split.storages.memory;

import com.google.common.collect.Maps;
import io.split.engine.experiments.ParsedRuleBasedSegment;
import io.split.storages.RuleBasedSegmentCache;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;

public class RuleBasedSegmentCacheInMemoryImp implements RuleBasedSegmentCache {

private static final Logger _log = LoggerFactory.getLogger(RuleBasedSegmentCacheInMemoryImp.class);

private final ConcurrentMap<String, ParsedRuleBasedSegment> _concurrentMap;

private AtomicLong _changeNumber;

public RuleBasedSegmentCacheInMemoryImp() {
this(-1);
}

public RuleBasedSegmentCacheInMemoryImp(long startingChangeNumber) {
_concurrentMap = Maps.newConcurrentMap();
_changeNumber = new AtomicLong(startingChangeNumber);
}

@Override
public boolean remove(String name) {
ParsedRuleBasedSegment removed = _concurrentMap.remove(name);
return removed != null;
}

@Override
public ParsedRuleBasedSegment get(String name) {
return _concurrentMap.get(name);
}

@Override
public Collection<ParsedRuleBasedSegment> getAll() {
return _concurrentMap.values();
}

@Override
public long getChangeNumber() {
return _changeNumber.get();
}

@Override
public void setChangeNumber(long changeNumber) {
if (changeNumber < _changeNumber.get()) {
_log.error("ChangeNumber for feature flags cache is less than previous");
}

_changeNumber.set(changeNumber);
}

@Override
public List<String> ruleBasedSegmentNames() {
List<String> ruleBasedSegmentNamesList = new ArrayList<>();
for (String key: _concurrentMap.keySet()) {
ruleBasedSegmentNamesList.add(_concurrentMap.get(key).ruleBasedSegment());
}
return ruleBasedSegmentNamesList;
}

public void clear() {
_concurrentMap.clear();
}

private void putMany(List<ParsedRuleBasedSegment> ruleBasedSegments) {
for (ParsedRuleBasedSegment ruleBasedSegment : ruleBasedSegments) {
_concurrentMap.put(ruleBasedSegment.ruleBasedSegment(), ruleBasedSegment);
}
}

@Override
public void update(List<ParsedRuleBasedSegment> toAdd, List<String> toRemove, long changeNumber) {
if(toAdd != null) {
putMany(toAdd);
}
if(toRemove != null) {
for(String ruleBasedSegment : toRemove) {
remove(ruleBasedSegment);
}
}
setChangeNumber(changeNumber);
}

public Set<String> getSegments() {
return _concurrentMap.values().stream()
.flatMap(parsedRuleBasedSegment -> parsedRuleBasedSegment.getSegmentsNames().stream()).collect(Collectors.toSet());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.split.engine.experiments;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import io.split.client.dtos.ConditionType;
import io.split.client.dtos.MatcherCombiner;
import io.split.engine.matchers.AttributeMatcher;
import io.split.engine.matchers.CombiningMatcher;
import io.split.engine.matchers.UserDefinedSegmentMatcher;

import org.junit.Assert;
import org.junit.Test;

public class ParsedRuleBasedSegmentTest {

@Test
public void works() {
AttributeMatcher segmentMatcher = AttributeMatcher.vanilla(new UserDefinedSegmentMatcher("employees"));
CombiningMatcher segmentCombiningMatcher = new CombiningMatcher(MatcherCombiner.AND, Lists.newArrayList(segmentMatcher));
ParsedRuleBasedSegment parsedRuleBasedSegment = new ParsedRuleBasedSegment("another_rule_based_segment",
Lists.newArrayList(new ParsedCondition(ConditionType.WHITELIST, segmentCombiningMatcher, null, "label")),"user",
123, Lists.newArrayList("mauro@test.io","gaston@test.io"), Lists.newArrayList("segment1", "segment2"));

Assert.assertEquals(Sets.newHashSet("employees"), parsedRuleBasedSegment.getSegmentsNames());
Assert.assertEquals("another_rule_based_segment", parsedRuleBasedSegment.ruleBasedSegment());
Assert.assertEquals(Lists.newArrayList(new ParsedCondition(ConditionType.WHITELIST, segmentCombiningMatcher, null, "label")),
parsedRuleBasedSegment.parsedConditions());
Assert.assertEquals(123, parsedRuleBasedSegment.changeNumber());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package io.split.storages.memory;

import com.google.common.collect.Sets;
import io.split.client.dtos.MatcherCombiner;
import io.split.engine.experiments.ParsedRuleBasedSegment;
import io.split.engine.experiments.ParsedCondition;
import io.split.client.dtos.ConditionType;

import io.split.engine.matchers.AttributeMatcher;
import io.split.engine.matchers.CombiningMatcher;
import io.split.engine.matchers.UserDefinedSegmentMatcher;
import io.split.engine.matchers.strings.WhitelistMatcher;
import junit.framework.TestCase;
import org.junit.Test;
import com.google.common.collect.Lists;

public class RuleBasedSegmentCacheInMemoryImplTest extends TestCase {

@Test
public void testAddAndDeleteSegment(){
RuleBasedSegmentCacheInMemoryImp ruleBasedSegmentCache = new RuleBasedSegmentCacheInMemoryImp();
AttributeMatcher whiteListMatcher = AttributeMatcher.vanilla(new WhitelistMatcher(Lists.newArrayList("test_1", "admin")));
CombiningMatcher whitelistCombiningMatcher = new CombiningMatcher(MatcherCombiner.AND, Lists.newArrayList(whiteListMatcher));
ParsedRuleBasedSegment parsedRuleBasedSegment = new ParsedRuleBasedSegment("sample_rule_based_segment",
Lists.newArrayList(new ParsedCondition(ConditionType.WHITELIST, whitelistCombiningMatcher, null, "label")),"user",
123, Lists.newArrayList("mauro@test.io","gaston@test.io"), Lists.newArrayList());
ruleBasedSegmentCache.update(Lists.newArrayList(parsedRuleBasedSegment), null, 123);
assertEquals(123, ruleBasedSegmentCache.getChangeNumber());
assertEquals(parsedRuleBasedSegment, ruleBasedSegmentCache.get("sample_rule_based_segment"));

ruleBasedSegmentCache.update(null, Lists.newArrayList("sample_rule_based_segment"), 124);
assertEquals(124, ruleBasedSegmentCache.getChangeNumber());
assertEquals(null, ruleBasedSegmentCache.get("sample_rule_based_segment"));
}

@Test
public void testMultipleSegment(){
RuleBasedSegmentCacheInMemoryImp ruleBasedSegmentCache = new RuleBasedSegmentCacheInMemoryImp();
AttributeMatcher whiteListMatcher = AttributeMatcher.vanilla(new WhitelistMatcher(Lists.newArrayList("test_1", "admin")));
CombiningMatcher whitelistCombiningMatcher = new CombiningMatcher(MatcherCombiner.AND, Lists.newArrayList(whiteListMatcher));
ParsedRuleBasedSegment parsedRuleBasedSegment1 = new ParsedRuleBasedSegment("sample_rule_based_segment",
Lists.newArrayList(new ParsedCondition(ConditionType.WHITELIST, whitelistCombiningMatcher, null, "label")),"user",
123, Lists.newArrayList("mauro@test.io","gaston@test.io"), Lists.newArrayList(Lists.newArrayList("segment1", "segment3")));

AttributeMatcher segmentMatcher = AttributeMatcher.vanilla(new UserDefinedSegmentMatcher("employees"));
CombiningMatcher segmentCombiningMatcher = new CombiningMatcher(MatcherCombiner.AND, Lists.newArrayList(segmentMatcher));
ParsedRuleBasedSegment parsedRuleBasedSegment2 = new ParsedRuleBasedSegment("another_rule_based_segment",
Lists.newArrayList(new ParsedCondition(ConditionType.WHITELIST, segmentCombiningMatcher, null, "label")),"user",
123, Lists.newArrayList("mauro@test.io","gaston@test.io"), Lists.newArrayList("segment1", "segment2"));

ruleBasedSegmentCache.update(Lists.newArrayList(parsedRuleBasedSegment1, parsedRuleBasedSegment2), null, 123);
assertEquals(Lists.newArrayList("another_rule_based_segment", "sample_rule_based_segment"), ruleBasedSegmentCache.ruleBasedSegmentNames());
assertEquals(Sets.newHashSet("employees"), ruleBasedSegmentCache.getSegments());
}
}