Skip to content

Commit 5ab18a6

Browse files
authored
fix: Fix Go feature server entitykey serialization for version 3 (#5622)
* fix: Fix the Makefile for Golang unit test. Signed-off-by: Shuchu Han <[email protected]> * fix: Update the serialization of EntityKey to version 3 in Go Feature server. Signed-off-by: Shuchu Han <[email protected]> * fix: Add serialization verstion number in the feature_repo.yaml for testing. Signed-off-by: Shuchu Han <[email protected]> * fix: Fix a typo in one comment. Signed-off-by: Shuchu Han <[email protected]> * fix: Format the code. Signed-off-by: Shuchu Han <[email protected]> --------- Signed-off-by: Shuchu Han <[email protected]>
1 parent e8eae71 commit 5ab18a6

File tree

7 files changed

+119
-45
lines changed

7 files changed

+119
-45
lines changed

Makefile

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -713,20 +713,25 @@ compile-protos-go: install-go-proto-dependencies ## Compile Go protobuf files
713713
--go-grpc_out=$(ROOT_DIR)/go/protos \
714714
--go-grpc_opt=module=github.com/feast-dev/feast/go/protos $(ROOT_DIR)/protos/feast/$(folder)/*.proto; ) true
715715

716-
#install-go-ci-dependencies:
717-
# go install golang.org/x/tools/cmd/goimports
718-
# python -m pip install "pybindgen==0.22.1" "grpcio-tools>=1.56.2,<2" "mypy-protobuf>=3.1"
716+
install-go-ci-dependencies:
717+
go install golang.org/x/tools/cmd/goimports
718+
uv pip install "pybindgen==0.22.1" "grpcio-tools>=1.56.2,<2" "mypy-protobuf>=3.1"
719719

720720
.PHONY: build-go
721721
build-go: compile-protos-go ## Build Go code
722722
go build -o feast ./go/main.go
723723

724-
.PHONY: install-feast-ci-locally
725-
install-feast-ci-locally: ## Install Feast CI dependencies locally
726-
uv pip install -e ".[ci]"
724+
725+
## Assume the uv will create an .venv folder for itself.
726+
## The unit test funcions will call the Python "feast" command to initialze a feast repo.
727+
.PHONY: install-feast-locally
728+
install-feast-locally: ## Install Feast locally
729+
uv pip install -e "."
730+
@export PATH=$(ROOT_DIR)/.venv/bin:$$PATH
731+
@echo $$PATH
727732

728733
.PHONY: test-go
729-
test-go: compile-protos-go install-feast-ci-locally compile-protos-python ## Run Go tests
734+
test-go: compile-protos-python compile-protos-go install-go-ci-dependencies install-feast-locally ## Run Go tests
730735
CGO_ENABLED=1 go test -coverprofile=coverage.out ./... && go tool cover -html=coverage.out -o coverage.html
731736

732737
.PHONY: format-go

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
module github.com/feast-dev/feast
22

3-
go 1.22.0
3+
go 1.23
44

5-
toolchain go1.22.5
5+
toolchain go1.23.12
66

77
require (
88
github.com/apache/arrow/go/v17 v17.0.0

go/internal/feast/onlinestore/dynamodbonlinestore.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ func (d *DynamodbOnlineStore) OnlineRead(ctx context.Context, entityKeys []*type
278278

279279
// process null imputation for entity ids that don't exist in dynamodb
280280
currentTime := timestamppb.Now() // TODO: should use a different timestamp?
281-
for entityId, _ := range unprocessedEntityIdsFeatureView {
281+
for entityId := range unprocessedEntityIdsFeatureView {
282282
entityIndex := entityIndexMap[entityId]
283283
for _, featureName := range featureNames {
284284
featureIndex := featureNamesIndex[featureName]

go/internal/feast/onlinestore/onlinestore.go

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -87,41 +87,63 @@ func serializeEntityKey(entityKey *types.EntityKey, entityKeySerializationVersio
8787
}
8888

8989
keys := make([]string, 0, len(m))
90-
for k := range entityKey.JoinKeys {
91-
keys = append(keys, entityKey.JoinKeys[k])
92-
}
93-
sort.Strings(keys)
90+
keys = append(keys, entityKey.JoinKeys...)
91+
sort.Strings(keys) // Sort the keys
9492

9593
// Build the key
96-
length := 5 * len(keys)
94+
length := 7 * len(keys)
9795
bufferList := make([][]byte, length)
96+
offset := 0
97+
98+
// For entityKeySerializationVersion 3 and above, we add the number of join keys
99+
// as the first 4 bytes of the serialized key.
100+
if entityKeySerializationVersion >= 3 {
101+
byteBuffer := make([]byte, 4)
102+
binary.LittleEndian.PutUint32(byteBuffer, uint32(len(keys)))
103+
bufferList[offset] = byteBuffer // First buffer is always the length of the keys
104+
offset++
105+
}
98106

99107
for i := 0; i < len(keys); i++ {
100-
offset := i * 2
108+
// Add the key type STRING info
101109
byteBuffer := make([]byte, 4)
102110
binary.LittleEndian.PutUint32(byteBuffer, uint32(types.ValueType_Enum_value["STRING"]))
103111
bufferList[offset] = byteBuffer
104-
bufferList[offset+1] = []byte(keys[i])
112+
offset++
113+
114+
// Add the size of current "key" string
115+
keyLenByteBuffer := make([]byte, 4)
116+
binary.LittleEndian.PutUint32(keyLenByteBuffer, uint32(len(keys[i])))
117+
bufferList[offset] = keyLenByteBuffer
118+
offset++
119+
120+
// Add value
121+
bufferList[offset] = []byte(keys[i])
122+
offset++
105123
}
106124

107125
for i := 0; i < len(keys); i++ {
108-
offset := (2 * len(keys)) + (i * 3)
109126
value := m[keys[i]].GetVal()
110127

111128
valueBytes, valueTypeBytes, err := serializeValue(value, entityKeySerializationVersion)
112129
if err != nil {
113130
return valueBytes, err
114131
}
115132

133+
// Add value type info
116134
typeBuffer := make([]byte, 4)
117135
binary.LittleEndian.PutUint32(typeBuffer, uint32(valueTypeBytes))
136+
bufferList[offset] = typeBuffer
137+
offset++
118138

139+
// Add length info
119140
lenBuffer := make([]byte, 4)
120141
binary.LittleEndian.PutUint32(lenBuffer, uint32(len(*valueBytes)))
142+
bufferList[offset] = lenBuffer
143+
offset++
121144

122-
bufferList[offset+0] = typeBuffer
123-
bufferList[offset+1] = lenBuffer
124-
bufferList[offset+2] = *valueBytes
145+
bufferList[offset] = *valueBytes
146+
offset++
125147
}
126148

127149
// Convert from an array of byte arrays to a single byte array
@@ -132,3 +154,26 @@ func serializeEntityKey(entityKey *types.EntityKey, entityKeySerializationVersio
132154

133155
return &entityKeyBuffer, nil
134156
}
157+
158+
func serializeValue(value interface{}, entityKeySerializationVersion int64) (*[]byte, types.ValueType_Enum, error) {
159+
// TODO: Implement support for other types (at least the major types like ints, strings, bytes)
160+
switch x := (value).(type) {
161+
case *types.Value_StringVal:
162+
valueString := []byte(x.StringVal)
163+
return &valueString, types.ValueType_STRING, nil
164+
case *types.Value_BytesVal:
165+
return &x.BytesVal, types.ValueType_BYTES, nil
166+
case *types.Value_Int32Val:
167+
valueBuffer := make([]byte, 4)
168+
binary.LittleEndian.PutUint32(valueBuffer, uint32(x.Int32Val))
169+
return &valueBuffer, types.ValueType_INT32, nil
170+
case *types.Value_Int64Val:
171+
valueBuffer := make([]byte, 8)
172+
binary.LittleEndian.PutUint64(valueBuffer, uint64(x.Int64Val))
173+
return &valueBuffer, types.ValueType_INT64, nil
174+
case nil:
175+
return nil, types.ValueType_INVALID, fmt.Errorf("could not detect type for %v", x)
176+
default:
177+
return nil, types.ValueType_INVALID, fmt.Errorf("could not detect type for %v", x)
178+
}
179+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package onlinestore
2+
3+
import (
4+
"github.com/feast-dev/feast/go/protos/feast/types"
5+
"github.com/stretchr/testify/assert"
6+
"reflect"
7+
"testing"
8+
)
9+
10+
func Test_serializeEntityKey(t *testing.T) {
11+
expect_res := []byte{1, 0, 0, 0, 2, 0, 0, 0, 9, 0, 0, 0, 100, 114, 105, 118, 101, 114, 95, 105, 100, 4, 0, 0, 0, 8, 0, 0, 0, 233, 3, 0, 0, 0, 0, 0, 0}
12+
tests := []struct {
13+
name string // description of this test case
14+
// Named input parameters for target function.
15+
entityKey *types.EntityKey
16+
entityKeySerializationVersion int64
17+
want []byte
18+
wantErr bool
19+
}{
20+
{
21+
"test a specific key",
22+
&types.EntityKey{
23+
JoinKeys: []string{"driver_id"},
24+
EntityValues: []*types.Value{{Val: &types.Value_Int64Val{Int64Val: 1001}}},
25+
},
26+
3,
27+
expect_res,
28+
false,
29+
},
30+
}
31+
for _, tt := range tests {
32+
t.Run(tt.name, func(t *testing.T) {
33+
got, gotErr := serializeEntityKey(tt.entityKey, tt.entityKeySerializationVersion)
34+
if gotErr != nil {
35+
if !tt.wantErr {
36+
t.Errorf("serializeEntityKey() failed: %v", gotErr)
37+
}
38+
return
39+
}
40+
if tt.wantErr {
41+
t.Fatal("serializeEntityKey() succeeded unexpectedly")
42+
}
43+
assert.True(t, reflect.DeepEqual(*got, tt.want))
44+
})
45+
}
46+
}

go/internal/feast/onlinestore/redisonlinestore.go

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -338,26 +338,3 @@ func buildRedisKey(project string, entityKey *types.EntityKey, entityKeySerializ
338338
fullKey := append(*serKey, []byte(project)...)
339339
return &fullKey, nil
340340
}
341-
342-
func serializeValue(value interface{}, entityKeySerializationVersion int64) (*[]byte, types.ValueType_Enum, error) {
343-
// TODO: Implement support for other types (at least the major types like ints, strings, bytes)
344-
switch x := (value).(type) {
345-
case *types.Value_StringVal:
346-
valueString := []byte(x.StringVal)
347-
return &valueString, types.ValueType_STRING, nil
348-
case *types.Value_BytesVal:
349-
return &x.BytesVal, types.ValueType_BYTES, nil
350-
case *types.Value_Int32Val:
351-
valueBuffer := make([]byte, 4)
352-
binary.LittleEndian.PutUint32(valueBuffer, uint32(x.Int32Val))
353-
return &valueBuffer, types.ValueType_INT32, nil
354-
case *types.Value_Int64Val:
355-
valueBuffer := make([]byte, 8)
356-
binary.LittleEndian.PutUint64(valueBuffer, uint64(x.Int64Val))
357-
return &valueBuffer, types.ValueType_INT64, nil
358-
case nil:
359-
return nil, types.ValueType_INVALID, fmt.Errorf("could not detect type for %v", x)
360-
default:
361-
return nil, types.ValueType_INVALID, fmt.Errorf("could not detect type for %v", x)
362-
}
363-
}

go/internal/test/feature_repo/feature_store.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ project: feature_repo
22
registry: data/registry.db
33
provider: local
44
online_store:
5-
path: data/online_store.db
5+
path: data/online_store.db
6+
entity_key_serialization_version: 3

0 commit comments

Comments
 (0)