Skip to content
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

[SDKS-8921] Proxy Large Segments imp #198

Merged
merged 87 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
1b7b4bd
Bump peter-evans/create-pull-request from 6 to 7
dependabot[bot] Sep 16, 2024
2858ffb
Bump SonarSource/sonarcloud-github-action from 2.3.0 to 3.0.0
dependabot[bot] Sep 16, 2024
c19d316
Adding MembershipsDTO
sanzmauro Oct 3, 2024
098c0ea
polishing
sanzmauro Oct 3, 2024
7bdd98d
Migrate membershipsDto from sdk-api-go
sanzmauro Oct 4, 2024
80e52ec
update changes
sanzmauro Oct 4, 2024
bd2f5a0
Merge pull request #183 from splitio/dependabot/github_actions/peter-…
sanzmauro Oct 4, 2024
e18a797
Merge pull request #184 from splitio/dependabot/github_actions/SonarS…
sanzmauro Oct 4, 2024
209bcc3
adding LargeSegmentDTO
sanzmauro Oct 29, 2024
9f0b9b1
adding conf.MembershipVersion
sanzmauro Oct 29, 2024
af5ee73
adding HTTPLargeSegmentFetcher
sanzmauro Oct 29, 2024
e0b2ac6
update changes.txt
sanzmauro Oct 29, 2024
935a544
updated ls fetcher
sanzmauro Oct 29, 2024
10f5c9a
polishing
sanzmauro Oct 29, 2024
3823002
large segments storage implementation
sanzmauro Oct 29, 2024
eee0249
adding large segment storage in memory implementation
sanzmauro Oct 29, 2024
ec41875
adding interface
sanzmauro Oct 29, 2024
10c9198
wip
sanzmauro Oct 29, 2024
9ccd493
in progress
sanzmauro Oct 29, 2024
b4a8e57
cleanup
sanzmauro Oct 29, 2024
feecaf4
large segments storage implementation
sanzmauro Oct 29, 2024
41de199
fix tests
sanzmauro Nov 5, 2024
a5027dd
fix sonar
sanzmauro Nov 5, 2024
31c1c32
Adding LS task implementation
sanzmauro Nov 6, 2024
a8b1069
cleanup
sanzmauro Nov 6, 2024
b29066e
pr feedback
sanzmauro Nov 7, 2024
de719ef
[SDKS-8965] Large segments in memory storage implementation
sanzmauro Nov 11, 2024
302138f
updating largesegment DTOs
sanzmauro Nov 11, 2024
e5cee28
pr feedback
sanzmauro Nov 12, 2024
d6d3d80
Merge branch 'memberships-dto' into SDKS-8966-ls-updater
sanzmauro Nov 12, 2024
effe1a8
[SDKS-8964] Large Segment Fetcher implementation
sanzmauro Nov 12, 2024
3a65607
pr feedback
sanzmauro Nov 12, 2024
5d295ac
pr feedback
sanzmauro Nov 12, 2024
80536df
Merge branch 'SDKS-8921-large-segments' into SDKS-8966-ls-updater
sanzmauro Nov 12, 2024
24dcb4f
applying feedback
sanzmauro Nov 12, 2024
3d3b77e
merge ls-updater branch
sanzmauro Nov 13, 2024
657d4e0
fixed build
sanzmauro Nov 13, 2024
b47429f
pr feedback
sanzmauro Nov 13, 2024
96ad44d
[SDKS-8966] Add Large Segments updater implementation
sanzmauro Nov 13, 2024
bee316a
[SDKS-8963] Adding LS task implementation
sanzmauro Nov 13, 2024
e9f2301
adding LS configs
sanzmauro Nov 13, 2024
b1abedb
adding LargeSegmentEnabled config
sanzmauro Nov 13, 2024
bba7449
Adding LargeSegmentNames implementation for in memory
sanzmauro Nov 13, 2024
5deffa8
Merge pull request #192 from splitio/lsnames-implementation
sanzmauro Nov 13, 2024
35d4de3
Merge branch 'SDKS-8921-large-segments' into SDKS-8924-ls-configs
sanzmauro Nov 13, 2024
9425fb4
updating synchronizer implementation
sanzmauro Nov 14, 2024
e9e8a8f
fixing test
sanzmauro Nov 14, 2024
9a174c3
adding test
sanzmauro Nov 14, 2024
4748736
removed debug log
sanzmauro Nov 14, 2024
e8964f1
InLargeSegmentMatcher implementation
sanzmauro Nov 15, 2024
eec29dd
applying feedback
sanzmauro Nov 19, 2024
fa4dd18
adding stretchr/testify
sanzmauro Nov 20, 2024
4ac0b90
feedback
sanzmauro Nov 20, 2024
30faa3b
adding streaming support
sanzmauro Nov 21, 2024
a793c63
polishing
sanzmauro Nov 21, 2024
a85c9fd
pr feedback
sanzmauro Nov 21, 2024
0104d3e
pr feedback
sanzmauro Nov 21, 2024
ed4a8ac
removed noop implementation
sanzmauro Nov 21, 2024
ed7a603
removing noop
sanzmauro Nov 21, 2024
86de0e9
adding coverage
sanzmauro Nov 21, 2024
da71308
[SDKS-9032] Updating synchronizer implementation
sanzmauro Nov 21, 2024
07e6724
Merge pull request #194 from splitio/large-segmet-matcher-imp
sanzmauro Nov 21, 2024
4442e27
fixing test
sanzmauro Nov 21, 2024
a23858a
returning change numbers after fetch
sanzmauro Nov 19, 2024
17c976c
fixing tests
sanzmauro Nov 21, 2024
ef21a99
Merge pull request #195 from splitio/update-larsegment-worker
sanzmauro Nov 21, 2024
e9a634e
Merge pull request #196 from splitio/SDKS-8929-lsu-notification
sanzmauro Nov 21, 2024
31f8d17
polishing
sanzmauro Nov 21, 2024
a989272
polishing
sanzmauro Nov 22, 2024
cc8d041
adding coverage
sanzmauro Nov 22, 2024
9593110
polishing
sanzmauro Nov 25, 2024
03ffbb3
polishing
sanzmauro Nov 26, 2024
d5058e8
pr feedback
sanzmauro Nov 27, 2024
f0d0740
polishing
sanzmauro Nov 27, 2024
b560368
removed ls config
sanzmauro Nov 28, 2024
0a70c0b
Merge pull request #197 from splitio/ls-push-polishing
sanzmauro Nov 28, 2024
8c12354
Adding Large Segment healthcheck monitor
sanzmauro Nov 28, 2024
6b3a0c7
pr feedback
sanzmauro Dec 2, 2024
40caa25
updating log
sanzmauro Dec 2, 2024
53c1332
pr feedback
sanzmauro Dec 2, 2024
34b84bb
pr feedback
sanzmauro Dec 2, 2024
0f32e21
removing logs
sanzmauro Dec 2, 2024
50b4547
polishing
sanzmauro Dec 2, 2024
ef1c651
Merge pull request #200 from splitio/refresh-rate-fix
sanzmauro Dec 2, 2024
67ca924
Merge pull request #199 from splitio/SDKS-9085-healthcheck-monitor
sanzmauro Dec 2, 2024
ab9ff2a
init large segment fix
sanzmauro Dec 2, 2024
22c63ff
update date
sanzmauro Dec 2, 2024
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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:

- name: SonarQube Scan (Push)
if: ${{ github.event_name == 'push' }}
uses: SonarSource/sonarcloud-github-action@v2.3.0
uses: SonarSource/sonarcloud-github-action@v3.0.0
env:
SONAR_TOKEN: ${{ secrets.SONARQUBE_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand All @@ -46,7 +46,7 @@ jobs:

- name: SonarQube Scan (Pull Request)
if: ${{ github.event_name == 'pull_request' }}
uses: SonarSource/sonarcloud-github-action@v2.3.0
uses: SonarSource/sonarcloud-github-action@v3.0.0
env:
SONAR_TOKEN: ${{ secrets.SONARQUBE_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/update-license-year.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
git commit -m "Updated License Year" -a

- name: Create Pull Request
uses: peter-evans/create-pull-request@v6
uses: peter-evans/create-pull-request@v7
with:
token: ${{ secrets.GITHUB_TOKEN }}
title: Update License Year
Expand Down
8 changes: 7 additions & 1 deletion CHANGES
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
6.0.2 (Dec 2, 2024)
- Adding HTTPLargeSegmentsFetcher implementation.
- Adding MembershipsResponseDTO.
- Adding LargeSegmentDTO.
- Bump dependencies for vulnerability fixes.

6.0.1 (Sep 12, 2024)
- Updated split version filter to support 1.2.
- Fixed healcheck monitor for cases with no segments.
- Fixed healthcheck monitor for cases with no segments.

6.0.0 (May 14, 2024)
- BREAKING CHANGE:
Expand Down
11 changes: 11 additions & 0 deletions conf/conf.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,15 @@ type AdvancedConfig struct {
FlagSetsFilter []string
AuthSpecVersion string
FlagsSpecVersion string
LargeSegment *LargeSegmentConfig
}

type LargeSegmentConfig struct {
Enable bool
Version string
LazyLoad bool
Workers int
UpdateQueueSize int64
QueueSize int
RefreshRate int
}
42 changes: 27 additions & 15 deletions conf/defaults.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
package conf

const (
defaultHTTPTimeout = 30
defaultSegmentQueueSize = 500
defaultSegmentWorkers = 10
defaultEventsBulkSize = 5000
defaultEventsQueueSize = 10000
defaultImpressionsQueueSize = 10000
defaultImpressionsBulkSize = 5000
defaultStreamingEnabled = true
defaultSplitUpdateQueueSize = 5000
defaultSegmentUpdateQueueSize = 5000
defaultAuthServiceURL = "https://auth.split.io"
defaultEventsURL = "https://events.split.io/api"
defaultSdkURL = "https://sdk.split.io/api"
defaultStreamingServiceURL = "https://streaming.split.io/sse"
defaultTelemetryServiceURL = "https://telemetry.split.io/api/v1"
defaultHTTPTimeout = 30
defaultSegmentQueueSize = 500
defaultSegmentWorkers = 10
defaultEventsBulkSize = 5000
defaultEventsQueueSize = 10000
defaultImpressionsQueueSize = 10000
defaultImpressionsBulkSize = 5000
defaultStreamingEnabled = true
defaultSplitUpdateQueueSize = 5000
defaultSegmentUpdateQueueSize = 5000
defaultLargeSegmentUpdateQueueSize = 5000
defaultLargeSegmentQueueSize = 5000
defaultLargeSegmentWorkers = 5
defaultLargeSegmentLazyLoad = false
defaultLargeSegmentEnabled = false
defaultAuthServiceURL = "https://auth.split.io"
defaultEventsURL = "https://events.split.io/api"
defaultSdkURL = "https://sdk.split.io/api"
defaultStreamingServiceURL = "https://streaming.split.io/sse"
defaultTelemetryServiceURL = "https://telemetry.split.io/api/v1"
)

const (
Expand Down Expand Up @@ -52,5 +57,12 @@ func GetDefaultAdvancedConfig() AdvancedConfig {
SdkURL: defaultSdkURL,
StreamingServiceURL: defaultStreamingServiceURL,
TelemetryServiceURL: defaultTelemetryServiceURL,
LargeSegment: &LargeSegmentConfig{
Enable: defaultLargeSegmentEnabled,
UpdateQueueSize: defaultLargeSegmentUpdateQueueSize,
LazyLoad: defaultLargeSegmentLazyLoad,
Workers: defaultLargeSegmentWorkers,
QueueSize: defaultLargeSegmentQueueSize,
},
}
}
42 changes: 42 additions & 0 deletions dtos/largesegment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package dtos

import "net/http"

// Params
type Params struct {
Method string `json:"m"` // method
URL string `json:"u"` // url
Headers http.Header `json:"h"` // headers
Body []byte `json:"b,omitempty"` // body
}

// Data
type Data struct {
Interval *int64 `json:"i,omitempty"` // interval
Format int `json:"f"` // 0 unknown | 1 csv | otro // format
TotalKeys int64 `json:"k"` // totalKeys
FileSize int64 `json:"s"` // fileSize
ExpiresAt int64 `json:"e"` // expiration time url
}

// RFD struct
type RFD struct {
Data Data `json:"d"`
Params Params `json:"p"`
}

// LargeSegmentRFDResponseDTO
type LargeSegmentRFDResponseDTO struct {
Name string `json:"n"`
NotificationType string `json:"t"`
RFD *RFD `json:"rfd,omitempty"`
SpecVersion string `json:"v"`
ChangeNumber int64 `json:"cn"`
}

// LargeSegment
type LargeSegment struct {
Name string
Keys []string
ChangeNumber int64
}
18 changes: 18 additions & 0 deletions dtos/membership.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package dtos

// Segment struct
type Segment struct {
Name string `json:"n"`
}

// Memberships struct mapping segments data for memberships endpoint
type Memberships struct {
Segments []Segment `json:"k"`
ChangeNumber *int64 `json:"cn,omitempty"`
}

// MembershipsResponseDTO struct mapping memberships data for memberships endpoint
type MembershipsResponseDTO struct {
MySegments Memberships `json:"ms"`
MyLargeSegments Memberships `json:"ls"`
}
32 changes: 28 additions & 4 deletions dtos/notification.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ const (

// Update type constants
const (
UpdateTypeSplitChange = "SPLIT_UPDATE"
UpdateTypeSplitKill = "SPLIT_KILL"
UpdateTypeSegmentChange = "SEGMENT_UPDATE"
UpdateTypeContol = "CONTROL"
UpdateTypeSplitChange = "SPLIT_UPDATE"
UpdateTypeSplitKill = "SPLIT_KILL"
UpdateTypeSegmentChange = "SEGMENT_UPDATE"
UpdateTypeContol = "CONTROL"
UpdateTypeLargeSegmentChange = "LS_DEFINITION_UPDATE"
)

// Control type constants
Expand Down Expand Up @@ -310,13 +311,36 @@ func (u *ControlUpdate) String() string {
u.Channel(), u.controlType, u.Timestamp())
}

type LargeSegmentChangeUpdate struct {
BaseUpdate
LargeSegments []LargeSegmentRFDResponseDTO `json:"ls"`
}

func NewLargeSegmentChangeUpdate(baseUpdate BaseUpdate, largeSegments []LargeSegmentRFDResponseDTO) *LargeSegmentChangeUpdate {
return &LargeSegmentChangeUpdate{
BaseUpdate: baseUpdate,
LargeSegments: largeSegments,
}
}

// UpdateType is always UpdateTypeLargeSegmentChange for Large Segmet Updates
func (u *LargeSegmentChangeUpdate) UpdateType() string { return UpdateTypeLargeSegmentChange }

// String returns the string representation of a segment update notification
func (u *LargeSegmentChangeUpdate) String() string {
return fmt.Sprintf("LargeSegmentChange(channel=%s,changeNumber=%d,count=%d,timestamp=%d)",
u.Channel(), u.ChangeNumber(), len(u.LargeSegments), u.Timestamp())
}

// Compile-type assertions of interface requirements
var _ Event = &AblyError{}
var _ Message = &OccupancyMessage{}
var _ Message = &SplitChangeUpdate{}
var _ Message = &SplitKillUpdate{}
var _ Message = &SegmentChangeUpdate{}
var _ Message = &ControlUpdate{}
var _ Message = &LargeSegmentChangeUpdate{}
var _ Update = &SplitChangeUpdate{}
var _ Update = &SplitKillUpdate{}
var _ Update = &SegmentChangeUpdate{}
var _ Update = &LargeSegmentChangeUpdate{}
38 changes: 38 additions & 0 deletions dtos/notification_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,3 +225,41 @@ func TestControlUpdate(t *testing.T) {
t.Error("Unexpected String")
}
}

func TestLargeSegmentChangeUpdate(t *testing.T) {
ls := []LargeSegmentRFDResponseDTO{
{
Name: "ls1",
NotificationType: UpdateTypeLargeSegmentChange,
SpecVersion: "1.0",
RFD: &RFD{},
ChangeNumber: 123123,
},
}

lsUpdate := NewLargeSegmentChangeUpdate(NewBaseUpdate(NewBaseMessage(123456789, "ls_channel"), 123456), ls)
if lsUpdate.EventType() != SSEEventTypeMessage {
t.Error("Unexpected EventType")
}
if lsUpdate.Timestamp() != 123456789 {
t.Error("Unexpected Timestamp")
}
if lsUpdate.Channel() != "ls_channel" {
t.Error("Unexpected Channel")
}
if lsUpdate.MessageType() != MessageTypeUpdate {
t.Error("Unexpected MessageType")
}
if lsUpdate.ChangeNumber() != 123456 {
t.Error("Unexpected ChangeNumber")
}
if lsUpdate.UpdateType() != UpdateTypeLargeSegmentChange {
t.Error("Unexpected UpdateType")
}
if lsUpdate.String() != "LargeSegmentChange(channel=ls_channel,changeNumber=123456,count=1,timestamp=123456789)" {
t.Error("Unexpected String", lsUpdate.String())
}
if len(lsUpdate.LargeSegments) != 1 {
t.Error("LargeSegments len should be 1. Actual: ", len(lsUpdate.LargeSegments))
}
}
28 changes: 17 additions & 11 deletions dtos/split.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,17 +53,23 @@ type MatcherGroupDTO struct {

// MatcherDTO structure to map a Matcher definition fetched from JSON message.
type MatcherDTO struct {
KeySelector *KeySelectorDTO `json:"keySelector"`
MatcherType string `json:"matcherType"`
Negate bool `json:"negate"`
UserDefinedSegment *UserDefinedSegmentMatcherDataDTO `json:"userDefinedSegmentMatcherData"`
Whitelist *WhitelistMatcherDataDTO `json:"whitelistMatcherData"`
UnaryNumeric *UnaryNumericMatcherDataDTO `json:"unaryNumericMatcherData"`
Between *BetweenMatcherDataDTO `json:"betweenMatcherData"`
BetweenString *BetweenStringMatcherDataDTO `json:"betweenStringMatcherData"`
Dependency *DependencyMatcherDataDTO `json:"dependencyMatcherData"`
Boolean *bool `json:"booleanMatcherData"`
String *string `json:"stringMatcherData"`
KeySelector *KeySelectorDTO `json:"keySelector"`
MatcherType string `json:"matcherType"`
Negate bool `json:"negate"`
UserDefinedSegment *UserDefinedSegmentMatcherDataDTO `json:"userDefinedSegmentMatcherData"`
Whitelist *WhitelistMatcherDataDTO `json:"whitelistMatcherData"`
UnaryNumeric *UnaryNumericMatcherDataDTO `json:"unaryNumericMatcherData"`
Between *BetweenMatcherDataDTO `json:"betweenMatcherData"`
BetweenString *BetweenStringMatcherDataDTO `json:"betweenStringMatcherData"`
Dependency *DependencyMatcherDataDTO `json:"dependencyMatcherData"`
Boolean *bool `json:"booleanMatcherData"`
String *string `json:"stringMatcherData"`
UserDefinedLargeSegment *UserDefinedLargeSegmentMatcherDataDTO `json:"userDefinedLargeSegmentMatcherData"`
}

// UserDefinedLargeSegmentMatcherDataDTO structure to map a Matcher definition fetched from JSON message.
type UserDefinedLargeSegmentMatcherDataDTO struct {
LargeSegmentName string `json:"largeSegmentName"`
}

// UserDefinedSegmentMatcherDataDTO structure to map a Matcher definition fetched from JSON message.
Expand Down
20 changes: 17 additions & 3 deletions dtos/split_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ package dtos
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"testing"
)

var splitsMock, _ = ioutil.ReadFile("../testdata/splits_mock.json")
var splitMock, _ = ioutil.ReadFile("../testdata/split_mock.json")
var splitsMock, _ = os.ReadFile("../testdata/splits_mock.json")
var splitMock, _ = os.ReadFile("../testdata/split_mock.json")

func TestSplitDTO(t *testing.T) {
mockedData := fmt.Sprintf(string(splitsMock), splitMock)
Expand Down Expand Up @@ -63,5 +63,19 @@ func TestSplitDTO(t *testing.T) {
if len(splitChangesDtoFromMarshal.Splits[0].Sets) != 0 {
t.Error("Marshal struct mal formed [Sets]")
}

condition := splitChangesDtoFromMarshal.Splits[0].Conditions[0]
if condition.ConditionType != "WHITELIST" {
t.Error("Marshal struct mal formed [ConditionType]. Actual: ", condition.ConditionType)
}

matcher := condition.MatcherGroup.Matchers[0]
if matcher.MatcherType != "IN_LARGE_SEGMENT" {
t.Error("Marshal struct mal formed [MatcherType]")
}

if matcher.UserDefinedLargeSegment.LargeSegmentName != "mauro_sanz_ls" {
t.Error("Marshal struct mal formed [UserDefinedLargeSegment]")
}
}
}
1 change: 1 addition & 0 deletions engine/evaluator/evaluator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ func (s *mockStorage) FetchMany(
}
func (s *mockStorage) All() []dtos.SplitDTO { return make([]dtos.SplitDTO, 0) }
func (s *mockStorage) SegmentNames() *set.ThreadUnsafeSet { return nil }
func (s *mockStorage) LargeSegmentNames() *set.ThreadUnsafeSet { return nil }
func (s *mockStorage) SplitNames() []string { return make([]string, 0) }
func (s *mockStorage) TrafficTypeExists(trafficType string) bool { return true }
func (s *mockStorage) ChangeNumber() (int64, error) { return 0, nil }
Expand Down
39 changes: 39 additions & 0 deletions engine/grammar/matchers/inlargesegment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package matchers

import (
"fmt"

"github.com/splitio/go-split-commons/v6/storage"
)

// InLargeSegmentMatcher matches if the key passed is in the large segment which the matcher was constructed with
type InLargeSegmentMatcher struct {
Matcher
name string
}

// Match returns true if the key is in the matcher's segment
func (m *InLargeSegmentMatcher) Match(key string, attributes map[string]interface{}, bucketingKey *string) bool {
storage, ok := m.Context.Dependency("largeSegmentStorage").(storage.LargeSegmentStorageConsumer)
if !ok {
m.logger.Error("InLargeSegmentMatcher: Unable to retrieve large segment storage!")
return false
}

isInLargeSegment, err := storage.IsInLargeSegment(m.name, key)
if err != nil {
m.logger.Error(fmt.Printf("InLargeSegmentMatcher: Large Segment %s not found", m.name))
}
return isInLargeSegment
}

// NewInLargeSegmentMatcher instantiates a new InLargeSegmentMatcher
func NewInLargeSegmentMatcher(negate bool, name string, attributeName *string) *InLargeSegmentMatcher {
return &InLargeSegmentMatcher{
Matcher: Matcher{
negate: negate,
attributeName: attributeName,
},
name: name,
}
}
Loading
Loading