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

feat: support HSET in redis #3768

Merged
merged 20 commits into from
Aug 30, 2023
Merged

feat: support HSET in redis #3768

merged 20 commits into from
Aug 30, 2023

Conversation

debanjan97
Copy link
Member

@debanjan97 debanjan97 commented Aug 21, 2023

Description

This PR augments the capability of the redisManager
to support HSET as one of the operations.
More on HSET here.
The customdestinationmanager checks the format of the incoming event
and determines the type of operation required for writing into the key-value store.
If the incoming event is in the format of

{
  "hash": "..." ,
  "key": "...",
  "value": "..."
}

customdestinationmanager decides to use HSET.

More on why this change is required is here

Linear Ticket

https://linear.app/rudderstack/issue/PRO-633/server-add-support-for-hset

Security

  • The code changed/added as part of this pull request won't create any security issues with how the software is being used.

@debanjan97 debanjan97 marked this pull request as ready for review August 22, 2023 07:25
@codecov
Copy link

codecov bot commented Aug 22, 2023

Codecov Report

Patch coverage: 70.83% and project coverage change: +0.12% 🎉

Comparison is base (3dff32d) 68.94% compared to head (9a9cf61) 69.07%.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3768      +/-   ##
==========================================
+ Coverage   68.94%   69.07%   +0.12%     
==========================================
  Files         349      349              
  Lines       51872    51894      +22     
==========================================
+ Hits        35765    35844      +79     
+ Misses      13801    13745      -56     
+ Partials     2306     2305       -1     
Files Changed Coverage Δ
services/kvstoremanager/redis.go 42.57% <0.00%> (+11.72%) ⬆️
...stomdestinationmanager/customdestinationmanager.go 59.25% <100.00%> (+7.56%) ⬆️
services/kvstoremanager/kvstoremanager.go 100.00% <100.00%> (+38.09%) ⬆️

... and 8 files with indirect coverage changes

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

debanjan97 and others added 2 commits August 24, 2023 11:26
Co-authored-by: Akash Chetty <achetty.iitr@gmail.com>
Co-authored-by: Akash Chetty <achetty.iitr@gmail.com>
@achettyiitr
Copy link
Member

achettyiitr commented Aug 24, 2023

[Optional] We can remove the customdestinationmanager_suite_test.go, since there are no ginkgo-based tests present within the package.

Comment on lines 149 to 206
func TestKVManagerHSETInvocation(t *testing.T) {
initCustomerManager()
customManager := New("REDIS", Opts{}).(*CustomManagerT)
someDestination := backendconfig.DestinationT{
ID: "someDestinationID1",
DestinationDefinition: backendconfig.DestinationDefinitionT{
Name: "REDIS",
},
}
err := customManager.onNewDestination(someDestination)
assert.Nil(t, err)

event := json.RawMessage(`{
"message": {
"key": "someKey",
"value" : "someValue",
"hash": "someHash"
}
}
`)
ctrl := gomock.NewController(t)
mockKVStoreManager := mock_kvstoremanager.NewMockKVStoreManager(ctrl)
mockKVStoreManager.EXPECT().HSet("someHash", "someKey", "someValue").Times(1)
mockKVStoreManager.EXPECT().StatusCode(nil).Times(1)
customManager.send(event, mockKVStoreManager, someDestination.Config)
}

func TestKVManagerHMSETInvocation(t *testing.T) {
initCustomerManager()
customManager := New("REDIS", Opts{}).(*CustomManagerT)
someDestination := backendconfig.DestinationT{
ID: "someDestinationID1",
DestinationDefinition: backendconfig.DestinationDefinitionT{
Name: "REDIS",
},
}
err := customManager.onNewDestination(someDestination)
assert.Nil(t, err)

event := json.RawMessage(`{
"message": {
"key": "someKey",
"fields" : {
"field1": "value1",
"field2": "value2"
}
}
}
`)
ctrl := gomock.NewController(t)
mockKVStoreManager := mock_kvstoremanager.NewMockKVStoreManager(ctrl)
mockKVStoreManager.EXPECT().HMSet("someKey", map[string]interface{}{
"field1": "value1",
"field2": "value2",
}).Times(1)
mockKVStoreManager.EXPECT().StatusCode(nil).Times(1)
customManager.send(event, mockKVStoreManager, someDestination.Config)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Optional] Maybe we can remove duplicate code and compact these tests?

Suggested change
func TestKVManagerHSETInvocation(t *testing.T) {
initCustomerManager()
customManager := New("REDIS", Opts{}).(*CustomManagerT)
someDestination := backendconfig.DestinationT{
ID: "someDestinationID1",
DestinationDefinition: backendconfig.DestinationDefinitionT{
Name: "REDIS",
},
}
err := customManager.onNewDestination(someDestination)
assert.Nil(t, err)
event := json.RawMessage(`{
"message": {
"key": "someKey",
"value" : "someValue",
"hash": "someHash"
}
}
`)
ctrl := gomock.NewController(t)
mockKVStoreManager := mock_kvstoremanager.NewMockKVStoreManager(ctrl)
mockKVStoreManager.EXPECT().HSet("someHash", "someKey", "someValue").Times(1)
mockKVStoreManager.EXPECT().StatusCode(nil).Times(1)
customManager.send(event, mockKVStoreManager, someDestination.Config)
}
func TestKVManagerHMSETInvocation(t *testing.T) {
initCustomerManager()
customManager := New("REDIS", Opts{}).(*CustomManagerT)
someDestination := backendconfig.DestinationT{
ID: "someDestinationID1",
DestinationDefinition: backendconfig.DestinationDefinitionT{
Name: "REDIS",
},
}
err := customManager.onNewDestination(someDestination)
assert.Nil(t, err)
event := json.RawMessage(`{
"message": {
"key": "someKey",
"fields" : {
"field1": "value1",
"field2": "value2"
}
}
}
`)
ctrl := gomock.NewController(t)
mockKVStoreManager := mock_kvstoremanager.NewMockKVStoreManager(ctrl)
mockKVStoreManager.EXPECT().HMSet("someKey", map[string]interface{}{
"field1": "value1",
"field2": "value2",
}).Times(1)
mockKVStoreManager.EXPECT().StatusCode(nil).Times(1)
customManager.send(event, mockKVStoreManager, someDestination.Config)
}
func TestKVManagerInvocation(t *testing.T) {
initCustomerManager()
customManager := New("REDIS", Opts{}).(*CustomManagerT)
someDestination := backendconfig.DestinationT{
ID: "someDestinationID1",
DestinationDefinition: backendconfig.DestinationDefinitionT{
Name: "REDIS",
},
}
err := customManager.onNewDestination(someDestination)
assert.Nil(t, err)
ctrl := gomock.NewController(t)
defer ctrl.Finish()
t.Run("HSET", func(t *testing.T) {
event := json.RawMessage(`{
"message": {
"key": "someKey",
"value" : "someValue",
"hash": "someHash"
}
}
`)
mockKVStoreManager := mock_kvstoremanager.NewMockKVStoreManager(ctrl)
mockKVStoreManager.EXPECT().HSet("someHash", "someKey", "someValue").Times(1)
mockKVStoreManager.EXPECT().StatusCode(nil).Times(1)
customManager.send(event, mockKVStoreManager, someDestination.Config)
})
t.Run("HMSET", func(t *testing.T) {
event := json.RawMessage(`{
"message": {
"key": "someKey",
"fields" : {
"field1": "value1",
"field2": "value2"
}
}
}
`)
mockKVStoreManager := mock_kvstoremanager.NewMockKVStoreManager(ctrl)
mockKVStoreManager.EXPECT().HMSet("someKey", map[string]interface{}{
"field1": "value1",
"field2": "value2",
}).Times(1)
mockKVStoreManager.EXPECT().StatusCode(nil).Times(1)
customManager.send(event, mockKVStoreManager, someDestination.Config)
})
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed

// - message.hash
// It doesn't account for the value of the fields.
func IsHSETCompatibleEvent(jsonData json.RawMessage) bool {
return gjson.GetBytes(jsonData, hashPath).Exists() && gjson.GetBytes(jsonData, keyPath).Exists() && gjson.GetBytes(jsonData, valuePath).Exists()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would avoid the extensive and repeated usage of gjson.

Using a go struct

type HSetEvent struct {
   Hash string `json:"<hash_path>"`
   Key string `json:"<key_path>"`
   Value string `json:"<value_path>"`
}

It would be safer and faster (assuming fastjson library is used)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if I understand faster (assuming fastjson library is used) completely.

I can use fastjson's Exists but that doesn't require a go struct to unmarshal to.
Unless we somehow use fastjson while unmarshalling, using CustomUnmarshal, or I might be completely missing the point here.

@debanjan97 debanjan97 requested a review from lvrach August 29, 2023 05:36
Copy link

@a-rampalli a-rampalli left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@debanjan97 debanjan97 merged commit fd2b341 into master Aug 30, 2023
35 checks passed
@debanjan97 debanjan97 deleted the PRO-633 branch August 30, 2023 12:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants