From 4e310d32207459441541197e99dca60a781658aa Mon Sep 17 00:00:00 2001 From: Mauro Antonio Sanz Date: Tue, 22 Oct 2024 16:48:13 -0300 Subject: [PATCH 1/4] Adding storage and tests --- splitio/proxy/storage/large_segments.go | 82 ++++++++++++++++++++ splitio/proxy/storage/large_segments_test.go | 56 +++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 splitio/proxy/storage/large_segments.go create mode 100644 splitio/proxy/storage/large_segments_test.go 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) +} From db2fe6fe016abd04f1541492ab12d2dfb10b0d5f Mon Sep 17 00:00:00 2001 From: Mauro Antonio Sanz Date: Tue, 22 Oct 2024 17:06:05 -0300 Subject: [PATCH 2/4] polishing --- splitio/proxy/storage/large_segments.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/splitio/proxy/storage/large_segments.go b/splitio/proxy/storage/large_segments.go index e3c26dc3..81556c12 100644 --- a/splitio/proxy/storage/large_segments.go +++ b/splitio/proxy/storage/large_segments.go @@ -68,13 +68,12 @@ func (s *LargeSegmentsStorageImpl) names() []string { } func (s *LargeSegmentsStorageImpl) exists(lsName string, userKey string) bool { - data := s.largeSegments[lsName] - length := len(data) - if length == 0 { + data, ok := s.largeSegments[lsName] + if !ok { return false } - i := sort.Search(length, func(i int) bool { + i := sort.Search(len(data), func(i int) bool { return data[i] >= userKey }) From cc49e5b5640fa8a6833f70ba68c0a0742b15d45b Mon Sep 17 00:00:00 2001 From: Mauro Antonio Sanz Date: Wed, 23 Oct 2024 10:27:58 -0300 Subject: [PATCH 3/4] adding comments --- splitio/proxy/storage/large_segments.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/splitio/proxy/storage/large_segments.go b/splitio/proxy/storage/large_segments.go index 81556c12..4b647efd 100644 --- a/splitio/proxy/storage/large_segments.go +++ b/splitio/proxy/storage/large_segments.go @@ -7,20 +7,21 @@ import ( "github.com/splitio/go-toolkit/v5/logging" ) +// LargeSegmentsStorage defines the interface for a per-user large segments storage type LargeSegmentsStorage interface { Count() int SegmentsForUser(key string) []string Update(lsName string, userKeys []string) } -// MySegmentsCacheImpl implements the MySegmentsCache interface +// LargeSegmentsStorageImpl implements the LargeSegmentsStorage interface type LargeSegmentsStorageImpl struct { largeSegments map[string][]string mutex *sync.RWMutex logger logging.LoggerInterface } -// NewMySegmentsCache constructs a new MySegments cache +// NewLargeSegmentsStorage constructs a new LargeSegments cache func NewLargeSegmentsStorage(logger logging.LoggerInterface) *LargeSegmentsStorageImpl { return &LargeSegmentsStorageImpl{ largeSegments: make(map[string][]string), @@ -29,12 +30,14 @@ func NewLargeSegmentsStorage(logger logging.LoggerInterface) *LargeSegmentsStora } } +// Count retuns the amount of Large Segments func (s *LargeSegmentsStorageImpl) Count() int { s.mutex.RLock() defer s.mutex.RUnlock() return len(s.largeSegments) } +// SegmentsForUser returns the list of segments a certain user belongs to func (s *LargeSegmentsStorageImpl) SegmentsForUser(key string) []string { s.mutex.RLock() defer s.mutex.RUnlock() @@ -51,6 +54,7 @@ func (s *LargeSegmentsStorageImpl) SegmentsForUser(key string) []string { return toReturn } +// Update adds and remove keys to segments func (s *LargeSegmentsStorageImpl) Update(lsName string, userKeys []string) { s.mutex.Lock() defer s.mutex.Unlock() @@ -58,6 +62,7 @@ func (s *LargeSegmentsStorageImpl) Update(lsName string, userKeys []string) { s.largeSegments[lsName] = userKeys } +// names returns the list with Large Segment Names func (s *LargeSegmentsStorageImpl) names() []string { toReturn := make([]string, 0, len(s.largeSegments)) for key := range s.largeSegments { @@ -67,6 +72,7 @@ func (s *LargeSegmentsStorageImpl) names() []string { return toReturn } +// exists returns true if a userKey is part of a large segment, else returns false func (s *LargeSegmentsStorageImpl) exists(lsName string, userKey string) bool { data, ok := s.largeSegments[lsName] if !ok { From 38ca0faebbbdd39915748872e05b0fafc1293b5b Mon Sep 17 00:00:00 2001 From: Mauro Antonio Sanz Date: Fri, 25 Oct 2024 10:12:49 -0300 Subject: [PATCH 4/4] applying feedback --- splitio/proxy/storage/large_segments.go | 40 +++++--------------- splitio/proxy/storage/large_segments_test.go | 10 ++--- 2 files changed, 15 insertions(+), 35 deletions(-) diff --git a/splitio/proxy/storage/large_segments.go b/splitio/proxy/storage/large_segments.go index 4b647efd..974da9e0 100644 --- a/splitio/proxy/storage/large_segments.go +++ b/splitio/proxy/storage/large_segments.go @@ -10,7 +10,7 @@ import ( // LargeSegmentsStorage defines the interface for a per-user large segments storage type LargeSegmentsStorage interface { Count() int - SegmentsForUser(key string) []string + LargeSegmentsForUser(userKey string) []string Update(lsName string, userKeys []string) } @@ -38,16 +38,18 @@ func (s *LargeSegmentsStorageImpl) Count() int { } // SegmentsForUser returns the list of segments a certain user belongs to -func (s *LargeSegmentsStorageImpl) SegmentsForUser(key string) []string { +func (s *LargeSegmentsStorageImpl) LargeSegmentsForUser(userKey string) []string { s.mutex.RLock() defer s.mutex.RUnlock() - toReturn := make([]string, 0) - lsNames := s.names() + toReturn := make([]string, 0, len(s.largeSegments)) + for lsName, data := range s.largeSegments { + i := sort.Search(len(data), func(i int) bool { + return data[i] >= userKey + }) - for _, name := range lsNames { - if s.exists(name, key) { - toReturn = append(toReturn, name) + if i < len(data) && data[i] == userKey { + toReturn = append(toReturn, lsName) } } @@ -62,26 +64,4 @@ func (s *LargeSegmentsStorageImpl) Update(lsName string, userKeys []string) { s.largeSegments[lsName] = userKeys } -// names returns the list with Large Segment Names -func (s *LargeSegmentsStorageImpl) names() []string { - toReturn := make([]string, 0, len(s.largeSegments)) - for key := range s.largeSegments { - toReturn = append(toReturn, key) - } - - return toReturn -} - -// exists returns true if a userKey is part of a large segment, else returns false -func (s *LargeSegmentsStorageImpl) exists(lsName string, userKey string) bool { - data, ok := s.largeSegments[lsName] - if !ok { - return false - } - - i := sort.Search(len(data), func(i int) bool { - return data[i] >= userKey - }) - - return i < len(data) && data[i] == userKey -} +var _ LargeSegmentsStorage = (*LargeSegmentsStorageImpl)(nil) diff --git a/splitio/proxy/storage/large_segments_test.go b/splitio/proxy/storage/large_segments_test.go index a29e012a..44546ccd 100644 --- a/splitio/proxy/storage/large_segments_test.go +++ b/splitio/proxy/storage/large_segments_test.go @@ -38,19 +38,19 @@ func TestLatgeSegmentStorage(t *testing.T) { assert.Equal(t, 3, storage.Count()) - result := storage.SegmentsForUser(*sharedKey) + result := storage.LargeSegmentsForUser(*sharedKey) sort.Strings(result) assert.Equal(t, []string{"ls_test_1", "ls_test_2", "ls_test_3"}, result) - result = storage.SegmentsForUser(keys1[100]) + result = storage.LargeSegmentsForUser(keys1[100]) assert.Equal(t, []string{"ls_test_1"}, result) - result = storage.SegmentsForUser(keys2[100]) + result = storage.LargeSegmentsForUser(keys2[100]) assert.Equal(t, []string{"ls_test_2"}, result) - result = storage.SegmentsForUser(keys3[100]) + result = storage.LargeSegmentsForUser(keys3[100]) assert.Equal(t, []string{"ls_test_3"}, result) - result = storage.SegmentsForUser("mauro-test") + result = storage.LargeSegmentsForUser("mauro-test") assert.Equal(t, []string{}, result) }