Skip to content

Commit 283a2e9

Browse files
avoid extra json marshal when loading configuration from cache (FF-1564) (#31)
1 parent 9f836c7 commit 283a2e9

File tree

7 files changed

+71
-69
lines changed

7 files changed

+71
-69
lines changed

.github/workflows/memory-profile-compare.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,15 @@ jobs:
5454
name: memprofile-main
5555
path: .
5656

57+
- name: Display structure of downloaded files
58+
run: ls -R
59+
5760
- name: Compare memory profiles
58-
run: make profile-memory-compare BASE_FILE=memprofile.main.out COMPARE_FILE=memprofile.feat.out > memory-profile-comparison.text
61+
run: make profile-memory-compare BASE_FILE=memprofile.main.out FEAT_FILE=memprofile.feat.out > memory-profile-comparison.text
5962

63+
- name: Show memory profile comparison
64+
run: cat memory-profile-comparison.text
65+
6066
- name: Upload comparison result
6167
uses: actions/upload-artifact@v2
6268
with:

eppoclient/client.go

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package eppoclient
33
import (
44
"crypto/md5"
55
"encoding/hex"
6-
"encoding/json"
76
"errors"
87
"fmt"
98
"time"
@@ -103,23 +102,6 @@ func (ec *EppoClient) getAssignment(subjectKey string, flagKey string, subjectAt
103102

104103
assignedVariation := variationShard.Value
105104

106-
// Log assignment
107-
assignmentEvent := AssignmentEvent{
108-
Experiment: flagKey + "-" + rule.AllocationKey,
109-
FeatureFlag: flagKey,
110-
Allocation: rule.AllocationKey,
111-
Variation: assignedVariation,
112-
Subject: subjectKey,
113-
Timestamp: time.Now().String(),
114-
SubjectAttributes: subjectAttributes,
115-
}
116-
117-
_, jsonErr := json.Marshal(assignmentEvent)
118-
119-
if jsonErr != nil {
120-
panic("incorrect json")
121-
}
122-
123105
func() {
124106
// need to catch panics from Logger and continue
125107
defer func() {
@@ -129,6 +111,16 @@ func (ec *EppoClient) getAssignment(subjectKey string, flagKey string, subjectAt
129111
}
130112
}()
131113

114+
// Log assignment
115+
assignmentEvent := AssignmentEvent{
116+
Experiment: flagKey + "-" + rule.AllocationKey,
117+
FeatureFlag: flagKey,
118+
Allocation: rule.AllocationKey,
119+
Variation: assignedVariation,
120+
Subject: subjectKey,
121+
Timestamp: time.Now().String(),
122+
SubjectAttributes: subjectAttributes,
123+
}
132124
ec.logger.LogAssignment(assignmentEvent)
133125
}()
134126

eppoclient/configurationrequestor.go

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ import (
77

88
const RAC_ENDPOINT = "/randomized_assignment/v3/config"
99

10+
type racResponse struct {
11+
Flags map[string]experimentConfiguration `json:"flags"`
12+
}
13+
1014
type iConfigRequestor interface {
1115
GetConfiguration(key string) (experimentConfiguration, error)
1216
FetchAndStoreConfigurations()
@@ -36,24 +40,21 @@ func (ecr *experimentConfigurationRequestor) GetConfiguration(experimentKey stri
3640
}
3741

3842
func (ecr *experimentConfigurationRequestor) FetchAndStoreConfigurations() {
39-
var responseBody map[string]json.RawMessage
40-
41-
configs := dictionary{}
4243
result := ecr.httpClient.get(RAC_ENDPOINT)
44+
var wrapper racResponse
4345

44-
err := json.Unmarshal([]byte(result), &responseBody)
45-
46+
// Unmarshal JSON data directly into the wrapper struct
47+
err := json.Unmarshal([]byte(result), &wrapper)
4648
if err != nil {
47-
fmt.Println("Failed to unmarshal RAC response json", result)
49+
fmt.Println("Failed to unmarshal RAC response JSON", result)
4850
fmt.Println(err)
51+
return
4952
}
5053

51-
err = json.Unmarshal(responseBody["flags"], &configs)
52-
54+
// Now wrapper.Flags contains all configurations mapped by their keys
55+
// Pass this map directly to SetConfigurations
56+
err = ecr.configStore.SetConfigurations(wrapper.Flags)
5357
if err != nil {
54-
fmt.Println("Failed to unmarshal RAC response json in experiments section", result)
55-
fmt.Println(err)
58+
fmt.Println("Failed to set configurations in cache", err)
5659
}
57-
58-
ecr.configStore.SetConfigurations(configs)
5960
}

eppoclient/configurationstore.go

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
package eppoclient
22

33
import (
4-
"encoding/json"
54
"errors"
6-
"log"
7-
8-
lru "github.com/hashicorp/golang-lru"
5+
lru "github.com/hashicorp/golang-lru/v2"
96
)
107

118
type configurationStore struct {
12-
cache *lru.Cache
9+
cache *lru.Cache[string, experimentConfiguration]
1310
}
1411

1512
type Variation struct {
@@ -35,7 +32,7 @@ type experimentConfiguration struct {
3532
func newConfigurationStore(maxEntries int) *configurationStore {
3633
var configStore = &configurationStore{}
3734

38-
lruCache, err := lru.New(maxEntries)
35+
lruCache, err := lru.New[string, experimentConfiguration](maxEntries)
3936
configStore.cache = lruCache
4037

4138
if err != nil {
@@ -46,29 +43,18 @@ func newConfigurationStore(maxEntries int) *configurationStore {
4643
}
4744

4845
func (cs *configurationStore) GetConfiguration(key string) (expConfig experimentConfiguration, err error) {
49-
value, _ := cs.cache.Get(key)
50-
51-
if value == nil {
52-
err = errors.New("not found")
53-
return
54-
}
55-
56-
jsonString, err := json.Marshal(value)
57-
58-
if err != nil {
59-
log.Fatalln("Incorrect json")
60-
}
61-
ec := experimentConfiguration{}
62-
err = json.Unmarshal(jsonString, &ec)
63-
if err != nil {
64-
log.Fatalln("failure to unmarshal json into experiment configuration")
46+
// Attempt to get the value from the cache
47+
expConfig, ok := cs.cache.Get(key)
48+
if !ok {
49+
return expConfig, errors.New("configuration not found in cache")
6550
}
6651

67-
return ec, nil
52+
return expConfig, nil
6853
}
6954

70-
func (cs *configurationStore) SetConfigurations(configs dictionary) {
71-
for key, element := range configs {
72-
cs.cache.Add(key, element)
55+
func (cs *configurationStore) SetConfigurations(configs map[string]experimentConfiguration) error {
56+
for key, config := range configs {
57+
cs.cache.Add(key, config)
7358
}
59+
return nil
7460
}

eppoclient/configurationstore_test.go

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,38 +21,55 @@ const TEST_MAX_SIZE = 10
2121

2222
func Test_GetConfiguration_unknownKey(t *testing.T) {
2323
var store = newConfigurationStore(TEST_MAX_SIZE)
24-
store.SetConfigurations(dictionary{"randomization_algo": testExp})
25-
_, err := store.GetConfiguration("unknown_exp")
24+
err := store.SetConfigurations(map[string]experimentConfiguration{
25+
"randomization_algo": testExp,
26+
})
27+
28+
assert.NoError(t, err)
29+
result, err := store.GetConfiguration("unknown_exp")
2630

2731
assert.Error(t, err)
32+
assert.Equal(t, experimentConfiguration{}, result)
2833
}
2934

3035
func Test_GetConfiguration_knownKey(t *testing.T) {
3136
var store = newConfigurationStore(TEST_MAX_SIZE)
32-
store.SetConfigurations(dictionary{"randomization_algo": testExp})
33-
result, _ := store.GetConfiguration("randomization_algo")
37+
err := store.SetConfigurations(map[string]experimentConfiguration{
38+
"randomization_algo": testExp,
39+
})
40+
assert.NoError(t, err)
41+
result, err := store.GetConfiguration("randomization_algo")
3442

3543
expected := "randomization_algo"
3644

45+
assert.NoError(t, err)
3746
assert.Equal(t, expected, result.Name)
3847
}
3948

4049
func Test_GetConfiguration_evictsOldEntriesWhenMaxSizeExceeded(t *testing.T) {
4150
var store = newConfigurationStore(TEST_MAX_SIZE)
42-
store.SetConfigurations(dictionary{"item_to_be_evicted": testExp})
43-
result, _ := store.GetConfiguration("item_to_be_evicted")
51+
err := store.SetConfigurations(map[string]experimentConfiguration{
52+
"item_to_be_evicted": testExp,
53+
})
54+
assert.NoError(t, err)
55+
result, err := store.GetConfiguration("item_to_be_evicted")
4456

4557
expected := "randomization_algo"
58+
assert.NoError(t, err)
4659
assert.Equal(t, expected, result.Name)
4760

4861
for i := 0; i < TEST_MAX_SIZE; i++ {
4962
dictKey := fmt.Sprintf("test-entry-%v", i)
50-
store.SetConfigurations(dictionary{dictKey: testExp})
63+
err := store.SetConfigurations(map[string]experimentConfiguration{
64+
dictKey: testExp,
65+
})
66+
assert.NoError(t, err)
5167
}
5268

53-
result, err := store.GetConfiguration("item_to_be_evicted")
69+
result, err = store.GetConfiguration("item_to_be_evicted")
5470
assert.Error(t, err)
5571

56-
result, _ = store.GetConfiguration(fmt.Sprintf("test-entry-%v", TEST_MAX_SIZE-1))
72+
result, err = store.GetConfiguration(fmt.Sprintf("test-entry-%v", TEST_MAX_SIZE-1))
73+
assert.NoError(t, err)
5774
assert.Equal(t, expected, result.Name)
5875
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module github.com/Eppo-exp/golang-sdk/v2
33
go 1.19
44

55
require (
6-
github.com/hashicorp/golang-lru v0.5.4
6+
github.com/hashicorp/golang-lru/v2 v2.0.7
77
github.com/stretchr/testify v1.8.0
88
)
99

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
22
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
33
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4-
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
5-
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
4+
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
5+
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
66
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
77
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
88
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=

0 commit comments

Comments
 (0)