diff --git a/splitio/proxy/storage/large_segments.go b/splitio/proxy/storage/large_segments.go new file mode 100644 index 00000000..e3c26dc3 --- /dev/null +++ b/splitio/proxy/storage/large_segments.go @@ -0,0 +1,82 @@ +package storage + +import ( + "sort" + "sync" + + "github.com/splitio/go-toolkit/v5/logging" +) + +type LargeSegmentsStorage interface { + Count() int + SegmentsForUser(key string) []string + Update(lsName string, userKeys []string) +} + +// MySegmentsCacheImpl implements the MySegmentsCache interface +type LargeSegmentsStorageImpl struct { + largeSegments map[string][]string + mutex *sync.RWMutex + logger logging.LoggerInterface +} + +// NewMySegmentsCache constructs a new MySegments cache +func NewLargeSegmentsStorage(logger logging.LoggerInterface) *LargeSegmentsStorageImpl { + return &LargeSegmentsStorageImpl{ + largeSegments: make(map[string][]string), + mutex: &sync.RWMutex{}, + logger: logger, + } +} + +func (s *LargeSegmentsStorageImpl) Count() int { + s.mutex.RLock() + defer s.mutex.RUnlock() + return len(s.largeSegments) +} + +func (s *LargeSegmentsStorageImpl) SegmentsForUser(key string) []string { + s.mutex.RLock() + defer s.mutex.RUnlock() + + toReturn := make([]string, 0) + lsNames := s.names() + + for _, name := range lsNames { + if s.exists(name, key) { + toReturn = append(toReturn, name) + } + } + + return toReturn +} + +func (s *LargeSegmentsStorageImpl) Update(lsName string, userKeys []string) { + s.mutex.Lock() + defer s.mutex.Unlock() + + s.largeSegments[lsName] = userKeys +} + +func (s *LargeSegmentsStorageImpl) names() []string { + toReturn := make([]string, 0, len(s.largeSegments)) + for key := range s.largeSegments { + toReturn = append(toReturn, key) + } + + return toReturn +} + +func (s *LargeSegmentsStorageImpl) exists(lsName string, userKey string) bool { + data := s.largeSegments[lsName] + length := len(data) + if length == 0 { + return false + } + + i := sort.Search(length, func(i int) bool { + return data[i] >= userKey + }) + + return i < len(data) && data[i] == userKey +} diff --git a/splitio/proxy/storage/large_segments_test.go b/splitio/proxy/storage/large_segments_test.go new file mode 100644 index 00000000..a29e012a --- /dev/null +++ b/splitio/proxy/storage/large_segments_test.go @@ -0,0 +1,56 @@ +package storage + +import ( + "sort" + "testing" + + "github.com/google/uuid" + "github.com/splitio/go-toolkit/v5/logging" + "github.com/stretchr/testify/assert" +) + +func sortedKeys(count int, shared *string) []string { + keys := make([]string, 0, count) + for i := 0; i < count; i++ { + keys = append(keys, uuid.New().String()) + } + + if shared != nil { + keys = append(keys, *shared) + } + + sort.Strings(keys) + return keys +} + +func TestLatgeSegmentStorage(t *testing.T) { + storage := NewLargeSegmentsStorage(logging.NewLogger(nil)) + + keys1 := sortedKeys(10000, nil) + storage.Update("ls_test_1", keys1) + + sharedKey := &keys1[5000] + keys2 := sortedKeys(20000, sharedKey) + storage.Update("ls_test_2", keys2) + + keys3 := sortedKeys(30000, sharedKey) + storage.Update("ls_test_3", keys3) + + assert.Equal(t, 3, storage.Count()) + + result := storage.SegmentsForUser(*sharedKey) + sort.Strings(result) + assert.Equal(t, []string{"ls_test_1", "ls_test_2", "ls_test_3"}, result) + + result = storage.SegmentsForUser(keys1[100]) + assert.Equal(t, []string{"ls_test_1"}, result) + + result = storage.SegmentsForUser(keys2[100]) + assert.Equal(t, []string{"ls_test_2"}, result) + + result = storage.SegmentsForUser(keys3[100]) + assert.Equal(t, []string{"ls_test_3"}, result) + + result = storage.SegmentsForUser("mauro-test") + assert.Equal(t, []string{}, result) +}