Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
1901484
fix: Update Docker images to use official sources for integration tes…
EXPEbdodla Jun 23, 2025
1f1644f
fix: Only add sort key filter models to list if they meet the conditi…
piket Jun 23, 2025
42ef2f0
fix: Adding Snake Case to Include Metadata Check (#264)
Manisha4 Jun 26, 2025
f05b168
fix: Move use_write_time_for_ttl config to sfv instead of an online s…
piket Jun 26, 2025
c6d91d4
fix: Pack and unpack repeated list values into and out of arrow array…
piket Jun 26, 2025
f0d762c
fix: fix misplaced reset indexes (#266)
kpulipati29 Jun 26, 2025
d7d0086
feat: Adding Changes to Check if FV and SFV have Valid Updates (#254)
Manisha4 Jun 26, 2025
f4de1fa
fix: Validate requested features exist in view. (#269)
piket Jun 27, 2025
0430456
fix: Clean Up Error Messages (#274)
Manisha4 Jun 27, 2025
7cbcef8
Merge branch 'master' into feature/range-query-improvements
piket Jun 27, 2025
46d477e
feat: Adding Alter Table to Support Schema Updates for Cassandra (#262)
Manisha4 Jun 30, 2025
da6f72c
feat: Separate entities from features in Range Query response (#265)
omirandadev Jun 30, 2025
ec40037
fix: Keep duplicate requested features in response for range query. (…
piket Jun 30, 2025
85e754e
fix: Http range timestamp values should return in rfc3339 format (#277)
piket Jul 3, 2025
93f5b68
fix: Exception Handling in Updates Feature (#278)
Manisha4 Jul 3, 2025
048cd81
fix: Timestamps should be seconds percision. Return null status for f…
piket Jul 7, 2025
9409973
fix: Go server errors should be status errors with proper status code…
piket Jul 10, 2025
57e88a5
fix: Validation order for multiple missing end keys should pass (#283)
piket Jul 14, 2025
e196c6a
fix: Handle null values in Arrow list conversion to Proto values (#280)
EXPEbdodla Jul 14, 2025
0e5e704
fix: When given a string for a bytes entity/sort key filter, attempt …
piket Jul 14, 2025
e139a7a
fix: Improve Arrow to proto conversion to handle null and empty array…
EXPEbdodla Jul 15, 2025
0118407
feat: Add versioning information to the server and expose it via a ne…
EXPEbdodla Jul 18, 2025
5e7060c
fix: Add http int tests and fix http error codes (#287)
piket Jul 18, 2025
573aef1
fix: Update Feature View Not Catching SortedFeatureView/FeatureView E…
Manisha4 Aug 6, 2025
cf522e1
feat: Only call relevant registry endpoint when getting feature views…
omirandadev Aug 7, 2025
8d05d8b
feat: Streaming ingestion latency improvements (#292)
kpulipati29 Aug 13, 2025
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
6 changes: 3 additions & 3 deletions .github/workflows/unit_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ jobs:
make install-go-ci-dependencies
COMPILE_GO=true python setup.py develop
CGO_LDFLAGS_ALLOW=".*" COMPILE_GO=True python setup.py build_ext --inplace
- name: Test Milvus tests
- name: Test EG tests
if: matrix.os == 'ubuntu-latest'
run: python -m pytest -n 1 --color=yes sdk/python/tests/expediagroup/test_eg_milvus_online_store.py
run: python -m pytest -n 1 --color=yes sdk/python/tests/expediagroup/
- name: Test Python
if: matrix.os == 'ubuntu-latest'
run: make test-python-unit
Expand Down Expand Up @@ -133,4 +133,4 @@ jobs:
- uses: actions/upload-artifact@v4
with:
name: java-coverage-report
path: ${{ github.workspace }}/docs/coverage/java/target/site/jacoco-aggregate/
path: ${{ github.workspace }}/docs/coverage/java/target/site/jacoco-aggregate/
22 changes: 15 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ endif
TRINO_VERSION ?= 376
PYTHON_VERSION = ${shell python --version | grep -Eo '[0-9]\.[0-9]+'}

COMMIT = $(shell git rev-parse HEAD)
BUILD_TIME = $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
FEATURE_SERVER_VERSION = dev-$(shell git rev-parse --abbrev-ref HEAD)-$(shell date -u +%Y%m%d%H%M%S)

# General

format: format-python format-java format-go
Expand Down Expand Up @@ -435,17 +439,21 @@ install-feast-ci-locally:
pip install -e ".[ci]"

build-go: compile-protos-go
go build -o feast ./go/main.go
go build -o feast \
-ldflags "-X github.com/feast-dev/feast/go/internal/feast/version.Version=$(FEATURE_SERVER_VERSION) \
-X github.com/feast-dev/feast/go/internal/feast/version.CommitHash=$(COMMIT) \
-X github.com/feast-dev/feast/go/internal/feast/version.BuildTime=$(BUILD_TIME)" \
./go/main.go

test-go: compile-protos-go compile-protos-python install-feast-ci-locally
CGO_ENABLED=1 go test -tags=unit -coverprofile=coverage.out ./... && go tool cover -html=coverage.out -o coverage.html

test-go-integration: compile-protos-go compile-protos-python install-feast-ci-locally
docker compose -f go/integration_tests/valkey/docker-compose.yaml up -d
docker compose -f go/integration_tests/scylladb/docker-compose.yaml up -d
go test -tags=integration ./go/internal/...
docker compose -f go/integration_tests/valkey/docker-compose.yaml down
docker compose -f go/integration_tests/scylladb/docker-compose.yaml down
docker compose -f go/internal/feast/integration_tests/valkey/docker-compose.yaml up -d
docker compose -f go/internal/feast/integration_tests/scylladb/docker-compose.yaml up -d
go test -p 1 -tags=integration ./go/internal/...
docker compose -f go/internal/feast/integration_tests/valkey/docker-compose.yaml down
docker compose -f go/internal/feast/integration_tests/scylladb/docker-compose.yaml down

format-go:
gofmt -s -w go/
Expand Down Expand Up @@ -563,4 +571,4 @@ build-helm-docs:

# Note: requires node and yarn to be installed
build-ui:
cd $(ROOT_DIR)/sdk/python/feast/ui && yarn upgrade @feast-dev/feast-ui --latest && yarn install && npm run build --omit=dev
cd $(ROOT_DIR)/sdk/python/feast/ui && yarn upgrade @feast-dev/feast-ui --latest && yarn install && npm run build --omit=dev
2 changes: 1 addition & 1 deletion go/embedded/online_features.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ func (s *OnlineFeatureService) GetOnlineFeatures(

tsColumnBuilder := array.NewInt64Builder(pool)
for _, ts := range featureVector.Timestamps {
tsColumnBuilder.Append(types.GetTimestampMillis(ts))
tsColumnBuilder.Append(types.GetTimestampSeconds(ts))
}
tsColumn := tsColumnBuilder.NewArray()
outputColumns = append(outputColumns, tsColumn)
Expand Down
29 changes: 29 additions & 0 deletions go/internal/feast/errors/grpc_error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package errors

import (
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

func GrpcErrorf(code codes.Code, format string, args ...interface{}) error {
return status.Newf(code, format, args...).Err()
}

func GrpcFromError(err error) error {
if s, ok := status.FromError(err); ok {
return s.Err()
}
return status.Error(codes.Internal, err.Error())
}

func GrpcInternalErrorf(format string, args ...interface{}) error {
return GrpcErrorf(codes.Internal, format, args...)
}

func GrpcInvalidArgumentErrorf(format string, args ...interface{}) error {
return GrpcErrorf(codes.InvalidArgument, format, args...)
}

func GrpcNotFoundErrorf(format string, args ...interface{}) error {
return GrpcErrorf(codes.NotFound, format, args...)
}
85 changes: 50 additions & 35 deletions go/internal/feast/featurestore.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package feast

import (
"context"
"errors"
"fmt"
"github.com/feast-dev/feast/go/internal/feast/errors"
"github.com/feast-dev/feast/go/types"
"os"
"strings"
Expand Down Expand Up @@ -114,7 +114,7 @@ func entityTypeConversion(entityMap map[string]*prototypes.RepeatedValue, entity
for _, value := range entityValue.Val {
newVal, err := types.ConvertToValueType(value, entityColumn.Dtype)
if err != nil {
return fmt.Errorf("error converting entity value for %s: %w", entityName, err)
return errors.GrpcInternalErrorf("error converting entity value for %s: %v", entityName, err)
}
newEntityValue.Val = append(newEntityValue.Val, newVal)
}
Expand All @@ -134,7 +134,7 @@ func sortKeyFilterTypeConversion(sortKeyFilters []*serving.SortKeyFilter, sortKe
if filter.GetEquals() != nil {
equals, err := types.ConvertToValueType(filter.GetEquals(), sk.ValueType)
if err != nil {
return nil, fmt.Errorf("error converting sort key filter equals for %s: %w", sk.FieldName, err)
return nil, errors.GrpcInvalidArgumentErrorf("error converting sort key filter equals for %s: %v", sk.FieldName, err)
}
newFilters[i] = &serving.SortKeyFilter{
SortKeyName: sk.FieldName,
Expand All @@ -147,14 +147,14 @@ func sortKeyFilterTypeConversion(sortKeyFilters []*serving.SortKeyFilter, sortKe
if filter.GetRange().GetRangeStart() != nil {
rangeStart, err = types.ConvertToValueType(filter.GetRange().GetRangeStart(), sk.ValueType)
if err != nil {
return nil, fmt.Errorf("error converting sort key filter range start for %s: %w", sk.FieldName, err)
return nil, errors.GrpcInvalidArgumentErrorf("error converting sort key filter range start for %s: %v", sk.FieldName, err)
}
}
var rangeEnd *prototypes.Value
if filter.GetRange().GetRangeEnd() != nil {
rangeEnd, err = types.ConvertToValueType(filter.GetRange().GetRangeEnd(), sk.ValueType)
if err != nil {
return nil, fmt.Errorf("error converting sort key filter range end for %s: %w", sk.FieldName, err)
return nil, errors.GrpcInvalidArgumentErrorf("error converting sort key filter range end for %s: %v", sk.FieldName, err)
}
}
newFilters[i] = &serving.SortKeyFilter{
Expand All @@ -169,7 +169,7 @@ func sortKeyFilterTypeConversion(sortKeyFilters []*serving.SortKeyFilter, sortKe
},
}
} else {
return nil, fmt.Errorf("sort key %s not found in sort keys", filter.SortKeyName)
return nil, errors.GrpcInvalidArgumentErrorf("sort key %s not found in sort keys", filter.SortKeyName)
}
}
return newFilters, nil
Expand All @@ -188,16 +188,22 @@ func (fs *FeatureStore) GetOnlineFeatures(
var requestedFeatureViews []*onlineserving.FeatureViewAndRefs
var requestedOnDemandFeatureViews []*model.OnDemandFeatureView

// TODO: currently ignores SortedFeatureViews, need to either implement get for them or throw some kind of error/warning
if featureService != nil {
requestedFeatureViews, _, requestedOnDemandFeatureViews, err =
requestedFeatureViews, requestedOnDemandFeatureViews, err =
onlineserving.GetFeatureViewsToUseByService(featureService, fs.registry, fs.config.Project)
if err != nil {
return nil, err
}
} else {
requestedFeatureViews, _, requestedOnDemandFeatureViews, err =
requestedFeatureViews, requestedOnDemandFeatureViews, err =
onlineserving.GetFeatureViewsToUseByFeatureRefs(featureRefs, fs.registry, fs.config.Project)
if err != nil {
return nil, err
}
}
if err != nil {
return nil, err

if len(requestedFeatureViews) == 0 {
return nil, errors.GrpcNotFoundErrorf("no feature views found for the requested features")
}

entityColumnMap := make(map[string]*model.Field)
Expand Down Expand Up @@ -251,7 +257,7 @@ func (fs *FeatureStore) GetOnlineFeatures(
for _, groupRef := range groupedRefs {
featureData, err := fs.readFromOnlineStore(ctx, groupRef.EntityKeys, groupRef.FeatureViewNames, groupRef.FeatureNames)
if err != nil {
return nil, err
return nil, errors.GrpcFromError(err)
}

vectors, err := onlineserving.TransposeFeatureRowsIntoColumns(
Expand Down Expand Up @@ -281,7 +287,7 @@ func (fs *FeatureStore) GetOnlineFeatures(
fullFeatureNames,
)
if err != nil {
return nil, err
return nil, errors.GrpcFromError(err)
}
result = append(result, onDemandFeatures...)
}
Expand Down Expand Up @@ -313,19 +319,24 @@ func (fs *FeatureStore) GetOnlineFeaturesRange(

var err error
var requestedSortedFeatureViews []*onlineserving.SortedFeatureViewAndRefs

if featureService != nil {
_, requestedSortedFeatureViews, _, err =
onlineserving.GetFeatureViewsToUseByService(featureService, fs.registry, fs.config.Project)
requestedSortedFeatureViews, err =
onlineserving.GetSortedFeatureViewsToUseByService(featureService, fs.registry, fs.config.Project)
if err != nil {
return nil, err
}

} else {
_, requestedSortedFeatureViews, _, err =
onlineserving.GetFeatureViewsToUseByFeatureRefs(featureRefs, fs.registry, fs.config.Project)
}
if err != nil {
return nil, err
requestedSortedFeatureViews, err = onlineserving.GetSortedFeatureViewsToUseByFeatureRefs(
featureRefs, fs.registry, fs.config.Project)
if err != nil {
return nil, err
}
}

if len(requestedSortedFeatureViews) == 0 {
return nil, fmt.Errorf("no sorted feature views found for the requested features")
return nil, errors.GrpcNotFoundErrorf("no sorted feature views found for the requested features")
}

// Note: We're ignoring on-demand feature views for now.
Expand Down Expand Up @@ -356,7 +367,7 @@ func (fs *FeatureStore) GetOnlineFeaturesRange(
}

if len(expectedJoinKeysSet) == 0 {
return nil, fmt.Errorf("no entity join keys found, check feature view entity definition is well defined")
return nil, errors.GrpcInvalidArgumentErrorf("no entity join keys found, check feature view entity definition is well defined")
}

err = onlineserving.ValidateSortedFeatureRefs(requestedSortedFeatureViews, fullFeatureNames)
Expand All @@ -366,11 +377,11 @@ func (fs *FeatureStore) GetOnlineFeaturesRange(

numRows, err := onlineserving.ValidateEntityValues(joinKeyToEntityValues, requestData, expectedJoinKeysSet)
if err != nil {
return nil, fmt.Errorf("entity validation failed: %w", err)
return nil, errors.GrpcInvalidArgumentErrorf("entity validation failed: %v", err)
}

if numRows <= 0 {
return nil, fmt.Errorf("invalid number of entity rows: %d", numRows)
return nil, errors.GrpcInvalidArgumentErrorf("invalid number of entity rows: %d", numRows)
}

err = onlineserving.ValidateSortKeyFilters(sortKeyFilters, requestedSortedFeatureViews)
Expand All @@ -379,21 +390,15 @@ func (fs *FeatureStore) GetOnlineFeaturesRange(
}

if limit < 0 {
return nil, fmt.Errorf("limit must be non-negative, got %d", limit)
return nil, errors.GrpcInvalidArgumentErrorf("limit must be non-negative, got %d", limit)
}

entitylessCase := checkEntitylessCase(requestedSortedFeatureViews)
addDummyEntityIfNeeded(entitylessCase, joinKeyToEntityValues, numRows)

arrowMemory := memory.NewGoAllocator()
entityColumns, err := onlineserving.EntitiesToRangeFeatureVectors(
joinKeyToEntityValues, arrowMemory, numRows)
if err != nil {
return nil, err
}

result := make([]*onlineserving.RangeFeatureVector, 0, len(entityColumns))
result = append(result, entityColumns...)
result := make([]*onlineserving.RangeFeatureVector, 0)

groupedRangeRefs, err := onlineserving.GroupSortedFeatureRefs(
requestedSortedFeatureViews,
Expand All @@ -410,7 +415,7 @@ func (fs *FeatureStore) GetOnlineFeaturesRange(
for _, groupRef := range groupedRangeRefs {
featureData, err := fs.readRangeFromOnlineStore(ctx, groupRef)
if err != nil {
return nil, err
return nil, errors.GrpcFromError(err)
}

vectors, err := onlineserving.TransposeRangeFeatureRowsIntoColumns(
Expand All @@ -427,6 +432,11 @@ func (fs *FeatureStore) GetOnlineFeaturesRange(
result = append(result, vectors...)
}

result, err = onlineserving.KeepOnlyRequestedFeatures(result, featureRefs, featureService, fullFeatureNames)
if err != nil {
return nil, err
}

return result, nil
}

Expand All @@ -453,9 +463,14 @@ func (fs *FeatureStore) ParseFeatures(kind interface{}) (*Features, error) {
}
return &Features{FeaturesRefs: nil, FeatureService: featureService}, nil
case *serving.GetOnlineFeaturesRangeRequest_FeatureService:
return nil, errors.New("range requests only support 'kind' of a list of Features")
featureServiceRequest := kind.(*serving.GetOnlineFeaturesRangeRequest_FeatureService)
featureService, err := fs.registry.GetFeatureService(fs.config.Project, featureServiceRequest.FeatureService)
if err != nil {
return nil, err
}
return &Features{FeaturesRefs: nil, FeatureService: featureService}, nil
default:
return nil, errors.New("cannot parse 'kind' of either a Feature Service or list of Features from request")
return nil, errors.GrpcInvalidArgumentErrorf("cannot parse 'kind' of either a Feature Service or list of Features from request")
}
}

Expand Down
Loading
Loading