diff --git a/java/common/src/main/java/feast/common/models/FeatureV2.java b/java/common/src/main/java/feast/common/models/Feature.java similarity index 74% rename from java/common/src/main/java/feast/common/models/FeatureV2.java rename to java/common/src/main/java/feast/common/models/Feature.java index 8420cca80c..340a8cbe69 100644 --- a/java/common/src/main/java/feast/common/models/FeatureV2.java +++ b/java/common/src/main/java/feast/common/models/Feature.java @@ -18,7 +18,7 @@ import feast.proto.serving.ServingAPIProto.FeatureReferenceV2; -public class FeatureV2 { +public class Feature { /** * Accepts FeatureReferenceV2 object and returns its reference in String @@ -27,10 +27,10 @@ public class FeatureV2 { * @param featureReference {@link FeatureReferenceV2} * @return String format of FeatureReferenceV2 */ - public static String getFeatureStringRef(FeatureReferenceV2 featureReference) { - String ref = featureReference.getName(); - if (!featureReference.getFeatureTable().isEmpty()) { - ref = featureReference.getFeatureTable() + ":" + ref; + public static String getFeatureReference(FeatureReferenceV2 featureReference) { + String ref = featureReference.getFeatureName(); + if (!featureReference.getFeatureViewName().isEmpty()) { + ref = featureReference.getFeatureViewName() + ":" + ref; } return ref; } @@ -47,4 +47,12 @@ public static String getFeatureName(String featureReference) { String[] tokens = featureReference.split(":", 2); return tokens[tokens.length - 1]; } + + public static FeatureReferenceV2 parseFeatureReference(String featureReference) { + String[] tokens = featureReference.split(":", 2); + return FeatureReferenceV2.newBuilder() + .setFeatureViewName(tokens[0]) + .setFeatureName(tokens[1]) + .build(); + } } diff --git a/java/common/src/main/java/feast/common/models/FeatureTable.java b/java/common/src/main/java/feast/common/models/FeatureTable.java deleted file mode 100644 index 88fac151ce..0000000000 --- a/java/common/src/main/java/feast/common/models/FeatureTable.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright 2018-2020 The Feast Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package feast.common.models; - -import feast.proto.core.FeatureTableProto.FeatureTableSpec; -import feast.proto.serving.ServingAPIProto.FeatureReferenceV2; - -public class FeatureTable { - - /** - * Accepts FeatureTableSpec object and returns its reference in String - * "project/featuretable_name". - * - * @param project project name - * @param featureTableSpec {@link FeatureTableSpec} - * @return String format of FeatureTableReference - */ - public static String getFeatureTableStringRef(String project, FeatureTableSpec featureTableSpec) { - return String.format("%s/%s", project, featureTableSpec.getName()); - } - - /** - * Accepts FeatureReferenceV2 object and returns its reference in String - * "project/featuretable_name". - * - * @param project project name - * @param featureReference {@link FeatureReferenceV2} - * @return String format of FeatureTableReference - */ - public static String getFeatureTableStringRef( - String project, FeatureReferenceV2 featureReference) { - return String.format("%s/%s", project, featureReference.getFeatureTable()); - } -} diff --git a/java/common/src/test/java/feast/common/logging/entry/AuditLogEntryTest.java b/java/common/src/test/java/feast/common/logging/entry/AuditLogEntryTest.java index cf355e09e4..0c96ee9c56 100644 --- a/java/common/src/test/java/feast/common/logging/entry/AuditLogEntryTest.java +++ b/java/common/src/test/java/feast/common/logging/entry/AuditLogEntryTest.java @@ -39,12 +39,12 @@ public List getTestAuditLogs() { .addAllFeatures( Arrays.asList( FeatureReferenceV2.newBuilder() - .setFeatureTable("featuretable_1") - .setName("feature1") + .setFeatureViewName("featuretable_1") + .setFeatureName("feature1") .build(), FeatureReferenceV2.newBuilder() - .setFeatureTable("featuretable_1") - .setName("feature2") + .setFeatureViewName("featuretable_1") + .setFeatureName("feature2") .build())) .build(); diff --git a/java/common/src/test/java/feast/common/models/FeaturesTest.java b/java/common/src/test/java/feast/common/models/FeaturesTest.java index 180f7e4e69..953da61afe 100644 --- a/java/common/src/test/java/feast/common/models/FeaturesTest.java +++ b/java/common/src/test/java/feast/common/models/FeaturesTest.java @@ -31,14 +31,14 @@ public class FeaturesTest { public void setUp() { featureReference = FeatureReferenceV2.newBuilder() - .setFeatureTable("featuretable_1") - .setName("feature1") + .setFeatureViewName("featuretable_1") + .setFeatureName("feature1") .build(); } @Test public void shouldReturnFeatureStringRef() { - String actualFeatureStringRef = FeatureV2.getFeatureStringRef(featureReference); + String actualFeatureStringRef = Feature.getFeatureReference(featureReference); String expectedFeatureStringRef = "featuretable_1:feature1"; assertThat(actualFeatureStringRef, equalTo(expectedFeatureStringRef)); diff --git a/java/sdk/java/src/main/java/com/gojek/feast/FeastClient.java b/java/sdk/java/src/main/java/dev/feast/FeastClient.java similarity index 74% rename from java/sdk/java/src/main/java/com/gojek/feast/FeastClient.java rename to java/sdk/java/src/main/java/dev/feast/FeastClient.java index 0c0b279be6..e9aaab151a 100644 --- a/java/sdk/java/src/main/java/com/gojek/feast/FeastClient.java +++ b/java/sdk/java/src/main/java/dev/feast/FeastClient.java @@ -14,16 +14,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.gojek.feast; +package dev.feast; -import feast.proto.serving.ServingAPIProto.FeatureReferenceV2; +import com.google.common.collect.Lists; +import feast.proto.serving.ServingAPIProto; import feast.proto.serving.ServingAPIProto.GetFeastServingInfoRequest; import feast.proto.serving.ServingAPIProto.GetFeastServingInfoResponse; -import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequestV2; -import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequestV2.EntityRow; -import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesResponse; +import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequest; +import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesResponseV2; import feast.proto.serving.ServingServiceGrpc; import feast.proto.serving.ServingServiceGrpc.ServingServiceBlockingStub; +import feast.proto.types.ValueProto; import io.grpc.CallCredentials; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; @@ -32,9 +33,8 @@ import io.opentracing.contrib.grpc.TracingClientInterceptor; import io.opentracing.util.GlobalTracer; import java.io.File; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; +import java.time.Instant; +import java.util.*; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import javax.net.ssl.SSLException; @@ -118,11 +118,60 @@ public GetFeastServingInfoResponse getFeastServingInfo() { * @param featureRefs list of string feature references to retrieve in the following format * featureTable:feature, where 'featureTable' and 'feature' refer to the FeatureTable and * Feature names respectively. Only the Feature name is required. - * @param rows list of {@link Row} to select the entities to retrieve the features for. + * @param entities list of {@link Row} to select the entities to retrieve the features for. * @return list of {@link Row} containing retrieved data fields. */ - public List getOnlineFeatures(List featureRefs, List rows) { - return getOnlineFeatures(featureRefs, rows, ""); + public List getOnlineFeatures(List featureRefs, List entities) { + GetOnlineFeaturesRequest.Builder requestBuilder = GetOnlineFeaturesRequest.newBuilder(); + + requestBuilder.setFeatures( + ServingAPIProto.FeatureList.newBuilder().addAllVal(featureRefs).build()); + + requestBuilder.putAllEntities(getEntityValuesMap(entities)); + + GetOnlineFeaturesResponseV2 response = stub.getOnlineFeatures(requestBuilder.build()); + + List results = Lists.newArrayList(); + if (response.getResultsCount() == 0) { + return results; + } + + for (int rowIdx = 0; rowIdx < response.getResults(0).getValuesCount(); rowIdx++) { + Row row = Row.create(); + for (int featureIdx = 0; featureIdx < response.getResultsCount(); featureIdx++) { + row.set( + response.getMetadata().getFeatureNames().getVal(featureIdx), + response.getResults(featureIdx).getValues(rowIdx), + response.getResults(featureIdx).getStatuses(rowIdx)); + + row.setEntityTimestamp( + Instant.ofEpochSecond( + response.getResults(featureIdx).getEventTimestamps(rowIdx).getSeconds())); + } + for (Map.Entry entry : + entities.get(rowIdx).getFields().entrySet()) { + row.set(entry.getKey(), entry.getValue()); + } + + results.add(row); + } + return results; + } + + private Map getEntityValuesMap(List entities) { + Map columnarEntities = new HashMap<>(); + for (Row row : entities) { + for (Map.Entry field : row.getFields().entrySet()) { + if (!columnarEntities.containsKey(field.getKey())) { + columnarEntities.put(field.getKey(), ValueProto.RepeatedValue.newBuilder()); + } + columnarEntities.get(field.getKey()).addVal(field.getValue()); + } + } + + return columnarEntities.entrySet().stream() + .map((e) -> Map.entry(e.getKey(), e.getValue().build())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } /** @@ -149,42 +198,7 @@ public List getOnlineFeatures(List featureRefs, List rows) { * @return list of {@link Row} containing retrieved data fields. */ public List getOnlineFeatures(List featureRefs, List rows, String project) { - List features = RequestUtil.createFeatureRefs(featureRefs); - // build entity rows and collect entity references - HashSet entityRefs = new HashSet<>(); - List entityRows = - rows.stream() - .map( - row -> { - entityRefs.addAll(row.getFields().keySet()); - return EntityRow.newBuilder() - .setTimestamp(row.getEntityTimestamp()) - .putAllFields(row.getFields()) - .build(); - }) - .collect(Collectors.toList()); - - GetOnlineFeaturesResponse response = - stub.getOnlineFeaturesV2( - GetOnlineFeaturesRequestV2.newBuilder() - .addAllFeatures(features) - .addAllEntityRows(entityRows) - .setProject(project) - .build()); - - return response.getFieldValuesList().stream() - .map( - fieldValues -> { - Row row = Row.create(); - for (String fieldName : fieldValues.getFieldsMap().keySet()) { - row.set( - fieldName, - fieldValues.getFieldsMap().get(fieldName), - fieldValues.getStatusesMap().get(fieldName)); - } - return row; - }) - .collect(Collectors.toList()); + return getOnlineFeatures(featureRefs, rows); } protected FeastClient(ManagedChannel channel, Optional credentials) { diff --git a/java/sdk/java/src/main/java/com/gojek/feast/RequestUtil.java b/java/sdk/java/src/main/java/dev/feast/RequestUtil.java similarity index 95% rename from java/sdk/java/src/main/java/com/gojek/feast/RequestUtil.java rename to java/sdk/java/src/main/java/dev/feast/RequestUtil.java index 69c8f9f737..fc13c45311 100644 --- a/java/sdk/java/src/main/java/com/gojek/feast/RequestUtil.java +++ b/java/sdk/java/src/main/java/dev/feast/RequestUtil.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.gojek.feast; +package dev.feast; import feast.proto.serving.ServingAPIProto.FeatureReferenceV2; import java.util.List; @@ -71,8 +71,8 @@ public static FeatureReferenceV2 parseFeatureRef(String featureRefString) { String[] featureReferenceParts = featureRefString.split(":"); FeatureReferenceV2 featureRef = FeatureReferenceV2.newBuilder() - .setFeatureTable(featureReferenceParts[0]) - .setName(featureReferenceParts[1]) + .setFeatureViewName(featureReferenceParts[0]) + .setFeatureName(featureReferenceParts[1]) .build(); return featureRef; diff --git a/java/sdk/java/src/main/java/com/gojek/feast/Row.java b/java/sdk/java/src/main/java/dev/feast/Row.java similarity index 97% rename from java/sdk/java/src/main/java/com/gojek/feast/Row.java rename to java/sdk/java/src/main/java/dev/feast/Row.java index 51f820e320..308daa5a2f 100644 --- a/java/sdk/java/src/main/java/com/gojek/feast/Row.java +++ b/java/sdk/java/src/main/java/dev/feast/Row.java @@ -14,12 +14,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.gojek.feast; +package dev.feast; import com.google.protobuf.ByteString; import com.google.protobuf.Timestamp; import com.google.protobuf.util.Timestamps; -import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesResponse.FieldStatus; +import feast.proto.serving.ServingAPIProto.FieldStatus; import feast.proto.types.ValueProto.Value; import feast.proto.types.ValueProto.Value.ValCase; import java.time.Instant; diff --git a/java/sdk/java/src/main/java/com/gojek/feast/SecurityConfig.java b/java/sdk/java/src/main/java/dev/feast/SecurityConfig.java similarity index 98% rename from java/sdk/java/src/main/java/com/gojek/feast/SecurityConfig.java rename to java/sdk/java/src/main/java/dev/feast/SecurityConfig.java index 94c779cf44..29acb97631 100644 --- a/java/sdk/java/src/main/java/com/gojek/feast/SecurityConfig.java +++ b/java/sdk/java/src/main/java/dev/feast/SecurityConfig.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.gojek.feast; +package dev.feast; import com.google.auto.value.AutoValue; import io.grpc.CallCredentials; diff --git a/java/sdk/java/src/test/java/com/gojek/feast/FeastClientTest.java b/java/sdk/java/src/test/java/dev/feast/FeastClientTest.java similarity index 68% rename from java/sdk/java/src/test/java/com/gojek/feast/FeastClientTest.java rename to java/sdk/java/src/test/java/dev/feast/FeastClientTest.java index 29185cd153..3de5142a85 100644 --- a/java/sdk/java/src/test/java/com/gojek/feast/FeastClientTest.java +++ b/java/sdk/java/src/test/java/dev/feast/FeastClientTest.java @@ -14,20 +14,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.gojek.feast; +package dev.feast; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.AdditionalAnswers.delegatesTo; import static org.mockito.Mockito.mock; import com.google.protobuf.Timestamp; -import feast.proto.serving.ServingAPIProto.FeatureReferenceV2; -import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequestV2; -import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequestV2.EntityRow; -import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesResponse; -import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesResponse.FieldStatus; -import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesResponse.FieldValues; +import feast.proto.serving.ServingAPIProto; +import feast.proto.serving.ServingAPIProto.FieldStatus; +import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequest; +import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesResponseV2; import feast.proto.serving.ServingServiceGrpc.ServingServiceImplBase; +import feast.proto.types.ValueProto; import feast.proto.types.ValueProto.Value; import io.grpc.*; import io.grpc.inprocess.InProcessChannelBuilder; @@ -56,9 +55,9 @@ public class FeastClientTest { delegatesTo( new ServingServiceImplBase() { @Override - public void getOnlineFeaturesV2( - GetOnlineFeaturesRequestV2 request, - StreamObserver responseObserver) { + public void getOnlineFeatures( + GetOnlineFeaturesRequest request, + StreamObserver responseObserver) { if (!request.equals(FeastClientTest.getFakeRequest())) { responseObserver.onError(Status.FAILED_PRECONDITION.asRuntimeException()); } @@ -125,35 +124,46 @@ private void shouldGetOnlineFeaturesWithClient(FeastClient client) { }); } - private static GetOnlineFeaturesRequestV2 getFakeRequest() { + private static GetOnlineFeaturesRequest getFakeRequest() { // setup mock serving service stub - return GetOnlineFeaturesRequestV2.newBuilder() - .addFeatures( - FeatureReferenceV2.newBuilder().setFeatureTable("driver").setName("name").build()) - .addFeatures( - FeatureReferenceV2.newBuilder().setFeatureTable("driver").setName("rating").build()) - .addFeatures( - FeatureReferenceV2.newBuilder().setFeatureTable("driver").setName("null_value").build()) - .addEntityRows( - EntityRow.newBuilder() - .setTimestamp(Timestamp.newBuilder().setSeconds(100)) - .putFields("driver_id", intValue(1))) - .setProject("driver_project") + return GetOnlineFeaturesRequest.newBuilder() + .setFeatures( + ServingAPIProto.FeatureList.newBuilder() + .addVal("driver:name") + .addVal("driver:rating") + .addVal("driver:null_value") + .build()) + .putEntities("driver_id", ValueProto.RepeatedValue.newBuilder().addVal(intValue(1)).build()) .build(); } - private static GetOnlineFeaturesResponse getFakeResponse() { - return GetOnlineFeaturesResponse.newBuilder() - .addFieldValues( - FieldValues.newBuilder() - .putFields("driver_id", intValue(1)) - .putStatuses("driver_id", FieldStatus.PRESENT) - .putFields("driver:name", strValue("david")) - .putStatuses("driver:name", FieldStatus.PRESENT) - .putFields("driver:rating", intValue(3)) - .putStatuses("driver:rating", FieldStatus.PRESENT) - .putFields("driver:null_value", Value.newBuilder().build()) - .putStatuses("driver:null_value", FieldStatus.NULL_VALUE) + private static GetOnlineFeaturesResponseV2 getFakeResponse() { + return GetOnlineFeaturesResponseV2.newBuilder() + .addResults( + GetOnlineFeaturesResponseV2.FeatureVector.newBuilder() + .addValues(strValue("david")) + .addStatuses(FieldStatus.PRESENT) + .addEventTimestamps(Timestamp.newBuilder()) + .build()) + .addResults( + GetOnlineFeaturesResponseV2.FeatureVector.newBuilder() + .addValues(intValue(3)) + .addStatuses(FieldStatus.PRESENT) + .addEventTimestamps(Timestamp.newBuilder()) + .build()) + .addResults( + GetOnlineFeaturesResponseV2.FeatureVector.newBuilder() + .addValues(Value.newBuilder().build()) + .addStatuses(FieldStatus.NULL_VALUE) + .addEventTimestamps(Timestamp.newBuilder()) + .build()) + .setMetadata( + ServingAPIProto.GetOnlineFeaturesResponseMetadata.newBuilder() + .setFeatureNames( + ServingAPIProto.FeatureList.newBuilder() + .addVal("driver:name") + .addVal("driver:rating") + .addVal("driver:null_value")) .build()) .build(); } diff --git a/java/sdk/java/src/test/java/com/gojek/feast/RequestUtilTest.java b/java/sdk/java/src/test/java/dev/feast/RequestUtilTest.java similarity index 90% rename from java/sdk/java/src/test/java/com/gojek/feast/RequestUtilTest.java rename to java/sdk/java/src/test/java/dev/feast/RequestUtilTest.java index 1592e20664..21fb145b24 100644 --- a/java/sdk/java/src/test/java/com/gojek/feast/RequestUtilTest.java +++ b/java/sdk/java/src/test/java/dev/feast/RequestUtilTest.java @@ -14,14 +14,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.gojek.feast; +package dev.feast; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import com.google.common.collect.ImmutableList; import com.google.protobuf.TextFormat; -import feast.common.models.FeatureV2; +import feast.common.models.Feature; import feast.proto.serving.ServingAPIProto.FeatureReferenceV2; import java.util.Arrays; import java.util.Comparator; @@ -41,8 +41,8 @@ private static Stream provideValidFeatureRefs() { Arrays.asList("driver:driver_id"), Arrays.asList( FeatureReferenceV2.newBuilder() - .setFeatureTable("driver") - .setName("driver_id") + .setFeatureViewName("driver") + .setFeatureName("driver_id") .build()))); } @@ -52,8 +52,8 @@ void createFeatureRefs_ShouldReturnFeaturesForValidFeatureRefs( List input, List expected) { List actual = RequestUtil.createFeatureRefs(input); // Order of the actual and expected FeatureTables do no not matter - actual.sort(Comparator.comparing(FeatureReferenceV2::getName)); - expected.sort(Comparator.comparing(FeatureReferenceV2::getName)); + actual.sort(Comparator.comparing(FeatureReferenceV2::getFeatureName)); + expected.sort(Comparator.comparing(FeatureReferenceV2::getFeatureName)); assertEquals(expected.size(), actual.size()); for (int i = 0; i < expected.size(); i++) { String expectedString = TextFormat.printer().printToString(expected.get(i)); @@ -68,7 +68,7 @@ void renderFeatureRef_ShouldReturnFeatureRefString( List expected, List input) { input = input.stream().map(ref -> ref.toBuilder().build()).collect(Collectors.toList()); List actual = - input.stream().map(ref -> FeatureV2.getFeatureStringRef(ref)).collect(Collectors.toList()); + input.stream().map(ref -> Feature.getFeatureReference(ref)).collect(Collectors.toList()); assertEquals(expected.size(), actual.size()); for (int i = 0; i < expected.size(); i++) { assertEquals(expected.get(i), actual.get(i)); diff --git a/java/serving/src/main/java/feast/serving/config/ApplicationProperties.java b/java/serving/src/main/java/feast/serving/config/ApplicationProperties.java index 4d822d8dbc..dfcb9e8db2 100644 --- a/java/serving/src/main/java/feast/serving/config/ApplicationProperties.java +++ b/java/serving/src/main/java/feast/serving/config/ApplicationProperties.java @@ -42,22 +42,32 @@ public void setRegistry(String registry) { this.registry = registry; } - public void setRegistryRefreshInterval(int registryRefreshInterval) { - this.registryRefreshInterval = registryRefreshInterval; - } - @NotBlank private String registry; public String getRegistry() { return registry; } + @NotBlank private String project; + + public String getProject() { + return project; + } + + public void setProject(final String project) { + this.project = project; + } + private int registryRefreshInterval; public int getRegistryRefreshInterval() { return registryRefreshInterval; } + public void setRegistryRefreshInterval(int registryRefreshInterval) { + this.registryRefreshInterval = registryRefreshInterval; + } + /** * Finds and returns the active store * diff --git a/java/serving/src/main/java/feast/serving/config/ServingServiceConfigV2.java b/java/serving/src/main/java/feast/serving/config/ServingServiceConfigV2.java index 52d7d1c8d6..d3fe1ba116 100644 --- a/java/serving/src/main/java/feast/serving/config/ServingServiceConfigV2.java +++ b/java/serving/src/main/java/feast/serving/config/ServingServiceConfigV2.java @@ -44,12 +44,20 @@ public ServingServiceV2 registryBasedServingServiceV2( case REDIS_CLUSTER: RedisClientAdapter redisClusterClient = RedisClusterClient.create(store.getRedisClusterConfig()); - retrieverV2 = new OnlineRetriever(redisClusterClient, new EntityKeySerializerV2()); + retrieverV2 = + new OnlineRetriever( + applicationProperties.getFeast().getProject(), + redisClusterClient, + new EntityKeySerializerV2()); break; case REDIS: RedisClientAdapter redisClient = RedisClient.create(store.getRedisConfig()); log.info("Created EntityKeySerializerV2"); - retrieverV2 = new OnlineRetriever(redisClient, new EntityKeySerializerV2()); + retrieverV2 = + new OnlineRetriever( + applicationProperties.getFeast().getProject(), + redisClient, + new EntityKeySerializerV2()); break; default: throw new RuntimeException( @@ -67,7 +75,11 @@ public ServingServiceV2 registryBasedServingServiceV2( servingService = new OnlineServingServiceV2( - retrieverV2, tracer, registryRepository, onlineTransformationService); + retrieverV2, + tracer, + registryRepository, + onlineTransformationService, + applicationProperties.getFeast().getProject()); return servingService; } diff --git a/java/serving/src/main/java/feast/serving/controller/ServingServiceGRpcController.java b/java/serving/src/main/java/feast/serving/controller/ServingServiceGRpcController.java index 0a406930e6..0f4ef7b5ae 100644 --- a/java/serving/src/main/java/feast/serving/controller/ServingServiceGRpcController.java +++ b/java/serving/src/main/java/feast/serving/controller/ServingServiceGRpcController.java @@ -19,11 +19,9 @@ import feast.proto.serving.ServingAPIProto; import feast.proto.serving.ServingAPIProto.GetFeastServingInfoRequest; import feast.proto.serving.ServingAPIProto.GetFeastServingInfoResponse; -import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesResponse; import feast.proto.serving.ServingServiceGrpc.ServingServiceImplBase; import feast.serving.config.ApplicationProperties; import feast.serving.exception.SpecRetrievalException; -import feast.serving.interceptors.GrpcMonitoringContext; import feast.serving.service.ServingServiceV2; import feast.serving.util.RequestHelper; import io.grpc.Status; @@ -60,22 +58,19 @@ public void getFeastServingInfo( } @Override - public void getOnlineFeaturesV2( - ServingAPIProto.GetOnlineFeaturesRequestV2 request, - StreamObserver responseObserver) { + public void getOnlineFeatures( + ServingAPIProto.GetOnlineFeaturesRequest request, + StreamObserver responseObserver) { try { // authorize for the project in request object. - request.getProject(); - if (!request.getProject().isEmpty()) { - // update monitoring context - GrpcMonitoringContext.getInstance().setProject(request.getProject()); - } RequestHelper.validateOnlineRequest(request); Span span = tracer.buildSpan("getOnlineFeaturesV2").start(); - GetOnlineFeaturesResponse onlineFeatures = servingServiceV2.getOnlineFeatures(request); + ServingAPIProto.GetOnlineFeaturesResponseV2 onlineFeatures = + servingServiceV2.getOnlineFeatures(request); if (span != null) { span.finish(); } + responseObserver.onNext(onlineFeatures); responseObserver.onCompleted(); } catch (SpecRetrievalException e) { diff --git a/java/serving/src/main/java/feast/serving/controller/ServingServiceRestController.java b/java/serving/src/main/java/feast/serving/controller/ServingServiceRestController.java index 2f446adf67..fe8f13d8bc 100644 --- a/java/serving/src/main/java/feast/serving/controller/ServingServiceRestController.java +++ b/java/serving/src/main/java/feast/serving/controller/ServingServiceRestController.java @@ -18,10 +18,9 @@ import static feast.serving.util.mappers.ResponseJSONMapper.mapGetOnlineFeaturesResponse; +import feast.proto.serving.ServingAPIProto; import feast.proto.serving.ServingAPIProto.GetFeastServingInfoRequest; import feast.proto.serving.ServingAPIProto.GetFeastServingInfoResponse; -import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequestV2; -import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesResponse; import feast.serving.config.ApplicationProperties; import feast.serving.service.ServingServiceV2; import feast.serving.util.RequestHelper; @@ -53,9 +52,10 @@ public GetFeastServingInfoResponse getInfo() { produces = "application/json", consumes = "application/json") public List> getOnlineFeatures( - @RequestBody GetOnlineFeaturesRequestV2 request) { + @RequestBody ServingAPIProto.GetOnlineFeaturesRequest request) { RequestHelper.validateOnlineRequest(request); - GetOnlineFeaturesResponse onlineFeatures = servingService.getOnlineFeatures(request); + ServingAPIProto.GetOnlineFeaturesResponseV2 onlineFeatures = + servingService.getOnlineFeatures(request); return mapGetOnlineFeaturesResponse(onlineFeatures); } } diff --git a/java/serving/src/main/java/feast/serving/grpc/OnlineServingGrpcServiceV2.java b/java/serving/src/main/java/feast/serving/grpc/OnlineServingGrpcServiceV2.java index 68a17539ab..f3a35d1d0f 100644 --- a/java/serving/src/main/java/feast/serving/grpc/OnlineServingGrpcServiceV2.java +++ b/java/serving/src/main/java/feast/serving/grpc/OnlineServingGrpcServiceV2.java @@ -39,9 +39,9 @@ public void getFeastServingInfo( } @Override - public void getOnlineFeaturesV2( - ServingAPIProto.GetOnlineFeaturesRequestV2 request, - StreamObserver responseObserver) { + public void getOnlineFeatures( + ServingAPIProto.GetOnlineFeaturesRequest request, + StreamObserver responseObserver) { responseObserver.onNext(this.servingServiceV2.getOnlineFeatures(request)); responseObserver.onCompleted(); } diff --git a/java/serving/src/main/java/feast/serving/registry/Registry.java b/java/serving/src/main/java/feast/serving/registry/Registry.java index 144135c3ca..37fae3d8dc 100644 --- a/java/serving/src/main/java/feast/serving/registry/Registry.java +++ b/java/serving/src/main/java/feast/serving/registry/Registry.java @@ -16,10 +16,7 @@ */ package feast.serving.registry; -import feast.proto.core.FeatureProto; -import feast.proto.core.FeatureViewProto; -import feast.proto.core.OnDemandFeatureViewProto; -import feast.proto.core.RegistryProto; +import feast.proto.core.*; import feast.proto.serving.ServingAPIProto; import feast.serving.exception.SpecRetrievalException; import java.util.List; @@ -32,6 +29,7 @@ public class Registry { private Map featureViewNameToSpec; private Map onDemandFeatureViewNameToSpec; + private Map featureServiceNameToSpec; Registry(RegistryProto.Registry registry) { this.registry = registry; @@ -53,6 +51,12 @@ public class Registry { Collectors.toMap( OnDemandFeatureViewProto.OnDemandFeatureViewSpec::getName, Function.identity())); + this.featureServiceNameToSpec = + registry.getFeatureServicesList().stream() + .map(fs -> fs.getSpec()) + .collect( + Collectors.toMap( + FeatureServiceProto.FeatureServiceSpec::getName, Function.identity())); } public RegistryProto.Registry getRegistry() { @@ -60,8 +64,8 @@ public RegistryProto.Registry getRegistry() { } public FeatureViewProto.FeatureViewSpec getFeatureViewSpec( - String projectName, ServingAPIProto.FeatureReferenceV2 featureReference) { - String featureViewName = featureReference.getFeatureTable(); + ServingAPIProto.FeatureReferenceV2 featureReference) { + String featureViewName = featureReference.getFeatureViewName(); if (featureViewNameToSpec.containsKey(featureViewName)) { return featureViewNameToSpec.get(featureViewName); } @@ -70,11 +74,10 @@ public FeatureViewProto.FeatureViewSpec getFeatureViewSpec( } public FeatureProto.FeatureSpecV2 getFeatureSpec( - String projectName, ServingAPIProto.FeatureReferenceV2 featureReference) { - final FeatureViewProto.FeatureViewSpec spec = - this.getFeatureViewSpec(projectName, featureReference); + ServingAPIProto.FeatureReferenceV2 featureReference) { + final FeatureViewProto.FeatureViewSpec spec = this.getFeatureViewSpec(featureReference); for (final FeatureProto.FeatureSpecV2 featureSpec : spec.getFeaturesList()) { - if (featureSpec.getName().equals(featureReference.getName())) { + if (featureSpec.getName().equals(featureReference.getFeatureName())) { return featureSpec; } } @@ -82,12 +85,12 @@ public FeatureProto.FeatureSpecV2 getFeatureSpec( throw new SpecRetrievalException( String.format( "Unable to find feature with name: %s in feature view: %s", - featureReference.getName(), featureReference.getFeatureTable())); + featureReference.getFeatureName(), featureReference.getFeatureViewName())); } public OnDemandFeatureViewProto.OnDemandFeatureViewSpec getOnDemandFeatureViewSpec( - String projectName, ServingAPIProto.FeatureReferenceV2 featureReference) { - String onDemandFeatureViewName = featureReference.getFeatureTable(); + ServingAPIProto.FeatureReferenceV2 featureReference) { + String onDemandFeatureViewName = featureReference.getFeatureViewName(); if (onDemandFeatureViewNameToSpec.containsKey(onDemandFeatureViewName)) { return onDemandFeatureViewNameToSpec.get(onDemandFeatureViewName); } @@ -97,7 +100,16 @@ public OnDemandFeatureViewProto.OnDemandFeatureViewSpec getOnDemandFeatureViewSp } public boolean isOnDemandFeatureReference(ServingAPIProto.FeatureReferenceV2 featureReference) { - String onDemandFeatureViewName = featureReference.getFeatureTable(); + String onDemandFeatureViewName = featureReference.getFeatureViewName(); return onDemandFeatureViewNameToSpec.containsKey(onDemandFeatureViewName); } + + public FeatureServiceProto.FeatureServiceSpec getFeatureServiceSpec(String name) { + FeatureServiceProto.FeatureServiceSpec spec = featureServiceNameToSpec.get(name); + if (spec == null) { + throw new SpecRetrievalException( + String.format("Unable to find feature service with name: %s", name)); + } + return spec; + } } diff --git a/java/serving/src/main/java/feast/serving/registry/RegistryRepository.java b/java/serving/src/main/java/feast/serving/registry/RegistryRepository.java index 23c204b582..369493ee0f 100644 --- a/java/serving/src/main/java/feast/serving/registry/RegistryRepository.java +++ b/java/serving/src/main/java/feast/serving/registry/RegistryRepository.java @@ -18,6 +18,7 @@ import com.google.protobuf.Duration; import feast.proto.core.FeatureProto; +import feast.proto.core.FeatureServiceProto; import feast.proto.core.FeatureViewProto; import feast.proto.core.OnDemandFeatureViewProto; import feast.proto.core.RegistryProto; @@ -72,31 +73,33 @@ private void refresh() { } public FeatureViewProto.FeatureViewSpec getFeatureViewSpec( - String projectName, ServingAPIProto.FeatureReferenceV2 featureReference) { - return this.registry.getFeatureViewSpec(projectName, featureReference); + ServingAPIProto.FeatureReferenceV2 featureReference) { + return this.registry.getFeatureViewSpec(featureReference); } public FeatureProto.FeatureSpecV2 getFeatureSpec( - String projectName, ServingAPIProto.FeatureReferenceV2 featureReference) { - return this.registry.getFeatureSpec(projectName, featureReference); + ServingAPIProto.FeatureReferenceV2 featureReference) { + return this.registry.getFeatureSpec(featureReference); } public OnDemandFeatureViewProto.OnDemandFeatureViewSpec getOnDemandFeatureViewSpec( - String projectName, ServingAPIProto.FeatureReferenceV2 featureReference) { - return this.registry.getOnDemandFeatureViewSpec(projectName, featureReference); + ServingAPIProto.FeatureReferenceV2 featureReference) { + return this.registry.getOnDemandFeatureViewSpec(featureReference); } public boolean isOnDemandFeatureReference(ServingAPIProto.FeatureReferenceV2 featureReference) { return this.registry.isOnDemandFeatureReference(featureReference); } - public Duration getMaxAge( - String projectName, ServingAPIProto.FeatureReferenceV2 featureReference) { - return getFeatureViewSpec(projectName, featureReference).getTtl(); + public FeatureServiceProto.FeatureServiceSpec getFeatureServiceSpec(String name) { + return this.registry.getFeatureServiceSpec(name); } - public List getEntitiesList( - String projectName, ServingAPIProto.FeatureReferenceV2 featureReference) { - return getFeatureViewSpec(projectName, featureReference).getEntitiesList(); + public Duration getMaxAge(ServingAPIProto.FeatureReferenceV2 featureReference) { + return getFeatureViewSpec(featureReference).getTtl(); + } + + public List getEntitiesList(ServingAPIProto.FeatureReferenceV2 featureReference) { + return getFeatureViewSpec(featureReference).getEntitiesList(); } } diff --git a/java/serving/src/main/java/feast/serving/service/OnlineServingServiceV2.java b/java/serving/src/main/java/feast/serving/service/OnlineServingServiceV2.java index 2d5621e4b4..5774dc361a 100644 --- a/java/serving/src/main/java/feast/serving/service/OnlineServingServiceV2.java +++ b/java/serving/src/main/java/feast/serving/service/OnlineServingServiceV2.java @@ -16,32 +16,29 @@ */ package feast.serving.service; -import static feast.common.models.FeatureTable.getFeatureTableStringRef; - +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import com.google.protobuf.Duration; -import feast.common.models.FeatureV2; -import feast.proto.serving.ServingAPIProto.FeastServingType; +import com.google.protobuf.Timestamp; +import feast.common.models.Feature; +import feast.proto.core.FeatureServiceProto; +import feast.proto.serving.ServingAPIProto; import feast.proto.serving.ServingAPIProto.FeatureReferenceV2; +import feast.proto.serving.ServingAPIProto.FieldStatus; import feast.proto.serving.ServingAPIProto.GetFeastServingInfoRequest; import feast.proto.serving.ServingAPIProto.GetFeastServingInfoResponse; -import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequestV2; -import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesResponse; import feast.proto.serving.TransformationServiceAPIProto.TransformFeaturesRequest; import feast.proto.serving.TransformationServiceAPIProto.TransformFeaturesResponse; import feast.proto.serving.TransformationServiceAPIProto.ValueType; import feast.proto.types.ValueProto; -import feast.serving.exception.SpecRetrievalException; import feast.serving.registry.RegistryRepository; import feast.serving.util.Metrics; -import feast.storage.api.retriever.Feature; import feast.storage.api.retriever.OnlineRetrieverV2; import io.grpc.Status; import io.opentracing.Span; import io.opentracing.Tracer; import java.util.*; -import java.util.function.Function; import java.util.stream.Collectors; -import java.util.stream.IntStream; import org.apache.commons.lang3.tuple.Pair; import org.slf4j.Logger; @@ -52,126 +49,79 @@ public class OnlineServingServiceV2 implements ServingServiceV2 { private final OnlineRetrieverV2 retriever; private final RegistryRepository registryRepository; private final OnlineTransformationService onlineTransformationService; + private final String project; public OnlineServingServiceV2( OnlineRetrieverV2 retriever, Tracer tracer, RegistryRepository registryRepository, - OnlineTransformationService onlineTransformationService) { + OnlineTransformationService onlineTransformationService, + String project) { this.retriever = retriever; this.tracer = tracer; this.registryRepository = registryRepository; this.onlineTransformationService = onlineTransformationService; + this.project = project; } /** {@inheritDoc} */ @Override public GetFeastServingInfoResponse getFeastServingInfo( GetFeastServingInfoRequest getFeastServingInfoRequest) { - return GetFeastServingInfoResponse.newBuilder() - .setType(FeastServingType.FEAST_SERVING_TYPE_ONLINE) - .build(); + return GetFeastServingInfoResponse.getDefaultInstance(); } @Override - public GetOnlineFeaturesResponse getOnlineFeatures(GetOnlineFeaturesRequestV2 request) { - // Autofill default project if project is not specified - String projectName = request.getProject(); - if (projectName.isEmpty()) { - projectName = "default"; - } - + public ServingAPIProto.GetOnlineFeaturesResponseV2 getOnlineFeatures( + ServingAPIProto.GetOnlineFeaturesRequest request) { // Split all feature references into non-ODFV (e.g. batch and stream) references and ODFV. - List allFeatureReferences = request.getFeaturesList(); - List featureReferences = + List allFeatureReferences = getFeaturesList(request); + List retrievedFeatureReferences = allFeatureReferences.stream() .filter(r -> !this.registryRepository.isOnDemandFeatureReference(r)) .collect(Collectors.toList()); + int userRequestedFeaturesSize = retrievedFeatureReferences.size(); + List onDemandFeatureReferences = allFeatureReferences.stream() .filter(r -> this.registryRepository.isOnDemandFeatureReference(r)) .collect(Collectors.toList()); - // Get the set of request data feature names and feature inputs from the ODFV references. - Pair, List> pair = - this.onlineTransformationService.extractRequestDataFeatureNamesAndOnDemandFeatureInputs( - onDemandFeatureReferences, projectName); - Set requestDataFeatureNames = pair.getLeft(); - List onDemandFeatureInputs = pair.getRight(); + // ToDo (pyalex): refactor transformation service to delete unused left part of the returned + // Pair from extractRequestDataFeatureNamesAndOnDemandFeatureInputs. + // Currently, we can retrieve context variables directly from GetOnlineFeaturesRequest. + List onDemandFeatureInputs = + this.onlineTransformationService + .extractRequestDataFeatureNamesAndOnDemandFeatureInputs(onDemandFeatureReferences) + .getRight(); // Add on demand feature inputs to list of feature references to retrieve. - Set addedFeatureReferences = new HashSet(); for (FeatureReferenceV2 onDemandFeatureInput : onDemandFeatureInputs) { - if (!featureReferences.contains(onDemandFeatureInput)) { - featureReferences.add(onDemandFeatureInput); - addedFeatureReferences.add(onDemandFeatureInput); + if (!retrievedFeatureReferences.contains(onDemandFeatureInput)) { + retrievedFeatureReferences.add(onDemandFeatureInput); } } - // Separate entity rows into entity data and request feature data. - Pair, Map>> - entityRowsAndRequestDataFeatures = - this.onlineTransformationService.separateEntityRows(requestDataFeatureNames, request); - List entityRows = - entityRowsAndRequestDataFeatures.getLeft(); - Map> requestDataFeatures = - entityRowsAndRequestDataFeatures.getRight(); - // TODO: error checking on lengths of lists in entityRows and requestDataFeatures - - // Extract values and statuses to be used later in constructing FieldValues for the response. - // The online features retrieved will augment these two data structures. - List> values = - entityRows.stream().map(r -> new HashMap<>(r.getFieldsMap())).collect(Collectors.toList()); - List> statuses = - entityRows.stream() - .map( - r -> - r.getFieldsMap().entrySet().stream() - .map(entry -> Pair.of(entry.getKey(), getMetadata(entry.getValue(), false))) - .collect(Collectors.toMap(Pair::getLeft, Pair::getRight))) - .collect(Collectors.toList()); + List> entityRows = getEntityRows(request); - String finalProjectName = projectName; - Map featureMaxAges = - featureReferences.stream() - .distinct() - .collect( - Collectors.toMap( - Function.identity(), - ref -> this.registryRepository.getMaxAge(finalProjectName, ref))); - List entityNames = - featureReferences.stream() - .map(ref -> this.registryRepository.getEntitiesList(finalProjectName, ref)) - .findFirst() - .get(); - - Map featureValueTypes = - featureReferences.stream() - .distinct() - .collect( - Collectors.toMap( - Function.identity(), - ref -> { - try { - return this.registryRepository - .getFeatureSpec(finalProjectName, ref) - .getValueType(); - } catch (SpecRetrievalException e) { - return ValueProto.ValueType.Enum.INVALID; - } - })); + List entityNames; + if (retrievedFeatureReferences.size() > 0) { + entityNames = this.registryRepository.getEntitiesList(retrievedFeatureReferences.get(0)); + } else { + throw new RuntimeException("Requested features list must not be empty"); + } Span storageRetrievalSpan = tracer.buildSpan("storageRetrieval").start(); if (storageRetrievalSpan != null) { storageRetrievalSpan.setTag("entities", entityRows.size()); - storageRetrievalSpan.setTag("features", featureReferences.size()); + storageRetrievalSpan.setTag("features", retrievedFeatureReferences.size()); } - List> features = - retriever.getOnlineFeatures(projectName, entityRows, featureReferences, entityNames); + List> features = + retriever.getOnlineFeatures(entityRows, retrievedFeatureReferences, entityNames); + if (storageRetrievalSpan != null) { storageRetrievalSpan.finish(); } - if (features.size() != entityRows.size()) { throw Status.INTERNAL .withDescription( @@ -182,132 +132,188 @@ public GetOnlineFeaturesResponse getOnlineFeatures(GetOnlineFeaturesRequestV2 re Span postProcessingSpan = tracer.buildSpan("postProcessing").start(); - for (int i = 0; i < entityRows.size(); i++) { - GetOnlineFeaturesRequestV2.EntityRow entityRow = entityRows.get(i); - Map featureRow = features.get(i); + ServingAPIProto.GetOnlineFeaturesResponseV2.Builder responseBuilder = + ServingAPIProto.GetOnlineFeaturesResponseV2.newBuilder(); - Map rowValues = values.get(i); - Map rowStatuses = statuses.get(i); + Timestamp now = Timestamp.newBuilder().setSeconds(System.currentTimeMillis() / 1000).build(); + Timestamp nullTimestamp = Timestamp.newBuilder().build(); + ValueProto.Value nullValue = ValueProto.Value.newBuilder().build(); - for (FeatureReferenceV2 featureReference : featureReferences) { - if (featureRow.containsKey(featureReference)) { - Feature feature = featureRow.get(featureReference); + for (int featureIdx = 0; featureIdx < userRequestedFeaturesSize; featureIdx++) { + FeatureReferenceV2 featureReference = retrievedFeatureReferences.get(featureIdx); - ValueProto.Value value = feature.getFeatureValue(featureValueTypes.get(featureReference)); + ValueProto.ValueType.Enum valueType = + this.registryRepository.getFeatureSpec(featureReference).getValueType(); - Boolean isOutsideMaxAge = - checkOutsideMaxAge(feature, entityRow, featureMaxAges.get(featureReference)); + Duration maxAge = this.registryRepository.getMaxAge(featureReference); - if (value != null) { - rowValues.put(FeatureV2.getFeatureStringRef(featureReference), value); - } else { - rowValues.put( - FeatureV2.getFeatureStringRef(featureReference), - ValueProto.Value.newBuilder().build()); - } + ServingAPIProto.GetOnlineFeaturesResponseV2.FeatureVector.Builder vectorBuilder = + responseBuilder.addResultsBuilder(); - rowStatuses.put( - FeatureV2.getFeatureStringRef(featureReference), getMetadata(value, isOutsideMaxAge)); - } else { - rowValues.put( - FeatureV2.getFeatureStringRef(featureReference), - ValueProto.Value.newBuilder().build()); + for (int rowIdx = 0; rowIdx < features.size(); rowIdx++) { + feast.storage.api.retriever.Feature feature = features.get(rowIdx).get(featureIdx); + if (feature == null) { + vectorBuilder.addValues(nullValue); + vectorBuilder.addStatuses(FieldStatus.NOT_FOUND); + vectorBuilder.addEventTimestamps(nullTimestamp); + continue; + } - rowStatuses.put( - FeatureV2.getFeatureStringRef(featureReference), getMetadata(null, false)); + ValueProto.Value featureValue = feature.getFeatureValue(valueType); + if (featureValue == null) { + vectorBuilder.addValues(nullValue); + vectorBuilder.addStatuses(FieldStatus.NOT_FOUND); + vectorBuilder.addEventTimestamps(nullTimestamp); + continue; } + + vectorBuilder.addValues(featureValue); + vectorBuilder.addStatuses( + getFeatureStatus(featureValue, checkOutsideMaxAge(feature, now, maxAge))); + vectorBuilder.addEventTimestamps(feature.getEventTimestamp()); } - // Populate metrics/log request - populateCountMetrics(rowStatuses, projectName); + + populateCountMetrics(featureReference, vectorBuilder); } + responseBuilder.setMetadata( + ServingAPIProto.GetOnlineFeaturesResponseMetadata.newBuilder() + .setFeatureNames( + ServingAPIProto.FeatureList.newBuilder() + .addAllVal( + retrievedFeatureReferences.stream() + .map(Feature::getFeatureReference) + .collect(Collectors.toList())))); + if (postProcessingSpan != null) { postProcessingSpan.finish(); } - populateHistogramMetrics(entityRows, featureReferences, projectName); - populateFeatureCountMetrics(featureReferences, projectName); - - // Handle ODFVs. For each ODFV reference, we send a TransformFeaturesRequest to the FTS. - // The request should contain the entity data, the retrieved features, and the request data. if (!onDemandFeatureReferences.isEmpty()) { - // Augment values, which contains the entity data and retrieved features, with the request - // data. Also augment statuses. - for (int i = 0; i < values.size(); i++) { - Map rowValues = values.get(i); - Map rowStatuses = statuses.get(i); - - for (Map.Entry> entry : requestDataFeatures.entrySet()) { - String key = entry.getKey(); - List fieldValues = entry.getValue(); - rowValues.put(key, fieldValues.get(i)); - rowStatuses.put(key, GetOnlineFeaturesResponse.FieldStatus.PRESENT); - } - } + // Handle ODFVs. For each ODFV reference, we send a TransformFeaturesRequest to the FTS. + // The request should contain the entity data, the retrieved features, and the request context + // data. + this.populateOnDemandFeatures( + onDemandFeatureReferences, + onDemandFeatureInputs, + retrievedFeatureReferences, + request, + features, + responseBuilder); + } - // Serialize the augmented values. - ValueType transformationInput = - this.onlineTransformationService.serializeValuesIntoArrowIPC(values); - - // Send out requests to the FTS and process the responses. - Set onDemandFeatureStringReferences = - onDemandFeatureReferences.stream() - .map(r -> FeatureV2.getFeatureStringRef(r)) - .collect(Collectors.toSet()); - for (FeatureReferenceV2 featureReference : onDemandFeatureReferences) { - String onDemandFeatureViewName = featureReference.getFeatureTable(); - TransformFeaturesRequest transformFeaturesRequest = - TransformFeaturesRequest.newBuilder() - .setOnDemandFeatureViewName(onDemandFeatureViewName) - .setProject(projectName) - .setTransformationInput(transformationInput) - .build(); - - TransformFeaturesResponse transformFeaturesResponse = - this.onlineTransformationService.transformFeatures(transformFeaturesRequest); - - this.onlineTransformationService.processTransformFeaturesResponse( - transformFeaturesResponse, - onDemandFeatureViewName, - onDemandFeatureStringReferences, - values, - statuses); - } + populateHistogramMetrics(entityRows, retrievedFeatureReferences); + populateFeatureCountMetrics(retrievedFeatureReferences); - // Remove all features that were added as inputs for ODFVs. - Set addedFeatureStringReferences = - addedFeatureReferences.stream() - .map(r -> FeatureV2.getFeatureStringRef(r)) - .collect(Collectors.toSet()); - for (int i = 0; i < values.size(); i++) { - Map rowValues = values.get(i); - Map rowStatuses = statuses.get(i); - List keysToRemove = - rowValues.keySet().stream() - .filter(k -> addedFeatureStringReferences.contains(k)) - .collect(Collectors.toList()); - for (String key : keysToRemove) { - rowValues.remove(key); - rowStatuses.remove(key); + return responseBuilder.build(); + } + + private List getFeaturesList( + ServingAPIProto.GetOnlineFeaturesRequest request) { + if (request.getFeatures().getValCount() > 0) { + return request.getFeatures().getValList().stream() + .map(Feature::parseFeatureReference) + .collect(Collectors.toList()); + } + + FeatureServiceProto.FeatureServiceSpec featureServiceSpec = + this.registryRepository.getFeatureServiceSpec(request.getFeatureService()); + + return featureServiceSpec.getFeaturesList().stream() + .flatMap( + featureViewProjection -> + featureViewProjection.getFeatureColumnsList().stream() + .map( + f -> + FeatureReferenceV2.newBuilder() + .setFeatureViewName(featureViewProjection.getFeatureViewName()) + .setFeatureName(f.getName()) + .build())) + .collect(Collectors.toList()); + } + + private List> getEntityRows( + ServingAPIProto.GetOnlineFeaturesRequest request) { + if (request.getEntitiesCount() == 0) { + throw new RuntimeException("Entities map shouldn't be empty"); + } + + Set entityNames = request.getEntitiesMap().keySet(); + String firstEntity = entityNames.stream().findFirst().get(); + int rowsCount = request.getEntitiesMap().get(firstEntity).getValCount(); + List> entityRows = Lists.newArrayListWithExpectedSize(rowsCount); + + for (Map.Entry entity : request.getEntitiesMap().entrySet()) { + for (int i = 0; i < rowsCount; i++) { + if (entityRows.size() < i + 1) { + entityRows.add(i, Maps.newHashMapWithExpectedSize(entityNames.size())); } + + entityRows.get(i).put(entity.getKey(), entity.getValue().getVal(i)); } } - // Build response field values from entityValuesMap and entityStatusesMap - // Response field values should be in the same order as the entityRows provided by the user. - List fieldValuesList = - IntStream.range(0, entityRows.size()) - .mapToObj( - entityRowIdx -> - GetOnlineFeaturesResponse.FieldValues.newBuilder() - .putAllFields(values.get(entityRowIdx)) - .putAllStatuses(statuses.get(entityRowIdx)) - .build()) + return entityRows; + } + + private void populateOnDemandFeatures( + List onDemandFeatureReferences, + List onDemandFeatureInputs, + List retrievedFeatureReferences, + ServingAPIProto.GetOnlineFeaturesRequest request, + List> features, + ServingAPIProto.GetOnlineFeaturesResponseV2.Builder responseBuilder) { + + List>> onDemandContext = + request.getRequestContextMap().entrySet().stream() + .map(e -> Pair.of(e.getKey(), e.getValue().getValList())) .collect(Collectors.toList()); - return GetOnlineFeaturesResponse.newBuilder().addAllFieldValues(fieldValuesList).build(); - } + for (int featureIdx = 0; featureIdx < retrievedFeatureReferences.size(); featureIdx++) { + FeatureReferenceV2 featureReference = retrievedFeatureReferences.get(featureIdx); + if (!onDemandFeatureInputs.contains(featureReference)) { + continue; + } + + ValueProto.ValueType.Enum valueType = + this.registryRepository.getFeatureSpec(featureReference).getValueType(); + + List valueList = Lists.newArrayListWithExpectedSize(features.size()); + for (int rowIdx = 0; rowIdx < features.size(); rowIdx++) { + valueList.add(features.get(rowIdx).get(featureIdx).getFeatureValue(valueType)); + } + + onDemandContext.add(Pair.of(Feature.getFeatureReference(featureReference), valueList)); + } + // Serialize the augmented values. + ValueType transformationInput = + this.onlineTransformationService.serializeValuesIntoArrowIPC(onDemandContext); + + // Send out requests to the FTS and process the responses. + Set onDemandFeatureStringReferences = + onDemandFeatureReferences.stream() + .map(r -> Feature.getFeatureReference(r)) + .collect(Collectors.toSet()); + + for (FeatureReferenceV2 featureReference : onDemandFeatureReferences) { + String onDemandFeatureViewName = featureReference.getFeatureViewName(); + TransformFeaturesRequest transformFeaturesRequest = + TransformFeaturesRequest.newBuilder() + .setOnDemandFeatureViewName(onDemandFeatureViewName) + .setTransformationInput(transformationInput) + .build(); + + TransformFeaturesResponse transformFeaturesResponse = + this.onlineTransformationService.transformFeatures(transformFeaturesRequest); + + this.onlineTransformationService.processTransformFeaturesResponse( + transformFeaturesResponse, + onDemandFeatureViewName, + onDemandFeatureStringReferences, + responseBuilder); + } + } /** * Generate Field level Status metadata for the given valueMap. * @@ -317,17 +323,16 @@ public GetOnlineFeaturesResponse getOnlineFeatures(GetOnlineFeaturesRequestV2 re * @return a 1:1 map keyed by field name containing field status metadata instead of values in the * given valueMap. */ - private static GetOnlineFeaturesResponse.FieldStatus getMetadata( - ValueProto.Value value, boolean isOutsideMaxAge) { + private static FieldStatus getFeatureStatus(ValueProto.Value value, boolean isOutsideMaxAge) { if (value == null) { - return GetOnlineFeaturesResponse.FieldStatus.NOT_FOUND; + return FieldStatus.NOT_FOUND; } else if (isOutsideMaxAge) { - return GetOnlineFeaturesResponse.FieldStatus.OUTSIDE_MAX_AGE; + return FieldStatus.OUTSIDE_MAX_AGE; } else if (value.getValCase().equals(ValueProto.Value.ValCase.VAL_NOT_SET)) { - return GetOnlineFeaturesResponse.FieldStatus.NULL_VALUE; + return FieldStatus.NULL_VALUE; } - return GetOnlineFeaturesResponse.FieldStatus.PRESENT; + return FieldStatus.PRESENT; } /** @@ -336,17 +341,17 @@ private static GetOnlineFeaturesResponse.FieldStatus getMetadata( * in entity row exceeds FeatureTable max age. * * @param feature contains the ingestion timing and feature data. - * @param entityRow contains the retrieval timing of when features are pulled. + * @param entityTimestamp contains the retrieval timing of when features are pulled. * @param maxAge feature's max age. */ private static boolean checkOutsideMaxAge( - Feature feature, GetOnlineFeaturesRequestV2.EntityRow entityRow, Duration maxAge) { + feast.storage.api.retriever.Feature feature, Timestamp entityTimestamp, Duration maxAge) { if (maxAge.equals(Duration.getDefaultInstance())) { // max age is not set return false; } - long givenTimestamp = entityRow.getTimestamp().getSeconds(); + long givenTimestamp = entityTimestamp.getSeconds(); if (givenTimestamp == 0) { givenTimestamp = System.currentTimeMillis() / 1000; } @@ -359,54 +364,45 @@ private static boolean checkOutsideMaxAge( * * @param entityRows entity rows provided in request * @param featureReferences feature references provided in request - * @param project project name provided in request */ private void populateHistogramMetrics( - List entityRows, - List featureReferences, - String project) { + List> entityRows, List featureReferences) { Metrics.requestEntityCountDistribution - .labels(project) + .labels(this.project) .observe(Double.valueOf(entityRows.size())); Metrics.requestFeatureCountDistribution - .labels(project) + .labels(this.project) .observe(Double.valueOf(featureReferences.size())); - - long countDistinctFeatureTables = - featureReferences.stream() - .map(featureReference -> getFeatureTableStringRef(project, featureReference)) - .distinct() - .count(); - Metrics.requestFeatureTableCountDistribution - .labels(project) - .observe(Double.valueOf(countDistinctFeatureTables)); } /** * Populate count metrics that can be used for analysing online retrieval calls * - * @param statusMap Statuses of features which have been requested - * @param project Project where request for features was called from + * @param featureRef singe Feature Reference + * @param featureVector Feature Vector built for this requested feature */ private void populateCountMetrics( - Map statusMap, String project) { - statusMap.forEach( - (featureRefString, status) -> { - if (status == GetOnlineFeaturesResponse.FieldStatus.NOT_FOUND) { - Metrics.notFoundKeyCount.labels(project, featureRefString).inc(); - } - if (status == GetOnlineFeaturesResponse.FieldStatus.OUTSIDE_MAX_AGE) { - Metrics.staleKeyCount.labels(project, featureRefString).inc(); - } - }); + FeatureReferenceV2 featureRef, + ServingAPIProto.GetOnlineFeaturesResponseV2.FeatureVectorOrBuilder featureVector) { + String featureRefString = Feature.getFeatureReference(featureRef); + featureVector + .getStatusesList() + .forEach( + (status) -> { + if (status == FieldStatus.NOT_FOUND) { + Metrics.notFoundKeyCount.labels(this.project, featureRefString).inc(); + } + if (status == FieldStatus.OUTSIDE_MAX_AGE) { + Metrics.staleKeyCount.labels(this.project, featureRefString).inc(); + } + }); } - private void populateFeatureCountMetrics( - List featureReferences, String project) { + private void populateFeatureCountMetrics(List featureReferences) { featureReferences.forEach( featureReference -> Metrics.requestFeatureCount - .labels(project, FeatureV2.getFeatureStringRef(featureReference)) + .labels(project, Feature.getFeatureReference(featureReference)) .inc()); } } diff --git a/java/serving/src/main/java/feast/serving/service/OnlineTransformationService.java b/java/serving/src/main/java/feast/serving/service/OnlineTransformationService.java index 23ee9854b2..bfe717aa96 100644 --- a/java/serving/src/main/java/feast/serving/service/OnlineTransformationService.java +++ b/java/serving/src/main/java/feast/serving/service/OnlineTransformationService.java @@ -16,8 +16,10 @@ */ package feast.serving.service; +import com.google.common.collect.Lists; import com.google.protobuf.ByteString; -import feast.common.models.FeatureV2; +import com.google.protobuf.Timestamp; +import feast.common.models.Feature; import feast.proto.core.DataSourceProto; import feast.proto.core.FeatureProto; import feast.proto.core.FeatureViewProto; @@ -82,13 +84,13 @@ public TransformFeaturesResponse transformFeatures( @Override public Pair, List> extractRequestDataFeatureNamesAndOnDemandFeatureInputs( - List onDemandFeatureReferences, String projectName) { + List onDemandFeatureReferences) { Set requestDataFeatureNames = new HashSet(); List onDemandFeatureInputs = new ArrayList(); for (ServingAPIProto.FeatureReferenceV2 featureReference : onDemandFeatureReferences) { OnDemandFeatureViewProto.OnDemandFeatureViewSpec onDemandFeatureViewSpec = - this.registryRepository.getOnDemandFeatureViewSpec(projectName, featureReference); + this.registryRepository.getOnDemandFeatureViewSpec(featureReference); Map inputs = onDemandFeatureViewSpec.getInputsMap(); @@ -110,8 +112,8 @@ public TransformFeaturesResponse transformFeatures( String featureName = featureSpec.getName(); ServingAPIProto.FeatureReferenceV2 onDemandFeatureInput = ServingAPIProto.FeatureReferenceV2.newBuilder() - .setFeatureTable(featureViewName) - .setName(featureName) + .setFeatureViewName(featureViewName) + .setFeatureName(featureName) .build(); onDemandFeatureInputs.add(onDemandFeatureInput); } @@ -187,8 +189,7 @@ public void processTransformFeaturesResponse( transformFeaturesResponse, String onDemandFeatureViewName, Set onDemandFeatureStringReferences, - List> values, - List> statuses) { + ServingAPIProto.GetOnlineFeaturesResponseV2.Builder responseBuilder) { try { BufferAllocator allocator = new RootAllocator(Long.MAX_VALUE); ArrowFileReader reader = @@ -203,6 +204,7 @@ public void processTransformFeaturesResponse( VectorSchemaRoot readBatch = reader.getVectorSchemaRoot(); Schema responseSchema = readBatch.getSchema(); List responseFields = responseSchema.getFields(); + Timestamp now = Timestamp.newBuilder().setSeconds(System.currentTimeMillis() / 1000).build(); for (Field field : responseFields) { String columnName = field.getName(); @@ -217,6 +219,9 @@ public void processTransformFeaturesResponse( FieldVector fieldVector = readBatch.getVector(field); int valueCount = fieldVector.getValueCount(); + ServingAPIProto.GetOnlineFeaturesResponseV2.FeatureVector.Builder vectorBuilder = + responseBuilder.addResultsBuilder(); + List valueList = Lists.newArrayListWithExpectedSize(valueCount); // TODO: support all Feast types // TODO: clean up the switch statement @@ -226,27 +231,13 @@ public void processTransformFeaturesResponse( case INT64_BITWIDTH: for (int i = 0; i < valueCount; i++) { long int64Value = ((BigIntVector) fieldVector).get(i); - Map rowValues = values.get(i); - Map rowStatuses = - statuses.get(i); - ValueProto.Value value = - ValueProto.Value.newBuilder().setInt64Val(int64Value).build(); - rowValues.put(fullFeatureName, value); - rowStatuses.put( - fullFeatureName, ServingAPIProto.GetOnlineFeaturesResponse.FieldStatus.PRESENT); + valueList.add(ValueProto.Value.newBuilder().setInt64Val(int64Value).build()); } break; case INT32_BITWIDTH: for (int i = 0; i < valueCount; i++) { - int intValue = ((IntVector) fieldVector).get(i); - Map rowValues = values.get(i); - Map rowStatuses = - statuses.get(i); - ValueProto.Value value = - ValueProto.Value.newBuilder().setInt32Val(intValue).build(); - rowValues.put(fullFeatureName, value); - rowStatuses.put( - fullFeatureName, ServingAPIProto.GetOnlineFeaturesResponse.FieldStatus.PRESENT); + int int32Value = ((IntVector) fieldVector).get(i); + valueList.add(ValueProto.Value.newBuilder().setInt32Val(int32Value).build()); } break; default: @@ -265,27 +256,13 @@ public void processTransformFeaturesResponse( case DOUBLE: for (int i = 0; i < valueCount; i++) { double doubleValue = ((Float8Vector) fieldVector).get(i); - Map rowValues = values.get(i); - Map rowStatuses = - statuses.get(i); - ValueProto.Value value = - ValueProto.Value.newBuilder().setDoubleVal(doubleValue).build(); - rowValues.put(fullFeatureName, value); - rowStatuses.put( - fullFeatureName, ServingAPIProto.GetOnlineFeaturesResponse.FieldStatus.PRESENT); + valueList.add(ValueProto.Value.newBuilder().setDoubleVal(doubleValue).build()); } break; case SINGLE: for (int i = 0; i < valueCount; i++) { float floatValue = ((Float4Vector) fieldVector).get(i); - Map rowValues = values.get(i); - Map rowStatuses = - statuses.get(i); - ValueProto.Value value = - ValueProto.Value.newBuilder().setFloatVal(floatValue).build(); - rowValues.put(fullFeatureName, value); - rowStatuses.put( - fullFeatureName, ServingAPIProto.GetOnlineFeaturesResponse.FieldStatus.PRESENT); + valueList.add(ValueProto.Value.newBuilder().setFloatVal(floatValue).build()); } break; default: @@ -299,6 +276,14 @@ public void processTransformFeaturesResponse( .asRuntimeException(); } } + + for (ValueProto.Value v : valueList) { + vectorBuilder.addValues(v); + vectorBuilder.addStatuses(ServingAPIProto.FieldStatus.PRESENT); + vectorBuilder.addEventTimestamps(now); + } + + responseBuilder.getMetadataBuilder().getFeatureNamesBuilder().addVal(fullFeatureName); } } catch (IOException e) { log.info(e.toString()); @@ -310,30 +295,52 @@ public void processTransformFeaturesResponse( } /** {@inheritDoc} */ - public ValueType serializeValuesIntoArrowIPC(List> values) { + public ValueType serializeValuesIntoArrowIPC(List>> values) { // In order to be serialized correctly, the data must be packaged in a VectorSchemaRoot. // We first construct all the columns. Map columnNameToColumn = new HashMap(); BufferAllocator allocator = new RootAllocator(Long.MAX_VALUE); - Map firstAugmentedRowValues = values.get(0); - for (Map.Entry entry : firstAugmentedRowValues.entrySet()) { + + List columnFields = new ArrayList(); + List columns = new ArrayList(); + + for (Pair> columnEntry : values) { // The Python FTS does not expect full feature names, so we extract the feature name. - String columnName = FeatureV2.getFeatureName(entry.getKey()); - ValueProto.Value.ValCase valCase = entry.getValue().getValCase(); + String columnName = Feature.getFeatureName(columnEntry.getKey()); + + List columnValues = columnEntry.getValue(); FieldVector column; + ValueProto.Value.ValCase valCase = columnValues.get(0).getValCase(); // TODO: support all Feast types switch (valCase) { case INT32_VAL: column = new IntVector(columnName, allocator); + column.setValueCount(columnValues.size()); + for (int idx = 0; idx < columnValues.size(); idx++) { + ((IntVector) column).set(idx, columnValues.get(idx).getInt32Val()); + } break; case INT64_VAL: column = new BigIntVector(columnName, allocator); + column.setValueCount(columnValues.size()); + for (int idx = 0; idx < columnValues.size(); idx++) { + ((BigIntVector) column).set(idx, columnValues.get(idx).getInt64Val()); + } + break; case DOUBLE_VAL: column = new Float8Vector(columnName, allocator); + column.setValueCount(columnValues.size()); + for (int idx = 0; idx < columnValues.size(); idx++) { + ((Float8Vector) column).set(idx, columnValues.get(idx).getInt64Val()); + } break; case FLOAT_VAL: column = new Float4Vector(columnName, allocator); + column.setValueCount(columnValues.size()); + for (int idx = 0; idx < columnValues.size(); idx++) { + ((Float4Vector) column).set(idx, columnValues.get(idx).getInt64Val()); + } break; default: throw Status.INTERNAL @@ -341,53 +348,11 @@ public ValueType serializeValuesIntoArrowIPC(List> "Column " + columnName + " has a type that is currently not handled: " + valCase) .asRuntimeException(); } - column.allocateNew(); - columnNameToColumn.put(columnName, column); - } - - // Add the data, row by row. - for (int i = 0; i < values.size(); i++) { - Map augmentedRowValues = values.get(i); - - for (Map.Entry entry : augmentedRowValues.entrySet()) { - String columnName = FeatureV2.getFeatureName(entry.getKey()); - ValueProto.Value value = entry.getValue(); - ValueProto.Value.ValCase valCase = value.getValCase(); - FieldVector column = columnNameToColumn.get(columnName); - // TODO: support all Feast types - switch (valCase) { - case INT32_VAL: - ((IntVector) column).setSafe(i, value.getInt32Val()); - break; - case INT64_VAL: - ((BigIntVector) column).setSafe(i, value.getInt64Val()); - break; - case DOUBLE_VAL: - ((Float8Vector) column).setSafe(i, value.getDoubleVal()); - break; - case FLOAT_VAL: - ((Float4Vector) column).setSafe(i, value.getFloatVal()); - break; - default: - throw Status.INTERNAL - .withDescription( - "Column " - + columnName - + " has a type that is currently not handled: " - + valCase) - .asRuntimeException(); - } - } - } - // Construct the VectorSchemaRoot. - List columnFields = new ArrayList(); - List columns = new ArrayList(); - for (FieldVector column : columnNameToColumn.values()) { - column.setValueCount(values.size()); - columnFields.add(column.getField()); columns.add(column); + columnFields.add(column.getField()); } + VectorSchemaRoot schemaRoot = new VectorSchemaRoot(columnFields, columns); // Serialize the VectorSchemaRoot into Arrow IPC format. diff --git a/java/serving/src/main/java/feast/serving/service/ServingServiceV2.java b/java/serving/src/main/java/feast/serving/service/ServingServiceV2.java index 05acb31b78..096b155a0e 100644 --- a/java/serving/src/main/java/feast/serving/service/ServingServiceV2.java +++ b/java/serving/src/main/java/feast/serving/service/ServingServiceV2.java @@ -17,8 +17,6 @@ package feast.serving.service; import feast.proto.serving.ServingAPIProto; -import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequestV2; -import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesResponse; public interface ServingServiceV2 { /** @@ -36,23 +34,15 @@ ServingAPIProto.GetFeastServingInfoResponse getFeastServingInfo( /** * Get features from an online serving store, given a list of {@link - * feast.proto.serving.ServingAPIProto.FeatureReferenceV2}s to retrieve, and list of {@link - * feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequestV2.EntityRow}s to join the - * retrieved values to. - * - *

Features can be queried across feature tables, but each {@link - * feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequestV2.EntityRow} must contain all - * entities for all feature tables included in the request. + * feast.proto.serving.ServingAPIProto.FeatureReferenceV2}s to retrieve or name of the feature + * service, and vectorized entities Map<String, {@link + * feast.proto.types.ValueProto.RepeatedValue}> to join the retrieved values to. * *

This request is fulfilled synchronously. * - * @param getFeaturesRequest {@link GetOnlineFeaturesRequestV2} containing list of {@link - * feast.proto.serving.ServingAPIProto.FeatureReferenceV2}s to retrieve and list of {@link - * feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequestV2.EntityRow}s to join the - * retrieved values to. - * @return {@link GetOnlineFeaturesResponse} with list of {@link - * feast.proto.serving.ServingAPIProto.GetOnlineFeaturesResponse.FieldValues} for each {@link - * feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequestV2.EntityRow} supplied. + * @return {@link feast.proto.serving.ServingAPIProto.GetOnlineFeaturesResponseV2} with list of + * {@link feast.proto.serving.ServingAPIProto.GetOnlineFeaturesResponseV2.FeatureVector}. */ - GetOnlineFeaturesResponse getOnlineFeatures(GetOnlineFeaturesRequestV2 getFeaturesRequest); + ServingAPIProto.GetOnlineFeaturesResponseV2 getOnlineFeatures( + ServingAPIProto.GetOnlineFeaturesRequest getFeaturesRequest); } diff --git a/java/serving/src/main/java/feast/serving/service/TransformationService.java b/java/serving/src/main/java/feast/serving/service/TransformationService.java index caa5279302..36cce43e0d 100644 --- a/java/serving/src/main/java/feast/serving/service/TransformationService.java +++ b/java/serving/src/main/java/feast/serving/service/TransformationService.java @@ -18,7 +18,6 @@ import feast.proto.serving.ServingAPIProto; import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequestV2; -import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesResponse; import feast.proto.serving.TransformationServiceAPIProto.TransformFeaturesRequest; import feast.proto.serving.TransformationServiceAPIProto.TransformFeaturesResponse; import feast.proto.serving.TransformationServiceAPIProto.ValueType; @@ -42,13 +41,12 @@ public interface TransformationService { * list of ODFV references. * * @param onDemandFeatureReferences list of ODFV references to be parsed - * @param projectName project name * @return a pair containing the set of request data feature names and list of on demand feature * inputs */ Pair, List> extractRequestDataFeatureNamesAndOnDemandFeatureInputs( - List onDemandFeatureReferences, String projectName); + List onDemandFeatureReferences); /** * Separate the entity rows of a request into entity data and request feature data. @@ -68,15 +66,13 @@ public interface TransformationService { * @param transformFeaturesResponse response to be processed * @param onDemandFeatureViewName name of ODFV to which the response corresponds * @param onDemandFeatureStringReferences set of all ODFV references that should be kept - * @param values list of field maps to be augmented with additional fields from the response - * @param statuses list of status maps to be augmented + * @param responseBuilder {@link ServingAPIProto.GetOnlineFeaturesResponseV2.Builder} */ void processTransformFeaturesResponse( TransformFeaturesResponse transformFeaturesResponse, String onDemandFeatureViewName, Set onDemandFeatureStringReferences, - List> values, - List> statuses); + ServingAPIProto.GetOnlineFeaturesResponseV2.Builder responseBuilder); /** * Serialize data into Arrow IPC format, to be sent to the Python feature transformation server. @@ -84,5 +80,5 @@ void processTransformFeaturesResponse( * @param values list of field maps to be serialized * @return the data packaged into a ValueType proto object */ - ValueType serializeValuesIntoArrowIPC(List> values); + ValueType serializeValuesIntoArrowIPC(List>> values); } diff --git a/java/serving/src/main/java/feast/serving/util/RequestHelper.java b/java/serving/src/main/java/feast/serving/util/RequestHelper.java index 4d478f430f..f730e01982 100644 --- a/java/serving/src/main/java/feast/serving/util/RequestHelper.java +++ b/java/serving/src/main/java/feast/serving/util/RequestHelper.java @@ -16,27 +16,28 @@ */ package feast.serving.util; +import feast.common.models.Feature; +import feast.proto.serving.ServingAPIProto; import feast.proto.serving.ServingAPIProto.FeatureReferenceV2; -import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequestV2; public class RequestHelper { - public static void validateOnlineRequest(GetOnlineFeaturesRequestV2 request) { + public static void validateOnlineRequest(ServingAPIProto.GetOnlineFeaturesRequest request) { // All EntityRows should not be empty - if (request.getEntityRowsCount() <= 0) { + if (request.getEntitiesCount() <= 0) { throw new IllegalArgumentException("Entity value must be provided"); } // All FeatureReferences should have FeatureTable name and Feature name - for (FeatureReferenceV2 featureReference : request.getFeaturesList()) { - validateOnlineRequestFeatureReference(featureReference); + for (String featureReference : request.getFeatures().getValList()) { + validateOnlineRequestFeatureReference(Feature.parseFeatureReference(featureReference)); } } public static void validateOnlineRequestFeatureReference(FeatureReferenceV2 featureReference) { - if (featureReference.getFeatureTable().isEmpty()) { + if (featureReference.getFeatureViewName().isEmpty()) { throw new IllegalArgumentException("FeatureTable name must be provided in FeatureReference"); } - if (featureReference.getName().isEmpty()) { + if (featureReference.getFeatureName().isEmpty()) { throw new IllegalArgumentException("Feature name must be provided in FeatureReference"); } } diff --git a/java/serving/src/main/java/feast/serving/util/mappers/ResponseJSONMapper.java b/java/serving/src/main/java/feast/serving/util/mappers/ResponseJSONMapper.java index 238df54951..1e82bf864c 100644 --- a/java/serving/src/main/java/feast/serving/util/mappers/ResponseJSONMapper.java +++ b/java/serving/src/main/java/feast/serving/util/mappers/ResponseJSONMapper.java @@ -16,8 +16,7 @@ */ package feast.serving.util.mappers; -import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesResponse; -import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesResponse.FieldValues; +import feast.proto.serving.ServingAPIProto; import feast.proto.types.ValueProto.Value; import java.util.List; import java.util.Map; @@ -27,15 +26,23 @@ public class ResponseJSONMapper { public static List> mapGetOnlineFeaturesResponse( - GetOnlineFeaturesResponse response) { - return response.getFieldValuesList().stream() + ServingAPIProto.GetOnlineFeaturesResponseV2 response) { + return response.getResultsList().stream() .map(fieldValues -> convertFieldValuesToMap(fieldValues)) .collect(Collectors.toList()); } - private static Map convertFieldValuesToMap(FieldValues fieldValues) { - return fieldValues.getFieldsMap().entrySet().stream() - .collect(Collectors.toMap(es -> es.getKey(), es -> extractValue(es.getValue()))); + private static Map convertFieldValuesToMap( + ServingAPIProto.GetOnlineFeaturesResponseV2.FeatureVector vec) { + return Map.of( + "values", + vec.getValuesList().stream() + .map(ResponseJSONMapper::extractValue) + .collect(Collectors.toList()), + "statuses", + vec.getStatusesList(), + "event_timestamp", + vec.getEventTimestampsList()); } private static Object extractValue(Value value) { diff --git a/java/serving/src/main/resources/application.yml b/java/serving/src/main/resources/application.yml index 4fba32a0ae..1f6d5b34c4 100644 --- a/java/serving/src/main/resources/application.yml +++ b/java/serving/src/main/resources/application.yml @@ -1,4 +1,5 @@ feast: + project: "" registry: "prompt_dory/data/registry.db" registryRefreshInterval: 0 diff --git a/java/serving/src/test/java/feast/serving/it/ServingBase.java b/java/serving/src/test/java/feast/serving/it/ServingBase.java deleted file mode 100644 index 3a42f9a85e..0000000000 --- a/java/serving/src/test/java/feast/serving/it/ServingBase.java +++ /dev/null @@ -1,307 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright 2018-2021 The Feast Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package feast.serving.it; - -import static org.awaitility.Awaitility.await; -import static org.hamcrest.Matchers.equalTo; -import static org.junit.jupiter.api.Assertions.*; - -import com.google.common.collect.ImmutableList; -import com.google.inject.*; -import com.google.inject.Module; -import com.google.inject.util.Modules; -import com.google.protobuf.Timestamp; -import feast.proto.core.FeatureProto; -import feast.proto.core.FeatureViewProto; -import feast.proto.core.RegistryProto; -import feast.proto.serving.ServingAPIProto; -import feast.proto.serving.ServingServiceGrpc; -import feast.proto.types.ValueProto; -import feast.serving.config.*; -import feast.serving.grpc.OnlineServingGrpcServiceV2; -import feast.serving.util.DataGenerator; -import io.grpc.*; -import io.grpc.inprocess.InProcessChannelBuilder; -import io.grpc.inprocess.InProcessServerBuilder; -import io.grpc.protobuf.services.ProtoReflectionService; -import io.grpc.util.MutableHandlerRegistry; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.time.Duration; -import java.util.concurrent.TimeUnit; -import org.junit.jupiter.api.*; -import org.testcontainers.containers.DockerComposeContainer; -import org.testcontainers.containers.wait.strategy.Wait; -import org.testcontainers.junit.jupiter.Testcontainers; - -@Testcontainers -abstract class ServingBase { - static DockerComposeContainer environment; - - ServingServiceGrpc.ServingServiceBlockingStub servingStub; - Injector injector; - String serverName; - ManagedChannel channel; - Server server; - MutableHandlerRegistry serviceRegistry; - - @BeforeAll - static void globalSetup() { - environment = - new DockerComposeContainer( - new File("src/test/resources/docker-compose/docker-compose-redis-it.yml")) - .withExposedService("redis", 6379) - .withOptions() - .waitingFor( - "materialize", - Wait.forLogMessage(".*Materialization finished.*\\n", 1) - .withStartupTimeout(Duration.ofMinutes(5))); - environment.start(); - } - - @AfterAll - static void globalTeardown() { - environment.stop(); - } - - @BeforeEach - public void envSetUp() throws Exception { - - AbstractModule appPropertiesModule = - new AbstractModule() { - @Override - protected void configure() { - bind(OnlineServingGrpcServiceV2.class); - } - - @Provides - ApplicationProperties applicationProperties() { - final ApplicationProperties p = new ApplicationProperties(); - p.setAwsRegion("us-east-1"); - - final ApplicationProperties.FeastProperties feastProperties = createFeastProperties(); - p.setFeast(feastProperties); - - final ApplicationProperties.TracingProperties tracingProperties = - new ApplicationProperties.TracingProperties(); - feastProperties.setTracing(tracingProperties); - - tracingProperties.setEnabled(false); - return p; - } - }; - - Module overrideConfig = registryConfig(); - Module registryConfig; - if (overrideConfig != null) { - registryConfig = Modules.override(new RegistryConfig()).with(registryConfig()); - } else { - registryConfig = new RegistryConfig(); - } - - injector = - Guice.createInjector( - new ServingServiceConfigV2(), - registryConfig, - new InstrumentationConfig(), - appPropertiesModule); - - OnlineServingGrpcServiceV2 onlineServingGrpcServiceV2 = - injector.getInstance(OnlineServingGrpcServiceV2.class); - - serverName = InProcessServerBuilder.generateName(); - - server = - InProcessServerBuilder.forName(serverName) - .fallbackHandlerRegistry(serviceRegistry) - .addService(onlineServingGrpcServiceV2) - .addService(ProtoReflectionService.newInstance()) - .build(); - server.start(); - - channel = InProcessChannelBuilder.forName(serverName).usePlaintext().directExecutor().build(); - - servingStub = - ServingServiceGrpc.newBlockingStub(channel) - .withDeadlineAfter(5, TimeUnit.SECONDS) - .withWaitForReady(); - } - - @AfterEach - public void envTeardown() throws Exception { - // assume channel and server are not null - channel.shutdown(); - server.shutdown(); - // fail the test if cannot gracefully shutdown - try { - assert channel.awaitTermination(5, TimeUnit.SECONDS) - : "channel cannot be gracefully shutdown"; - assert server.awaitTermination(5, TimeUnit.SECONDS) : "server cannot be gracefully shutdown"; - } finally { - channel.shutdownNow(); - server.shutdownNow(); - } - } - - protected ServingAPIProto.GetOnlineFeaturesRequestV2 buildOnlineRequest(int driverId) { - // getOnlineFeatures Information - String projectName = "feast_project"; - String entityName = "driver_id"; - - // Instantiate EntityRows - final Timestamp timestamp = Timestamp.getDefaultInstance(); - ServingAPIProto.GetOnlineFeaturesRequestV2.EntityRow entityRow1 = - DataGenerator.createEntityRow( - entityName, DataGenerator.createInt64Value(driverId), timestamp.getSeconds()); - ImmutableList entityRows = - ImmutableList.of(entityRow1); - - // Instantiate FeatureReferences - ServingAPIProto.FeatureReferenceV2 feature1Reference = - DataGenerator.createFeatureReference("driver_hourly_stats", "conv_rate"); - ServingAPIProto.FeatureReferenceV2 feature2Reference = - DataGenerator.createFeatureReference("driver_hourly_stats", "avg_daily_trips"); - ImmutableList featureReferences = - ImmutableList.of(feature1Reference, feature2Reference); - - // Build GetOnlineFeaturesRequestV2 - return TestUtils.createOnlineFeatureRequest(projectName, featureReferences, entityRows); - } - - static RegistryProto.Registry registryProto = readLocalRegistry(); - - private static RegistryProto.Registry readLocalRegistry() { - try { - return RegistryProto.Registry.parseFrom( - Files.readAllBytes(Paths.get("src/test/resources/docker-compose/feast10/registry.db"))); - } catch (IOException e) { - e.printStackTrace(); - } - - return null; - } - - @Test - public void shouldGetOnlineFeatures() { - ServingAPIProto.GetOnlineFeaturesRequestV2 req = buildOnlineRequest(1005); - ServingAPIProto.GetOnlineFeaturesResponse featureResponse = - servingStub.withDeadlineAfter(1000, TimeUnit.MILLISECONDS).getOnlineFeaturesV2(req); - - assertEquals(1, featureResponse.getFieldValuesCount()); - - final ServingAPIProto.GetOnlineFeaturesResponse.FieldValues fieldValue = - featureResponse.getFieldValues(0); - for (final String key : - ImmutableList.of( - "driver_hourly_stats:avg_daily_trips", "driver_hourly_stats:conv_rate", "driver_id")) { - assertTrue(fieldValue.containsFields(key)); - assertTrue(fieldValue.containsStatuses(key)); - assertEquals( - ServingAPIProto.GetOnlineFeaturesResponse.FieldStatus.PRESENT, - fieldValue.getStatusesOrThrow(key)); - } - - assertEquals( - 500, fieldValue.getFieldsOrThrow("driver_hourly_stats:avg_daily_trips").getInt64Val()); - assertEquals(1005, fieldValue.getFieldsOrThrow("driver_id").getInt64Val()); - assertEquals( - 0.5, fieldValue.getFieldsOrThrow("driver_hourly_stats:conv_rate").getDoubleVal(), 0.0001); - } - - @Test - public void shouldGetOnlineFeaturesWithOutsideMaxAgeStatus() { - ServingAPIProto.GetOnlineFeaturesResponse featureResponse = - servingStub.getOnlineFeaturesV2(buildOnlineRequest(1001)); - - assertEquals(1, featureResponse.getFieldValuesCount()); - - final ServingAPIProto.GetOnlineFeaturesResponse.FieldValues fieldValue = - featureResponse.getFieldValues(0); - for (final String key : - ImmutableList.of("driver_hourly_stats:avg_daily_trips", "driver_hourly_stats:conv_rate")) { - assertTrue(fieldValue.containsFields(key)); - assertTrue(fieldValue.containsStatuses(key)); - assertEquals( - ServingAPIProto.GetOnlineFeaturesResponse.FieldStatus.OUTSIDE_MAX_AGE, - fieldValue.getStatusesOrThrow(key)); - } - - assertEquals( - 100, fieldValue.getFieldsOrThrow("driver_hourly_stats:avg_daily_trips").getInt64Val()); - assertEquals(1001, fieldValue.getFieldsOrThrow("driver_id").getInt64Val()); - assertEquals( - 0.1, fieldValue.getFieldsOrThrow("driver_hourly_stats:conv_rate").getDoubleVal(), 0.0001); - } - - @Test - public void shouldGetOnlineFeaturesWithNotFoundStatus() { - ServingAPIProto.GetOnlineFeaturesResponse featureResponse = - servingStub.getOnlineFeaturesV2(buildOnlineRequest(-1)); - - assertEquals(1, featureResponse.getFieldValuesCount()); - - final ServingAPIProto.GetOnlineFeaturesResponse.FieldValues fieldValue = - featureResponse.getFieldValues(0); - for (final String key : - ImmutableList.of("driver_hourly_stats:avg_daily_trips", "driver_hourly_stats:conv_rate")) { - assertTrue(fieldValue.containsFields(key)); - assertTrue(fieldValue.containsStatuses(key)); - assertEquals( - ServingAPIProto.GetOnlineFeaturesResponse.FieldStatus.NOT_FOUND, - fieldValue.getStatusesOrThrow(key)); - } - } - - @Test - public void shouldRefreshRegistryAndServeNewFeatures() throws InterruptedException { - updateRegistryFile( - registryProto - .toBuilder() - .addFeatureViews( - FeatureViewProto.FeatureView.newBuilder() - .setSpec( - FeatureViewProto.FeatureViewSpec.newBuilder() - .setName("new_view") - .addEntities("driver_id") - .addFeatures( - FeatureProto.FeatureSpecV2.newBuilder() - .setName("new_feature") - .setValueType(ValueProto.ValueType.Enum.BOOL)))) - .build()); - - ServingAPIProto.GetOnlineFeaturesRequestV2 request = - buildOnlineRequest(1005) - .toBuilder() - .addFeatures(DataGenerator.createFeatureReference("new_view", "new_feature")) - .build(); - - await() - .ignoreException(StatusRuntimeException.class) - .atMost(5, TimeUnit.SECONDS) - .until(() -> servingStub.getOnlineFeaturesV2(request).getFieldValuesCount(), equalTo(1)); - } - - abstract ApplicationProperties.FeastProperties createFeastProperties(); - - AbstractModule registryConfig() { - return null; - } - - abstract void updateRegistryFile(RegistryProto.Registry registry); -} diff --git a/java/serving/src/test/java/feast/serving/it/ServingBaseTests.java b/java/serving/src/test/java/feast/serving/it/ServingBaseTests.java new file mode 100644 index 0000000000..4d4272324e --- /dev/null +++ b/java/serving/src/test/java/feast/serving/it/ServingBaseTests.java @@ -0,0 +1,161 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2021 The Feast Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package feast.serving.it; + +import static org.awaitility.Awaitility.await; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.jupiter.api.Assertions.*; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import feast.proto.core.FeatureProto; +import feast.proto.core.FeatureViewProto; +import feast.proto.core.RegistryProto; +import feast.proto.serving.ServingAPIProto; +import feast.proto.serving.ServingAPIProto.FieldStatus; +import feast.proto.types.ValueProto; +import feast.serving.util.DataGenerator; +import io.grpc.StatusRuntimeException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.*; + +abstract class ServingBaseTests extends ServingEnvironment { + + protected ServingAPIProto.GetOnlineFeaturesRequest buildOnlineRequest(int driverId) { + // getOnlineFeatures Information + String entityName = "driver_id"; + + // Instantiate EntityRows + Map entityRows = + ImmutableMap.of( + entityName, + ValueProto.RepeatedValue.newBuilder() + .addVal(DataGenerator.createInt64Value(driverId)) + .build()); + + ImmutableList featureReferences = + ImmutableList.of("driver_hourly_stats:conv_rate", "driver_hourly_stats:avg_daily_trips"); + + // Build GetOnlineFeaturesRequestV2 + return TestUtils.createOnlineFeatureRequest(featureReferences, entityRows); + } + + static RegistryProto.Registry registryProto = readLocalRegistry(); + + private static RegistryProto.Registry readLocalRegistry() { + try { + return RegistryProto.Registry.parseFrom( + Files.readAllBytes(Paths.get("src/test/resources/docker-compose/feast10/registry.db"))); + } catch (IOException e) { + e.printStackTrace(); + } + + return null; + } + + @Test + public void shouldGetOnlineFeatures() { + ServingAPIProto.GetOnlineFeaturesResponseV2 featureResponse = + servingStub.getOnlineFeatures(buildOnlineRequest(1005)); + + assertEquals(2, featureResponse.getResultsCount()); + assertEquals(1, featureResponse.getResults(0).getValuesCount()); + + assertEquals( + ImmutableList.of("driver_hourly_stats:conv_rate", "driver_hourly_stats:avg_daily_trips"), + featureResponse.getMetadata().getFeatureNames().getValList()); + + for (int featureIdx : List.of(0, 1)) { + assertEquals( + List.of(ServingAPIProto.FieldStatus.PRESENT), + featureResponse.getResults(featureIdx).getStatusesList()); + } + + assertEquals(0.5, featureResponse.getResults(0).getValues(0).getDoubleVal(), 0.0001); + assertEquals(500, featureResponse.getResults(1).getValues(0).getInt64Val()); + } + + @Test + public void shouldGetOnlineFeaturesWithOutsideMaxAgeStatus() { + ServingAPIProto.GetOnlineFeaturesResponseV2 featureResponse = + servingStub.getOnlineFeatures(buildOnlineRequest(1001)); + + assertEquals(2, featureResponse.getResultsCount()); + assertEquals(1, featureResponse.getResults(0).getValuesCount()); + + for (int featureIdx : List.of(0, 1)) { + assertEquals( + FieldStatus.OUTSIDE_MAX_AGE, featureResponse.getResults(featureIdx).getStatuses(0)); + } + + assertEquals(0.1, featureResponse.getResults(0).getValues(0).getDoubleVal(), 0.0001); + assertEquals(100, featureResponse.getResults(1).getValues(0).getInt64Val()); + } + + @Test + public void shouldGetOnlineFeaturesWithNotFoundStatus() { + ServingAPIProto.GetOnlineFeaturesResponseV2 featureResponse = + servingStub.getOnlineFeatures(buildOnlineRequest(-1)); + + assertEquals(2, featureResponse.getResultsCount()); + assertEquals(1, featureResponse.getResults(0).getValuesCount()); + + for (final int featureIdx : List.of(0, 1)) { + assertEquals(FieldStatus.NOT_FOUND, featureResponse.getResults(featureIdx).getStatuses(0)); + } + } + + @Test + public void shouldRefreshRegistryAndServeNewFeatures() throws InterruptedException { + updateRegistryFile( + registryProto + .toBuilder() + .addFeatureViews( + FeatureViewProto.FeatureView.newBuilder() + .setSpec( + FeatureViewProto.FeatureViewSpec.newBuilder() + .setName("new_view") + .addEntities("driver_id") + .addFeatures( + FeatureProto.FeatureSpecV2.newBuilder() + .setName("new_feature") + .setValueType(ValueProto.ValueType.Enum.BOOL)))) + .build()); + + ServingAPIProto.GetOnlineFeaturesRequest request = buildOnlineRequest(1005); + + ServingAPIProto.GetOnlineFeaturesRequest requestWithNewFeature = + request + .toBuilder() + .setFeatures(request.getFeatures().toBuilder().addVal("new_view:new_feature")) + .build(); + + await() + .ignoreException(StatusRuntimeException.class) + .atMost(5, TimeUnit.SECONDS) + .until( + () -> servingStub.getOnlineFeatures(requestWithNewFeature).getResultsCount(), + equalTo(3)); + } + + abstract void updateRegistryFile(RegistryProto.Registry registry); +} diff --git a/java/serving/src/test/java/feast/serving/it/ServingBenchmarkIT.java b/java/serving/src/test/java/feast/serving/it/ServingBenchmarkIT.java new file mode 100644 index 0000000000..43eed2fa33 --- /dev/null +++ b/java/serving/src/test/java/feast/serving/it/ServingBenchmarkIT.java @@ -0,0 +1,151 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2021 The Feast Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package feast.serving.it; + +import com.google.api.client.util.Lists; +import com.google.common.base.Stopwatch; +import com.google.common.collect.ImmutableMap; +import com.google.common.math.Quantiles; +import feast.proto.serving.ServingAPIProto; +import feast.proto.types.ValueProto; +import feast.serving.config.ApplicationProperties; +import feast.serving.util.DataGenerator; +import java.util.List; +import java.util.LongSummaryStatistics; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ServingBenchmarkIT extends ServingEnvironment { + private Random rand = new Random(); + public static final Logger log = LoggerFactory.getLogger(ServingBenchmarkIT.class); + + private static int WARM_UP_COUNT = 10; + + @Override + ApplicationProperties.FeastProperties createFeastProperties() { + return TestUtils.createBasicFeastProperties( + environment.getServiceHost("redis", 6379), environment.getServicePort("redis", 6379)); + } + + protected ServingAPIProto.GetOnlineFeaturesRequest buildOnlineRequest( + int rowsCount, int featuresCount) { + List entities = + IntStream.range(0, rowsCount) + .mapToObj(i -> DataGenerator.createInt64Value(rand.nextInt(1000))) + .collect(Collectors.toList()); + + List featureReferences = + IntStream.range(0, featuresCount) + .mapToObj(i -> String.format("feature_view_%d:feature_%d", i / 10, i)) + .collect(Collectors.toList()); + + Map entityRows = + ImmutableMap.of( + "entity", ValueProto.RepeatedValue.newBuilder().addAllVal(entities).build()); + + return TestUtils.createOnlineFeatureRequest(featureReferences, entityRows); + } + + protected ServingAPIProto.GetOnlineFeaturesRequest buildOnlineRequest(int rowsCount) { + List entities = + IntStream.range(0, rowsCount) + .mapToObj(i -> DataGenerator.createInt64Value(rand.nextInt(1000))) + .collect(Collectors.toList()); + + Map entityRows = + ImmutableMap.of( + "entity", ValueProto.RepeatedValue.newBuilder().addAllVal(entities).build()); + + return TestUtils.createOnlineFeatureRequest("benchmark_feature_service", entityRows); + } + + @Test + public void benchmarkServing100rows10features() { + ServingAPIProto.GetOnlineFeaturesRequest req = buildOnlineRequest(100, 10); + + measure( + () -> servingStub.withDeadlineAfter(1, TimeUnit.SECONDS).getOnlineFeatures(req), + "100 rows; 10 features", + 1000); + } + + @Test + public void benchmarkServing100rows50features() { + ServingAPIProto.GetOnlineFeaturesRequest req = buildOnlineRequest(100, 50); + + measure( + () -> servingStub.withDeadlineAfter(1, TimeUnit.SECONDS).getOnlineFeatures(req), + "100 rows; 50 features", + 1000); + } + + @Test + public void benchmarkServing100rows100features() { + ServingAPIProto.GetOnlineFeaturesRequest req = buildOnlineRequest(100, 100); + + measure( + () -> servingStub.withDeadlineAfter(1, TimeUnit.SECONDS).getOnlineFeatures(req), + "100 rows; 100 features", + 1000); + } + + @Test + public void benchmarkServing100rowsFullFeatureService() { + ServingAPIProto.GetOnlineFeaturesRequest req = buildOnlineRequest(100); + + measure( + () -> servingStub.withDeadlineAfter(1, TimeUnit.SECONDS).getOnlineFeatures(req), + "100 rows; Full FS", + 1000); + } + + private void measure(Runnable target, String name, int runs) { + Stopwatch timer = Stopwatch.createUnstarted(); + + List records = Lists.newArrayList(); + + for (int i = 0; i < runs; i++) { + timer.reset(); + timer.start(); + target.run(); + timer.stop(); + if (i >= WARM_UP_COUNT) { + records.add(timer.elapsed(TimeUnit.MILLISECONDS)); + } + } + + LongSummaryStatistics summary = + records.stream().collect(Collectors.summarizingLong(Long::longValue)); + + log.info(String.format("Test %s took (min): %d ms", name, summary.getMin())); + log.info(String.format("Test %s took (avg): %f ms", name, summary.getAverage())); + log.info( + String.format("Test %s took (median): %f ms", name, Quantiles.median().compute(records))); + log.info( + String.format( + "Test %s took (95p): %f ms", name, Quantiles.percentiles().index(95).compute(records))); + log.info( + String.format( + "Test %s took (99p): %f ms", name, Quantiles.percentiles().index(99).compute(records))); + } +} diff --git a/java/serving/src/test/java/feast/serving/it/ServingEnvironment.java b/java/serving/src/test/java/feast/serving/it/ServingEnvironment.java new file mode 100644 index 0000000000..0c622d7c42 --- /dev/null +++ b/java/serving/src/test/java/feast/serving/it/ServingEnvironment.java @@ -0,0 +1,159 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2018-2021 The Feast Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package feast.serving.it; + +import com.google.inject.*; +import com.google.inject.Module; +import com.google.inject.util.Modules; +import feast.proto.serving.ServingServiceGrpc; +import feast.serving.config.ApplicationProperties; +import feast.serving.config.InstrumentationConfig; +import feast.serving.config.RegistryConfig; +import feast.serving.config.ServingServiceConfigV2; +import feast.serving.grpc.OnlineServingGrpcServiceV2; +import io.grpc.ManagedChannel; +import io.grpc.Server; +import io.grpc.inprocess.InProcessChannelBuilder; +import io.grpc.inprocess.InProcessServerBuilder; +import io.grpc.protobuf.services.ProtoReflectionService; +import io.grpc.util.MutableHandlerRegistry; +import java.io.File; +import java.time.Duration; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.testcontainers.containers.DockerComposeContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Testcontainers +abstract class ServingEnvironment { + static DockerComposeContainer environment; + + ServingServiceGrpc.ServingServiceBlockingStub servingStub; + Injector injector; + String serverName; + ManagedChannel channel; + Server server; + MutableHandlerRegistry serviceRegistry; + + @BeforeAll + static void globalSetup() { + environment = + new DockerComposeContainer( + new File("src/test/resources/docker-compose/docker-compose-redis-it.yml")) + .withExposedService("redis", 6379) + .withOptions() + .waitingFor( + "materialize", + Wait.forLogMessage(".*Materialization finished.*\\n", 1) + .withStartupTimeout(Duration.ofMinutes(5))); + environment.start(); + } + + @AfterAll + static void globalTeardown() { + environment.stop(); + } + + @BeforeEach + public void envSetUp() throws Exception { + + AbstractModule appPropertiesModule = + new AbstractModule() { + @Override + protected void configure() { + bind(OnlineServingGrpcServiceV2.class); + } + + @Provides + ApplicationProperties applicationProperties() { + final ApplicationProperties p = new ApplicationProperties(); + p.setAwsRegion("us-east-1"); + + final ApplicationProperties.FeastProperties feastProperties = createFeastProperties(); + p.setFeast(feastProperties); + + final ApplicationProperties.TracingProperties tracingProperties = + new ApplicationProperties.TracingProperties(); + feastProperties.setTracing(tracingProperties); + + tracingProperties.setEnabled(false); + return p; + } + }; + + Module overrideConfig = registryConfig(); + Module registryConfig; + if (overrideConfig != null) { + registryConfig = Modules.override(new RegistryConfig()).with(registryConfig()); + } else { + registryConfig = new RegistryConfig(); + } + + injector = + Guice.createInjector( + new ServingServiceConfigV2(), + registryConfig, + new InstrumentationConfig(), + appPropertiesModule); + + OnlineServingGrpcServiceV2 onlineServingGrpcServiceV2 = + injector.getInstance(OnlineServingGrpcServiceV2.class); + + serverName = InProcessServerBuilder.generateName(); + + server = + InProcessServerBuilder.forName(serverName) + .fallbackHandlerRegistry(serviceRegistry) + .addService(onlineServingGrpcServiceV2) + .addService(ProtoReflectionService.newInstance()) + .build(); + server.start(); + + channel = InProcessChannelBuilder.forName(serverName).usePlaintext().directExecutor().build(); + + servingStub = + ServingServiceGrpc.newBlockingStub(channel) + .withDeadlineAfter(5, TimeUnit.SECONDS) + .withWaitForReady(); + } + + @AfterEach + public void envTeardown() throws Exception { + // assume channel and server are not null + channel.shutdown(); + server.shutdown(); + // fail the test if cannot gracefully shutdown + try { + assert channel.awaitTermination(5, TimeUnit.SECONDS) + : "channel cannot be gracefully shutdown"; + assert server.awaitTermination(5, TimeUnit.SECONDS) : "server cannot be gracefully shutdown"; + } finally { + channel.shutdownNow(); + server.shutdownNow(); + } + } + + abstract ApplicationProperties.FeastProperties createFeastProperties(); + + AbstractModule registryConfig() { + return null; + } +} diff --git a/java/serving/src/test/java/feast/serving/it/ServingRedisGSRegistryIT.java b/java/serving/src/test/java/feast/serving/it/ServingRedisGSRegistryIT.java index 36e0eebe8d..78871cd45c 100644 --- a/java/serving/src/test/java/feast/serving/it/ServingRedisGSRegistryIT.java +++ b/java/serving/src/test/java/feast/serving/it/ServingRedisGSRegistryIT.java @@ -20,8 +20,6 @@ import com.google.cloud.storage.*; import com.google.cloud.storage.testing.RemoteStorageHelper; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import feast.proto.core.RegistryProto; import feast.serving.config.ApplicationProperties; import java.util.concurrent.ExecutionException; @@ -29,7 +27,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; -public class ServingRedisGSRegistryIT extends ServingBase { +public class ServingRedisGSRegistryIT extends ServingBaseTests { static Storage storage = RemoteStorageHelper.create() .getOptions() @@ -64,16 +62,9 @@ static void tearDown() throws ExecutionException, InterruptedException { @Override ApplicationProperties.FeastProperties createFeastProperties() { final ApplicationProperties.FeastProperties feastProperties = - new ApplicationProperties.FeastProperties(); + TestUtils.createBasicFeastProperties( + environment.getServiceHost("redis", 6379), environment.getServicePort("redis", 6379)); feastProperties.setRegistry(blobId.toGsUtilUri()); - feastProperties.setRegistryRefreshInterval(1); - - feastProperties.setActiveStore("online"); - - feastProperties.setStores( - ImmutableList.of( - new ApplicationProperties.Store( - "online", "REDIS", ImmutableMap.of("host", "localhost", "port", "6379")))); return feastProperties; } diff --git a/java/serving/src/test/java/feast/serving/it/ServingRedisLocalRegistryIT.java b/java/serving/src/test/java/feast/serving/it/ServingRedisLocalRegistryIT.java index 53fda39466..c83d8dbbf1 100644 --- a/java/serving/src/test/java/feast/serving/it/ServingRedisLocalRegistryIT.java +++ b/java/serving/src/test/java/feast/serving/it/ServingRedisLocalRegistryIT.java @@ -16,27 +16,14 @@ */ package feast.serving.it; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import feast.proto.core.RegistryProto; import feast.serving.config.ApplicationProperties; -public class ServingRedisLocalRegistryIT extends ServingBase { +public class ServingRedisLocalRegistryIT extends ServingBaseTests { @Override ApplicationProperties.FeastProperties createFeastProperties() { - final ApplicationProperties.FeastProperties feastProperties = - new ApplicationProperties.FeastProperties(); - feastProperties.setRegistry("src/test/resources/docker-compose/feast10/registry.db"); - feastProperties.setRegistryRefreshInterval(1); - - feastProperties.setActiveStore("online"); - - feastProperties.setStores( - ImmutableList.of( - new ApplicationProperties.Store( - "online", "REDIS", ImmutableMap.of("host", "localhost", "port", "6379")))); - - return feastProperties; + return TestUtils.createBasicFeastProperties( + environment.getServiceHost("redis", 6379), environment.getServicePort("redis", 6379)); } @Override diff --git a/java/serving/src/test/java/feast/serving/it/ServingRedisS3RegistryIT.java b/java/serving/src/test/java/feast/serving/it/ServingRedisS3RegistryIT.java index 648fdaa5b5..d67fbf2621 100644 --- a/java/serving/src/test/java/feast/serving/it/ServingRedisS3RegistryIT.java +++ b/java/serving/src/test/java/feast/serving/it/ServingRedisS3RegistryIT.java @@ -21,8 +21,6 @@ import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3ClientBuilder; import com.amazonaws.services.s3.model.ObjectMetadata; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import com.google.inject.AbstractModule; import com.google.inject.Provides; import feast.proto.core.RegistryProto; @@ -31,7 +29,7 @@ import org.junit.jupiter.api.BeforeAll; import org.testcontainers.junit.jupiter.Container; -public class ServingRedisS3RegistryIT extends ServingBase { +public class ServingRedisS3RegistryIT extends ServingBaseTests { @Container static final S3MockContainer s3Mock = new S3MockContainer("2.2.3"); private static AmazonS3 createClient() { @@ -64,16 +62,9 @@ static void setUp() { @Override ApplicationProperties.FeastProperties createFeastProperties() { final ApplicationProperties.FeastProperties feastProperties = - new ApplicationProperties.FeastProperties(); + TestUtils.createBasicFeastProperties( + environment.getServiceHost("redis", 6379), environment.getServicePort("redis", 6379)); feastProperties.setRegistry("s3://test-bucket/registry.db"); - feastProperties.setRegistryRefreshInterval(1); - - feastProperties.setActiveStore("online"); - - feastProperties.setStores( - ImmutableList.of( - new ApplicationProperties.Store( - "online", "REDIS", ImmutableMap.of("host", "localhost", "port", "6379")))); return feastProperties; } diff --git a/java/serving/src/test/java/feast/serving/it/TestUtils.java b/java/serving/src/test/java/feast/serving/it/TestUtils.java index fb88b2fb37..71d15e4a89 100644 --- a/java/serving/src/test/java/feast/serving/it/TestUtils.java +++ b/java/serving/src/test/java/feast/serving/it/TestUtils.java @@ -16,9 +16,13 @@ */ package feast.serving.it; -import feast.proto.serving.ServingAPIProto.FeatureReferenceV2; -import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequestV2; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import feast.proto.serving.ServingAPIProto; +import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequest; import feast.proto.serving.ServingServiceGrpc; +import feast.proto.types.ValueProto; +import feast.serving.config.ApplicationProperties; import io.grpc.Channel; import io.grpc.ManagedChannelBuilder; import java.util.*; @@ -32,14 +36,39 @@ public static ServingServiceGrpc.ServingServiceBlockingStub getServingServiceStu return ServingServiceGrpc.newBlockingStub(secureChannel); } - public static GetOnlineFeaturesRequestV2 createOnlineFeatureRequest( - String projectName, - List featureReferences, - List entityRows) { - return GetOnlineFeaturesRequestV2.newBuilder() - .setProject(projectName) - .addAllFeatures(featureReferences) - .addAllEntityRows(entityRows) + public static GetOnlineFeaturesRequest createOnlineFeatureRequest( + List featureReferences, Map entityRows) { + return GetOnlineFeaturesRequest.newBuilder() + .setFeatures(ServingAPIProto.FeatureList.newBuilder().addAllVal(featureReferences)) + .putAllEntities(entityRows) .build(); } + + public static GetOnlineFeaturesRequest createOnlineFeatureRequest( + String featureService, Map entityRows) { + return GetOnlineFeaturesRequest.newBuilder() + .setFeatureService(featureService) + .putAllEntities(entityRows) + .build(); + } + + public static ApplicationProperties.FeastProperties createBasicFeastProperties( + String redisHost, Integer redisPort) { + final ApplicationProperties.FeastProperties feastProperties = + new ApplicationProperties.FeastProperties(); + feastProperties.setRegistry("src/test/resources/docker-compose/feast10/registry.db"); + feastProperties.setRegistryRefreshInterval(1); + + feastProperties.setActiveStore("online"); + feastProperties.setProject("feast_project"); + + feastProperties.setStores( + ImmutableList.of( + new ApplicationProperties.Store( + "online", + "REDIS", + ImmutableMap.of("host", redisHost, "port", redisPort.toString())))); + + return feastProperties; + } } diff --git a/java/serving/src/test/java/feast/serving/service/OnlineServingServiceTest.java b/java/serving/src/test/java/feast/serving/service/OnlineServingServiceTest.java index c43e3218c7..4234e9dce3 100644 --- a/java/serving/src/test/java/feast/serving/service/OnlineServingServiceTest.java +++ b/java/serving/src/test/java/feast/serving/service/OnlineServingServiceTest.java @@ -29,10 +29,8 @@ import feast.proto.core.FeatureProto; import feast.proto.core.FeatureViewProto; import feast.proto.serving.ServingAPIProto; -import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequestV2; -import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesResponse; -import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesResponse.FieldStatus; -import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesResponse.FieldValues; +import feast.proto.serving.ServingAPIProto.FieldStatus; +import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesResponseV2; import feast.proto.types.ValueProto; import feast.serving.registry.Registry; import feast.serving.registry.RegistryRepository; @@ -42,8 +40,9 @@ import io.opentracing.Tracer; import io.opentracing.Tracer.SpanBuilder; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; -import java.util.Map; +import java.util.stream.Collectors; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentMatchers; @@ -62,6 +61,8 @@ public class OnlineServingServiceTest { List mockedFeatureRows; List featureSpecs; + Timestamp now = Timestamp.newBuilder().setSeconds(System.currentTimeMillis() / 1000).build(); + @Before public void setUp() { initMocks(this); @@ -71,56 +72,57 @@ public void setUp() { OnlineTransformationService onlineTransformationService = new OnlineTransformationService(transformationServiceEndpoint, registryRepo); onlineServingServiceV2 = - new OnlineServingServiceV2(retrieverV2, tracer, registryRepo, onlineTransformationService); + new OnlineServingServiceV2( + retrieverV2, tracer, registryRepo, onlineTransformationService, "feast_project"); mockedFeatureRows = new ArrayList<>(); mockedFeatureRows.add( new ProtoFeature( ServingAPIProto.FeatureReferenceV2.newBuilder() - .setFeatureTable("featuretable_1") - .setName("feature_1") + .setFeatureViewName("featureview_1") + .setFeatureName("feature_1") .build(), - Timestamp.newBuilder().setSeconds(100).build(), + now, createStrValue("1"))); mockedFeatureRows.add( new ProtoFeature( ServingAPIProto.FeatureReferenceV2.newBuilder() - .setFeatureTable("featuretable_1") - .setName("feature_2") + .setFeatureViewName("featureview_1") + .setFeatureName("feature_2") .build(), - Timestamp.newBuilder().setSeconds(100).build(), + now, createStrValue("2"))); mockedFeatureRows.add( new ProtoFeature( ServingAPIProto.FeatureReferenceV2.newBuilder() - .setFeatureTable("featuretable_1") - .setName("feature_1") + .setFeatureViewName("featureview_1") + .setFeatureName("feature_1") .build(), - Timestamp.newBuilder().setSeconds(100).build(), + now, createStrValue("3"))); mockedFeatureRows.add( new ProtoFeature( ServingAPIProto.FeatureReferenceV2.newBuilder() - .setFeatureTable("featuretable_1") - .setName("feature_2") + .setFeatureViewName("featureview_1") + .setFeatureName("feature_2") .build(), - Timestamp.newBuilder().setSeconds(100).build(), + now, createStrValue("4"))); mockedFeatureRows.add( new ProtoFeature( ServingAPIProto.FeatureReferenceV2.newBuilder() - .setFeatureTable("featuretable_1") - .setName("feature_3") + .setFeatureViewName("featureview_1") + .setFeatureName("feature_3") .build(), - Timestamp.newBuilder().setSeconds(100).build(), + now, createStrValue("5"))); mockedFeatureRows.add( new ProtoFeature( ServingAPIProto.FeatureReferenceV2.newBuilder() - .setFeatureTable("featuretable_1") - .setName("feature_1") + .setFeatureViewName("featureview_1") + .setFeatureName("feature_1") .build(), - Timestamp.newBuilder().setSeconds(50).build(), + Timestamp.newBuilder().setSeconds(1).build(), createStrValue("6"))); featureSpecs = new ArrayList<>(); @@ -141,66 +143,63 @@ public void shouldReturnResponseWithValuesAndMetadataIfKeysPresent() { String projectName = "default"; ServingAPIProto.FeatureReferenceV2 featureReference1 = ServingAPIProto.FeatureReferenceV2.newBuilder() - .setFeatureTable("featuretable_1") - .setName("feature_1") + .setFeatureViewName("featureview_1") + .setFeatureName("feature_1") .build(); ServingAPIProto.FeatureReferenceV2 featureReference2 = ServingAPIProto.FeatureReferenceV2.newBuilder() - .setFeatureTable("featuretable_1") - .setName("feature_2") + .setFeatureViewName("featureview_1") + .setFeatureName("feature_2") .build(); List featureReferences = List.of(featureReference1, featureReference2); - GetOnlineFeaturesRequestV2 request = getOnlineFeaturesRequestV2(projectName, featureReferences); + ServingAPIProto.GetOnlineFeaturesRequest request = getOnlineFeaturesRequest(featureReferences); - List> featureRows = + List> featureRows = List.of( - ImmutableMap.of( - mockedFeatureRows.get(0).getFeatureReference(), mockedFeatureRows.get(0), - mockedFeatureRows.get(1).getFeatureReference(), mockedFeatureRows.get(1)), - ImmutableMap.of( - mockedFeatureRows.get(2).getFeatureReference(), mockedFeatureRows.get(2), - mockedFeatureRows.get(3).getFeatureReference(), mockedFeatureRows.get(3))); + List.of(mockedFeatureRows.get(0), mockedFeatureRows.get(1)), + List.of(mockedFeatureRows.get(2), mockedFeatureRows.get(3))); - when(retrieverV2.getOnlineFeatures(any(), any(), any(), any())).thenReturn(featureRows); - when(registry.getFeatureViewSpec(any(), any())).thenReturn(getFeatureViewSpec()); - when(registry.getFeatureSpec(projectName, mockedFeatureRows.get(0).getFeatureReference())) + when(retrieverV2.getOnlineFeatures(any(), any(), any())).thenReturn(featureRows); + when(registry.getFeatureViewSpec(any())).thenReturn(getFeatureViewSpec()); + when(registry.getFeatureSpec(mockedFeatureRows.get(0).getFeatureReference())) .thenReturn(featureSpecs.get(0)); - when(registry.getFeatureSpec(projectName, mockedFeatureRows.get(1).getFeatureReference())) + when(registry.getFeatureSpec(mockedFeatureRows.get(1).getFeatureReference())) .thenReturn(featureSpecs.get(1)); - when(registry.getFeatureSpec(projectName, mockedFeatureRows.get(2).getFeatureReference())) + when(registry.getFeatureSpec(mockedFeatureRows.get(2).getFeatureReference())) .thenReturn(featureSpecs.get(0)); - when(registry.getFeatureSpec(projectName, mockedFeatureRows.get(3).getFeatureReference())) + when(registry.getFeatureSpec(mockedFeatureRows.get(3).getFeatureReference())) .thenReturn(featureSpecs.get(1)); when(tracer.buildSpan(ArgumentMatchers.any())).thenReturn(Mockito.mock(SpanBuilder.class)); - GetOnlineFeaturesResponse expected = - GetOnlineFeaturesResponse.newBuilder() - .addFieldValues( - FieldValues.newBuilder() - .putFields("entity1", createInt64Value(1)) - .putStatuses("entity1", FieldStatus.PRESENT) - .putFields("entity2", createStrValue("a")) - .putStatuses("entity2", FieldStatus.PRESENT) - .putFields("featuretable_1:feature_1", createStrValue("1")) - .putStatuses("featuretable_1:feature_1", FieldStatus.PRESENT) - .putFields("featuretable_1:feature_2", createStrValue("2")) - .putStatuses("featuretable_1:feature_2", FieldStatus.PRESENT) - .build()) - .addFieldValues( - FieldValues.newBuilder() - .putFields("entity1", createInt64Value(2)) - .putStatuses("entity1", FieldStatus.PRESENT) - .putFields("entity2", createStrValue("b")) - .putStatuses("entity2", FieldStatus.PRESENT) - .putFields("featuretable_1:feature_1", createStrValue("3")) - .putStatuses("featuretable_1:feature_1", FieldStatus.PRESENT) - .putFields("featuretable_1:feature_2", createStrValue("4")) - .putStatuses("featuretable_1:feature_2", FieldStatus.PRESENT) - .build()) + GetOnlineFeaturesResponseV2 expected = + GetOnlineFeaturesResponseV2.newBuilder() + .addResults( + GetOnlineFeaturesResponseV2.FeatureVector.newBuilder() + .addValues(createStrValue("1")) + .addValues(createStrValue("3")) + .addStatuses(FieldStatus.PRESENT) + .addStatuses(FieldStatus.PRESENT) + .addEventTimestamps(now) + .addEventTimestamps(now)) + .addResults( + GetOnlineFeaturesResponseV2.FeatureVector.newBuilder() + .addValues(createStrValue("2")) + .addValues(createStrValue("4")) + .addStatuses(FieldStatus.PRESENT) + .addStatuses(FieldStatus.PRESENT) + .addEventTimestamps(now) + .addEventTimestamps(now)) + .setMetadata( + ServingAPIProto.GetOnlineFeaturesResponseMetadata.newBuilder() + .setFeatureNames( + ServingAPIProto.FeatureList.newBuilder() + .addVal("featureview_1:feature_1") + .addVal("featureview_1:feature_2"))) .build(); - GetOnlineFeaturesResponse actual = onlineServingServiceV2.getOnlineFeatures(request); + ServingAPIProto.GetOnlineFeaturesResponseV2 actual = + onlineServingServiceV2.getOnlineFeatures(request); assertThat(actual, equalTo(expected)); } @@ -209,17 +208,17 @@ public void shouldReturnResponseWithUnsetValuesAndMetadataIfKeysNotPresent() { String projectName = "default"; ServingAPIProto.FeatureReferenceV2 featureReference1 = ServingAPIProto.FeatureReferenceV2.newBuilder() - .setFeatureTable("featuretable_1") - .setName("feature_1") + .setFeatureViewName("featureview_1") + .setFeatureName("feature_1") .build(); ServingAPIProto.FeatureReferenceV2 featureReference2 = ServingAPIProto.FeatureReferenceV2.newBuilder() - .setFeatureTable("featuretable_1") - .setName("feature_2") + .setFeatureViewName("featureview_1") + .setFeatureName("feature_2") .build(); List featureReferences = List.of(featureReference1, featureReference2); - GetOnlineFeaturesRequestV2 request = getOnlineFeaturesRequestV2(projectName, featureReferences); + ServingAPIProto.GetOnlineFeaturesRequest request = getOnlineFeaturesRequest(featureReferences); List entityKeyList1 = new ArrayList<>(); List entityKeyList2 = new ArrayList<>(); @@ -227,49 +226,46 @@ public void shouldReturnResponseWithUnsetValuesAndMetadataIfKeysNotPresent() { entityKeyList1.add(mockedFeatureRows.get(1)); entityKeyList2.add(mockedFeatureRows.get(4)); - List> featureRows = + List> featureRows = List.of( - ImmutableMap.of( - mockedFeatureRows.get(0).getFeatureReference(), mockedFeatureRows.get(0), - mockedFeatureRows.get(1).getFeatureReference(), mockedFeatureRows.get(1)), - ImmutableMap.of( - mockedFeatureRows.get(4).getFeatureReference(), mockedFeatureRows.get(4))); + List.of(mockedFeatureRows.get(0), mockedFeatureRows.get(1)), + Arrays.asList(null, mockedFeatureRows.get(4))); - when(retrieverV2.getOnlineFeatures(any(), any(), any(), any())).thenReturn(featureRows); - when(registry.getFeatureViewSpec(any(), any())).thenReturn(getFeatureViewSpec()); - when(registry.getFeatureSpec(projectName, mockedFeatureRows.get(0).getFeatureReference())) + when(retrieverV2.getOnlineFeatures(any(), any(), any())).thenReturn(featureRows); + when(registry.getFeatureViewSpec(any())).thenReturn(getFeatureViewSpec()); + when(registry.getFeatureSpec(mockedFeatureRows.get(0).getFeatureReference())) .thenReturn(featureSpecs.get(0)); - when(registry.getFeatureSpec(projectName, mockedFeatureRows.get(1).getFeatureReference())) + when(registry.getFeatureSpec(mockedFeatureRows.get(1).getFeatureReference())) .thenReturn(featureSpecs.get(1)); when(tracer.buildSpan(ArgumentMatchers.any())).thenReturn(Mockito.mock(SpanBuilder.class)); - GetOnlineFeaturesResponse expected = - GetOnlineFeaturesResponse.newBuilder() - .addFieldValues( - FieldValues.newBuilder() - .putFields("entity1", createInt64Value(1)) - .putStatuses("entity1", FieldStatus.PRESENT) - .putFields("entity2", createStrValue("a")) - .putStatuses("entity2", FieldStatus.PRESENT) - .putFields("featuretable_1:feature_1", createStrValue("1")) - .putStatuses("featuretable_1:feature_1", FieldStatus.PRESENT) - .putFields("featuretable_1:feature_2", createStrValue("2")) - .putStatuses("featuretable_1:feature_2", FieldStatus.PRESENT) - .build()) - .addFieldValues( - FieldValues.newBuilder() - .putFields("entity1", createInt64Value(2)) - .putStatuses("entity1", FieldStatus.PRESENT) - .putFields("entity2", createStrValue("b")) - .putStatuses("entity2", FieldStatus.PRESENT) - .putFields("featuretable_1:feature_1", createEmptyValue()) - .putStatuses("featuretable_1:feature_1", FieldStatus.NOT_FOUND) - .putFields("featuretable_1:feature_2", createEmptyValue()) - .putStatuses("featuretable_1:feature_2", FieldStatus.NOT_FOUND) - .build()) + GetOnlineFeaturesResponseV2 expected = + GetOnlineFeaturesResponseV2.newBuilder() + .addResults( + GetOnlineFeaturesResponseV2.FeatureVector.newBuilder() + .addValues(createStrValue("1")) + .addValues(createEmptyValue()) + .addStatuses(FieldStatus.PRESENT) + .addStatuses(FieldStatus.NOT_FOUND) + .addEventTimestamps(now) + .addEventTimestamps(Timestamp.newBuilder().build())) + .addResults( + GetOnlineFeaturesResponseV2.FeatureVector.newBuilder() + .addValues(createStrValue("2")) + .addValues(createStrValue("5")) + .addStatuses(FieldStatus.PRESENT) + .addStatuses(FieldStatus.PRESENT) + .addEventTimestamps(now) + .addEventTimestamps(now)) + .setMetadata( + ServingAPIProto.GetOnlineFeaturesResponseMetadata.newBuilder() + .setFeatureNames( + ServingAPIProto.FeatureList.newBuilder() + .addVal("featureview_1:feature_1") + .addVal("featureview_1:feature_2"))) .build(); - GetOnlineFeaturesResponse actual = onlineServingServiceV2.getOnlineFeatures(request); + GetOnlineFeaturesResponseV2 actual = onlineServingServiceV2.getOnlineFeatures(request); assertThat(actual, equalTo(expected)); } @@ -278,32 +274,28 @@ public void shouldReturnResponseWithValuesAndMetadataIfMaxAgeIsExceeded() { String projectName = "default"; ServingAPIProto.FeatureReferenceV2 featureReference1 = ServingAPIProto.FeatureReferenceV2.newBuilder() - .setFeatureTable("featuretable_1") - .setName("feature_1") + .setFeatureViewName("featureview_1") + .setFeatureName("feature_1") .build(); ServingAPIProto.FeatureReferenceV2 featureReference2 = ServingAPIProto.FeatureReferenceV2.newBuilder() - .setFeatureTable("featuretable_1") - .setName("feature_2") + .setFeatureViewName("featureview_1") + .setFeatureName("feature_2") .build(); List featureReferences = List.of(featureReference1, featureReference2); - GetOnlineFeaturesRequestV2 request = getOnlineFeaturesRequestV2(projectName, featureReferences); + ServingAPIProto.GetOnlineFeaturesRequest request = getOnlineFeaturesRequest(featureReferences); - List> featureRows = + List> featureRows = List.of( - ImmutableMap.of( - mockedFeatureRows.get(5).getFeatureReference(), mockedFeatureRows.get(5), - mockedFeatureRows.get(1).getFeatureReference(), mockedFeatureRows.get(1)), - ImmutableMap.of( - mockedFeatureRows.get(5).getFeatureReference(), mockedFeatureRows.get(5), - mockedFeatureRows.get(1).getFeatureReference(), mockedFeatureRows.get(1))); + List.of(mockedFeatureRows.get(5), mockedFeatureRows.get(1)), + List.of(mockedFeatureRows.get(5), mockedFeatureRows.get(1))); - when(retrieverV2.getOnlineFeatures(any(), any(), any(), any())).thenReturn(featureRows); - when(registry.getFeatureViewSpec(any(), any())) + when(retrieverV2.getOnlineFeatures(any(), any(), any())).thenReturn(featureRows); + when(registry.getFeatureViewSpec(any())) .thenReturn( FeatureViewProto.FeatureViewSpec.newBuilder() - .setName("featuretable_1") + .setName("featureview_1") .addEntities("entity1") .addEntities("entity2") .addFeatures( @@ -316,47 +308,47 @@ public void shouldReturnResponseWithValuesAndMetadataIfMaxAgeIsExceeded() { .setName("feature_2") .setValueType(ValueProto.ValueType.Enum.STRING) .build()) - .setTtl(Duration.newBuilder().setSeconds(1)) + .setTtl(Duration.newBuilder().setSeconds(3600)) .build()); - when(registry.getFeatureSpec(projectName, mockedFeatureRows.get(1).getFeatureReference())) + when(registry.getFeatureSpec(mockedFeatureRows.get(1).getFeatureReference())) .thenReturn(featureSpecs.get(1)); - when(registry.getFeatureSpec(projectName, mockedFeatureRows.get(5).getFeatureReference())) + when(registry.getFeatureSpec(mockedFeatureRows.get(5).getFeatureReference())) .thenReturn(featureSpecs.get(0)); when(tracer.buildSpan(ArgumentMatchers.any())).thenReturn(Mockito.mock(SpanBuilder.class)); - GetOnlineFeaturesResponse expected = - GetOnlineFeaturesResponse.newBuilder() - .addFieldValues( - FieldValues.newBuilder() - .putFields("entity1", createInt64Value(1)) - .putStatuses("entity1", FieldStatus.PRESENT) - .putFields("entity2", createStrValue("a")) - .putStatuses("entity2", FieldStatus.PRESENT) - .putFields("featuretable_1:feature_1", createStrValue("6")) - .putStatuses("featuretable_1:feature_1", FieldStatus.OUTSIDE_MAX_AGE) - .putFields("featuretable_1:feature_2", createStrValue("2")) - .putStatuses("featuretable_1:feature_2", FieldStatus.PRESENT) - .build()) - .addFieldValues( - FieldValues.newBuilder() - .putFields("entity1", createInt64Value(2)) - .putStatuses("entity1", FieldStatus.PRESENT) - .putFields("entity2", createStrValue("b")) - .putStatuses("entity2", FieldStatus.PRESENT) - .putFields("featuretable_1:feature_1", createStrValue("6")) - .putStatuses("featuretable_1:feature_1", FieldStatus.OUTSIDE_MAX_AGE) - .putFields("featuretable_1:feature_2", createStrValue("2")) - .putStatuses("featuretable_1:feature_2", FieldStatus.PRESENT) - .build()) + GetOnlineFeaturesResponseV2 expected = + GetOnlineFeaturesResponseV2.newBuilder() + .addResults( + GetOnlineFeaturesResponseV2.FeatureVector.newBuilder() + .addValues(createStrValue("6")) + .addValues(createStrValue("6")) + .addStatuses(FieldStatus.OUTSIDE_MAX_AGE) + .addStatuses(FieldStatus.OUTSIDE_MAX_AGE) + .addEventTimestamps(Timestamp.newBuilder().setSeconds(1).build()) + .addEventTimestamps(Timestamp.newBuilder().setSeconds(1).build())) + .addResults( + GetOnlineFeaturesResponseV2.FeatureVector.newBuilder() + .addValues(createStrValue("2")) + .addValues(createStrValue("2")) + .addStatuses(FieldStatus.PRESENT) + .addStatuses(FieldStatus.PRESENT) + .addEventTimestamps(now) + .addEventTimestamps(now)) + .setMetadata( + ServingAPIProto.GetOnlineFeaturesResponseMetadata.newBuilder() + .setFeatureNames( + ServingAPIProto.FeatureList.newBuilder() + .addVal("featureview_1:feature_1") + .addVal("featureview_1:feature_2"))) .build(); - GetOnlineFeaturesResponse actual = onlineServingServiceV2.getOnlineFeatures(request); + GetOnlineFeaturesResponseV2 actual = onlineServingServiceV2.getOnlineFeatures(request); assertThat(actual, equalTo(expected)); } private FeatureViewProto.FeatureViewSpec getFeatureViewSpec() { return FeatureViewProto.FeatureViewSpec.newBuilder() - .setName("featuretable_1") + .setName("featureview_1") .addEntities("entity1") .addEntities("entity2") .addFeatures( @@ -373,31 +365,26 @@ private FeatureViewProto.FeatureViewSpec getFeatureViewSpec() { .build(); } - private GetOnlineFeaturesRequestV2 getOnlineFeaturesRequestV2( - String projectName, List featureReferences) { - return GetOnlineFeaturesRequestV2.newBuilder() - .setProject(projectName) - .addAllFeatures(featureReferences) - .addEntityRows( - GetOnlineFeaturesRequestV2.EntityRow.newBuilder() - .setTimestamp(Timestamp.newBuilder().setSeconds(100)) - .putFields("entity1", createInt64Value(1)) - .putFields("entity2", createStrValue("a"))) - .addEntityRows( - GetOnlineFeaturesRequestV2.EntityRow.newBuilder() - .setTimestamp(Timestamp.newBuilder().setSeconds(100)) - .putFields("entity1", createInt64Value(2)) - .putFields("entity2", createStrValue("b"))) - .addFeatures( - ServingAPIProto.FeatureReferenceV2.newBuilder() - .setFeatureTable("featuretable_1") - .setName("feature_1") - .build()) - .addFeatures( - ServingAPIProto.FeatureReferenceV2.newBuilder() - .setFeatureTable("featuretable_1") - .setName("feature_2") + private ServingAPIProto.GetOnlineFeaturesRequest getOnlineFeaturesRequest( + List featureReferences) { + return ServingAPIProto.GetOnlineFeaturesRequest.newBuilder() + .setFeatures( + ServingAPIProto.FeatureList.newBuilder() + .addAllVal( + featureReferences.stream() + .map(feast.common.models.Feature::getFeatureReference) + .collect(Collectors.toList())) .build()) + .putAllEntities( + ImmutableMap.of( + "entity1", + ValueProto.RepeatedValue.newBuilder() + .addAllVal(List.of(createInt64Value(1), createInt64Value(2))) + .build(), + "entity2", + ValueProto.RepeatedValue.newBuilder() + .addAllVal(List.of(createStrValue("a"), createStrValue("b"))) + .build())) .build(); } } diff --git a/java/serving/src/test/java/feast/serving/util/DataGenerator.java b/java/serving/src/test/java/feast/serving/util/DataGenerator.java index ab537fa6f9..d53632d0d6 100644 --- a/java/serving/src/test/java/feast/serving/util/DataGenerator.java +++ b/java/serving/src/test/java/feast/serving/util/DataGenerator.java @@ -260,8 +260,8 @@ public static ValueProto.Value createInt64Value(long value) { public static ServingAPIProto.FeatureReferenceV2 createFeatureReference( String featureTableName, String featureName) { return ServingAPIProto.FeatureReferenceV2.newBuilder() - .setFeatureTable(featureTableName) - .setName(featureName) + .setFeatureViewName(featureTableName) + .setFeatureName(featureName) .build(); } diff --git a/java/serving/src/test/java/feast/serving/util/RequestHelperTest.java b/java/serving/src/test/java/feast/serving/util/RequestHelperTest.java index 140d46cd56..fc19dbb02e 100644 --- a/java/serving/src/test/java/feast/serving/util/RequestHelperTest.java +++ b/java/serving/src/test/java/feast/serving/util/RequestHelperTest.java @@ -16,39 +16,40 @@ */ package feast.serving.util; -import feast.proto.serving.ServingAPIProto.FeatureReferenceV2; -import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequestV2; +import feast.proto.serving.ServingAPIProto; import org.junit.Test; public class RequestHelperTest { @Test(expected = IllegalArgumentException.class) public void shouldErrorIfEntityRowEmpty() { - FeatureReferenceV2 featureReference = - FeatureReferenceV2.newBuilder() - .setFeatureTable("featuretablename") - .setName("featurename") + + ServingAPIProto.GetOnlineFeaturesRequest getOnlineFeaturesRequest = + ServingAPIProto.GetOnlineFeaturesRequest.newBuilder() + .setFeatures( + ServingAPIProto.FeatureList.newBuilder().addVal("view:featurename").build()) .build(); - GetOnlineFeaturesRequestV2 getOnlineFeaturesRequestV2 = - GetOnlineFeaturesRequestV2.newBuilder().addFeatures(featureReference).build(); - RequestHelper.validateOnlineRequest(getOnlineFeaturesRequestV2); + + RequestHelper.validateOnlineRequest(getOnlineFeaturesRequest); } @Test(expected = IllegalArgumentException.class) public void shouldErrorIfFeatureReferenceTableEmpty() { - FeatureReferenceV2 featureReference = - FeatureReferenceV2.newBuilder().setName("featurename").build(); - GetOnlineFeaturesRequestV2 getOnlineFeaturesRequestV2 = - GetOnlineFeaturesRequestV2.newBuilder().addFeatures(featureReference).build(); - RequestHelper.validateOnlineRequest(getOnlineFeaturesRequestV2); + ServingAPIProto.GetOnlineFeaturesRequest getOnlineFeaturesRequest = + ServingAPIProto.GetOnlineFeaturesRequest.newBuilder() + .setFeatures(ServingAPIProto.FeatureList.newBuilder().addVal("featurename").build()) + .build(); + + RequestHelper.validateOnlineRequest(getOnlineFeaturesRequest); } @Test(expected = IllegalArgumentException.class) public void shouldErrorIfFeatureReferenceNameEmpty() { - FeatureReferenceV2 featureReference = - FeatureReferenceV2.newBuilder().setFeatureTable("featuretablename").build(); - GetOnlineFeaturesRequestV2 getOnlineFeaturesRequestV2 = - GetOnlineFeaturesRequestV2.newBuilder().addFeatures(featureReference).build(); - RequestHelper.validateOnlineRequest(getOnlineFeaturesRequestV2); + ServingAPIProto.GetOnlineFeaturesRequest getOnlineFeaturesRequest = + ServingAPIProto.GetOnlineFeaturesRequest.newBuilder() + .setFeatures(ServingAPIProto.FeatureList.newBuilder().addVal("view").build()) + .build(); + + RequestHelper.validateOnlineRequest(getOnlineFeaturesRequest); } } diff --git a/java/serving/src/test/resources/docker-compose/docker-compose-redis-it.yml b/java/serving/src/test/resources/docker-compose/docker-compose-redis-it.yml index 08a50233df..22e054f8b1 100644 --- a/java/serving/src/test/resources/docker-compose/docker-compose-redis-it.yml +++ b/java/serving/src/test/resources/docker-compose/docker-compose-redis-it.yml @@ -2,7 +2,7 @@ version: '3' services: redis: - image: redis:5-alpine + image: redis:6.2 ports: - "6379:6379" materialize: diff --git a/java/serving/src/test/resources/docker-compose/feast10/materialize.py b/java/serving/src/test/resources/docker-compose/feast10/materialize.py index c347728c68..ca4cc98db2 100644 --- a/java/serving/src/test/resources/docker-compose/feast10/materialize.py +++ b/java/serving/src/test/resources/docker-compose/feast10/materialize.py @@ -56,8 +56,56 @@ tags={}, ) + +# For Benchmarks +# Please read more in Feast RFC-031 (link https://docs.google.com/document/d/12UuvTQnTTCJhdRgy6h10zSbInNGSyEJkIxpOcgOen1I/edit) +# about this benchmark setup +def generate_data(num_rows: int, num_features: int, key_space: int, destination: str) -> pd.DataFrame: + features = [f"feature_{i}" for i in range(num_features)] + columns = ["entity", "event_timestamp"] + features + df = pd.DataFrame(0, index=np.arange(num_rows), columns=columns) + df["event_timestamp"] = datetime.utcnow() + for column in ["entity"] + features: + df[column] = np.random.randint(1, key_space, num_rows) + + df.to_parquet(destination) + +generate_data(10**3, 250, 10**3, "benchmark_data.parquet") + +generated_data_source = FileSource( + path="benchmark_data.parquet", + event_timestamp_column="event_timestamp", +) + +entity = Entity( + name="entity", + value_type=ValueType.INT64, +) + +benchmark_feature_views = [ + FeatureView( + name=f"feature_view_{i}", + entities=["entity"], + ttl=Duration(seconds=86400), + features=[ + Feature(name=f"feature_{10 * i + j}", dtype=ValueType.INT64) + for j in range(10) + ], + online=True, + batch_source=generated_data_source, + ) + for i in range(25) +] + +benchmark_feature_service = FeatureService( + name=f"benchmark_feature_service", + features=benchmark_feature_views, +) + + fs = FeatureStore(".") -fs.apply([driver_hourly_stats_view, driver]) +fs.apply([driver_hourly_stats_view, driver, + entity, benchmark_feature_service, *benchmark_feature_views]) now = datetime.now() fs.materialize(start, now) diff --git a/java/serving/src/test/resources/docker-compose/feast10/registry.db b/java/serving/src/test/resources/docker-compose/feast10/registry.db index b9a19475af..4590c5800a 100644 Binary files a/java/serving/src/test/resources/docker-compose/feast10/registry.db and b/java/serving/src/test/resources/docker-compose/feast10/registry.db differ diff --git a/java/storage/api/src/main/java/feast/storage/api/retriever/FeatureTableRequest.java b/java/storage/api/src/main/java/feast/storage/api/retriever/FeatureTableRequest.java index 6188a270c4..2f181e6de8 100644 --- a/java/storage/api/src/main/java/feast/storage/api/retriever/FeatureTableRequest.java +++ b/java/storage/api/src/main/java/feast/storage/api/retriever/FeatureTableRequest.java @@ -56,6 +56,7 @@ public Builder addFeatureReference(FeatureReferenceV2 featureReference) { public Map getFeatureRefsByName() { return getFeatureReferences().stream() .collect( - Collectors.toMap(FeatureReferenceV2::getName, featureReference -> featureReference)); + Collectors.toMap( + FeatureReferenceV2::getFeatureName, featureReference -> featureReference)); } } diff --git a/java/storage/api/src/main/java/feast/storage/api/retriever/OnlineRetrieverV2.java b/java/storage/api/src/main/java/feast/storage/api/retriever/OnlineRetrieverV2.java index db5db8b63c..fde8ba7396 100644 --- a/java/storage/api/src/main/java/feast/storage/api/retriever/OnlineRetrieverV2.java +++ b/java/storage/api/src/main/java/feast/storage/api/retriever/OnlineRetrieverV2.java @@ -17,6 +17,7 @@ package feast.storage.api.retriever; import feast.proto.serving.ServingAPIProto; +import feast.proto.types.ValueProto; import java.util.List; import java.util.Map; @@ -31,16 +32,14 @@ public interface OnlineRetrieverV2 { * Feature} returned should match the no. of given {@link * ServingAPIProto.GetOnlineFeaturesRequestV2.EntityRow}s * - * @param project name of project to request features from. * @param entityRows list of entity rows to request features for. * @param featureReferences specifies the FeatureTable to retrieve data from * @param entityNames name of entities * @return list of {@link Feature}s corresponding to data retrieved for each entity row from * FeatureTable specified in FeatureTable request. */ - List> getOnlineFeatures( - String project, - List entityRows, + List> getOnlineFeatures( + List> entityRows, List featureReferences, List entityNames); } diff --git a/java/storage/connectors/redis/src/main/java/feast/storage/connectors/redis/common/RedisHashDecoder.java b/java/storage/connectors/redis/src/main/java/feast/storage/connectors/redis/common/RedisHashDecoder.java index fd0f0a56dc..78b64fd141 100644 --- a/java/storage/connectors/redis/src/main/java/feast/storage/connectors/redis/common/RedisHashDecoder.java +++ b/java/storage/connectors/redis/src/main/java/feast/storage/connectors/redis/common/RedisHashDecoder.java @@ -16,7 +16,6 @@ */ package feast.storage.connectors.redis.common; -import com.google.common.collect.Maps; import com.google.common.hash.Hashing; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.Timestamp; @@ -35,13 +34,14 @@ public class RedisHashDecoder { * Converts all retrieved Redis Hash values based on EntityRows into {@link Feature} * * @param redisHashValues retrieved Redis Hash values based on EntityRows - * @param byteToFeatureReferenceMap map to decode bytes back to FeatureReference + * @param byteToFeatureIdxMap map to decode bytes back to FeatureReference * @param timestampPrefix timestamp prefix * @return Map of {@link ServingAPIProto.FeatureReferenceV2} to {@link Feature} */ - public static Map retrieveFeature( + public static List retrieveFeature( Map redisHashValues, - Map byteToFeatureReferenceMap, + Map byteToFeatureIdxMap, + List featureReferences, String timestampPrefix) { Map featureTableTimestampMap = redisHashValues.entrySet().stream() @@ -57,14 +57,11 @@ public static Map retrieveFeature( "Couldn't parse timestamp proto while pulling data from Redis"); } })); - Map results = - Maps.newHashMapWithExpectedSize(byteToFeatureReferenceMap.size()); + List results = new ArrayList<>(Collections.nCopies(featureReferences.size(), null)); for (Map.Entry entry : redisHashValues.entrySet()) { - ServingAPIProto.FeatureReferenceV2 featureReference = - byteToFeatureReferenceMap.get(ByteBuffer.wrap(entry.getKey())); - - if (featureReference == null) { + Integer featureIdx = byteToFeatureIdxMap.get(ByteBuffer.wrap(entry.getKey())); + if (featureIdx == null) { continue; } @@ -75,11 +72,11 @@ public static Map retrieveFeature( throw new RuntimeException( "Couldn't parse feature value proto while pulling data from Redis"); } - results.put( - featureReference, + results.set( + featureIdx, new ProtoFeature( - featureReference, - featureTableTimestampMap.get(featureReference.getFeatureTable()), + featureReferences.get(featureIdx), + featureTableTimestampMap.get(featureReferences.get(featureIdx).getFeatureViewName()), v)); } @@ -94,7 +91,7 @@ public static byte[] getTimestampRedisHashKeyBytes(String featureTable, String t public static byte[] getFeatureReferenceRedisHashKeyBytes( ServingAPIProto.FeatureReferenceV2 featureReference) { String delimitedFeatureReference = - featureReference.getFeatureTable() + ":" + featureReference.getName(); + featureReference.getFeatureViewName() + ":" + featureReference.getFeatureName(); return Hashing.murmur3_32() .hashString(delimitedFeatureReference, StandardCharsets.UTF_8) .asBytes(); diff --git a/java/storage/connectors/redis/src/main/java/feast/storage/connectors/redis/common/RedisKeyGenerator.java b/java/storage/connectors/redis/src/main/java/feast/storage/connectors/redis/common/RedisKeyGenerator.java index 797dd52215..389ca0abfd 100644 --- a/java/storage/connectors/redis/src/main/java/feast/storage/connectors/redis/common/RedisKeyGenerator.java +++ b/java/storage/connectors/redis/src/main/java/feast/storage/connectors/redis/common/RedisKeyGenerator.java @@ -28,7 +28,7 @@ public class RedisKeyGenerator { public static List buildRedisKeys( - String project, List entityRows) { + String project, List> entityRows) { List redisKeys = entityRows.stream() .map(entityRow -> makeRedisKey(project, entityRow)) @@ -45,17 +45,16 @@ public static List buildRedisKeys( * @return {@link RedisProto.RedisKeyV2} */ private static RedisProto.RedisKeyV2 makeRedisKey( - String project, ServingAPIProto.GetOnlineFeaturesRequestV2.EntityRow entityRow) { + String project, Map entityRow) { RedisProto.RedisKeyV2.Builder builder = RedisProto.RedisKeyV2.newBuilder().setProject(project); - Map fieldsMap = entityRow.getFieldsMap(); - List entityNames = new ArrayList<>(new HashSet<>(fieldsMap.keySet())); + List entityNames = new ArrayList<>(new HashSet<>(entityRow.keySet())); // Sort entity names by alphabetical order entityNames.sort(String::compareTo); for (String entityName : entityNames) { builder.addEntityNames(entityName); - builder.addEntityValues(fieldsMap.get(entityName)); + builder.addEntityValues(entityRow.get(entityName)); } return builder.build(); } diff --git a/java/storage/connectors/redis/src/main/java/feast/storage/connectors/redis/retriever/OnlineRetriever.java b/java/storage/connectors/redis/src/main/java/feast/storage/connectors/redis/retriever/OnlineRetriever.java index ab03049b9f..a71812e875 100644 --- a/java/storage/connectors/redis/src/main/java/feast/storage/connectors/redis/retriever/OnlineRetriever.java +++ b/java/storage/connectors/redis/src/main/java/feast/storage/connectors/redis/retriever/OnlineRetriever.java @@ -19,6 +19,7 @@ import com.google.common.collect.Lists; import feast.proto.serving.ServingAPIProto; import feast.proto.storage.RedisProto; +import feast.proto.types.ValueProto; import feast.storage.api.retriever.Feature; import feast.storage.api.retriever.OnlineRetrieverV2; import feast.storage.connectors.redis.common.RedisHashDecoder; @@ -38,52 +39,52 @@ public class OnlineRetriever implements OnlineRetrieverV2 { private static final String timestampPrefix = "_ts"; private final RedisClientAdapter redisClientAdapter; private final EntityKeySerializer keySerializer; + private final String project; // Number of fields in request to Redis which requires using HGETALL instead of HMGET public static final int HGETALL_NUMBER_OF_FIELDS_THRESHOLD = 50; - public OnlineRetriever(RedisClientAdapter redisClientAdapter, EntityKeySerializer keySerializer) { + public OnlineRetriever( + String project, RedisClientAdapter redisClientAdapter, EntityKeySerializer keySerializer) { + this.project = project; this.redisClientAdapter = redisClientAdapter; this.keySerializer = keySerializer; } @Override - public List> getOnlineFeatures( - String project, - List entityRows, + public List> getOnlineFeatures( + List> entityRows, List featureReferences, List entityNames) { - List redisKeys = RedisKeyGenerator.buildRedisKeys(project, entityRows); + List redisKeys = + RedisKeyGenerator.buildRedisKeys(this.project, entityRows); return getFeaturesFromRedis(redisKeys, featureReferences); } - private List> getFeaturesFromRedis( + private List> getFeaturesFromRedis( List redisKeys, List featureReferences) { - List> features = new ArrayList<>(); - // To decode bytes back to Feature Reference - Map byteToFeatureReferenceMap = new HashMap<>(); + // To decode bytes back to Feature + Map byteToFeatureIdxMap = new HashMap<>(); // Serialize using proto List binaryRedisKeys = redisKeys.stream().map(this.keySerializer::serialize).collect(Collectors.toList()); List retrieveFields = new ArrayList<>(); - featureReferences.stream() - .forEach( - featureReference -> { - - // eg. murmur() - byte[] featureReferenceBytes = - RedisHashDecoder.getFeatureReferenceRedisHashKeyBytes(featureReference); - retrieveFields.add(featureReferenceBytes); - byteToFeatureReferenceMap.put( - ByteBuffer.wrap(featureReferenceBytes), featureReference); - }); + for (int idx = 0; + idx < featureReferences.size(); + idx++) { // eg. murmur() + byte[] featureReferenceBytes = + RedisHashDecoder.getFeatureReferenceRedisHashKeyBytes(featureReferences.get(idx)); + retrieveFields.add(featureReferenceBytes); + + byteToFeatureIdxMap.put(ByteBuffer.wrap(featureReferenceBytes), idx); + } featureReferences.stream() - .map(ServingAPIProto.FeatureReferenceV2::getFeatureTable) + .map(ServingAPIProto.FeatureReferenceV2::getFeatureViewName) .distinct() .forEach( table -> { @@ -121,12 +122,12 @@ private List> getFeaturesFromRe } } - List> results = - Lists.newArrayListWithExpectedSize(futures.size()); + List> results = Lists.newArrayListWithExpectedSize(futures.size()); for (Future> f : futures) { try { results.add( - RedisHashDecoder.retrieveFeature(f.get(), byteToFeatureReferenceMap, timestampPrefix)); + RedisHashDecoder.retrieveFeature( + f.get(), byteToFeatureIdxMap, featureReferences, timestampPrefix)); } catch (InterruptedException | ExecutionException e) { throw new RuntimeException("Unexpected error when pulling data from Redis"); } diff --git a/protos/feast/serving/ServingService.proto b/protos/feast/serving/ServingService.proto index e37ecbbdde..7d45e61a5e 100644 --- a/protos/feast/serving/ServingService.proto +++ b/protos/feast/serving/ServingService.proto @@ -29,8 +29,8 @@ service ServingService { // Get information about this Feast serving. rpc GetFeastServingInfo (GetFeastServingInfoRequest) returns (GetFeastServingInfoResponse); - // Get online features (v2) synchronously. - rpc GetOnlineFeaturesV2 (GetOnlineFeaturesRequestV2) returns (GetOnlineFeaturesResponse); + // Get online features synchronously. + rpc GetOnlineFeatures (GetOnlineFeaturesRequest) returns (GetOnlineFeaturesResponseV2); } message GetFeastServingInfoRequest {} @@ -38,24 +38,17 @@ message GetFeastServingInfoRequest {} message GetFeastServingInfoResponse { // Feast version of this serving deployment. string version = 1; - - // Type of serving deployment, either ONLINE or BATCH. Different store types support different - // feature retrieval methods. - FeastServingType type = 2; - - // Note: Batch specific options start from 10. - // Staging location for this serving store, if any. - string job_staging_location = 10; } message FeatureReferenceV2 { - // Name of the Feature Table to retrieve the feature from. - string feature_table = 1; + // Name of the Feature View to retrieve the feature from. + string feature_view_name = 1; // Name of the Feature to retrieve the feature from. - string name = 2; + string feature_name = 2; } +// ToDo (oleksii): remove this message (since it's not used) and move EntityRow on package level message GetOnlineFeaturesRequestV2 { // List of features that are being retrieved repeated FeatureReferenceV2 features = 4; @@ -94,6 +87,11 @@ message GetOnlineFeaturesRequest { // A map of entity name -> list of values map entities = 3; bool full_feature_names = 4; + + // Context for OnDemand Feature Transformation + // (was moved to dedicated parameter to avoid unnecessary separation logic on serving side) + // A map of variable name -> list of values + map request_context = 5; } message GetOnlineFeaturesResponse { @@ -107,35 +105,43 @@ message GetOnlineFeaturesResponse { // Map of feature or entity name to feature/entity statuses/metadata. map statuses = 2; } - - enum FieldStatus { - // Status is unset for this field. - INVALID = 0; - - // Field value is present for this field and age is within max age. - PRESENT = 1; - - // Values could be found for entity key and age is within max age, but - // this field value is assigned a value on ingestion into feast. - NULL_VALUE = 2; - - // Entity key did not return any values as they do not exist in Feast. - // This could suggest that the feature values have not yet been ingested - // into feast or the ingestion failed. - NOT_FOUND = 3; - - // Values could be found for entity key, but field values are outside the maximum - // allowable range. - OUTSIDE_MAX_AGE = 4; +} + +message GetOnlineFeaturesResponseV2 { + GetOnlineFeaturesResponseMetadata metadata = 1; + + // Length of "results" array should match length of requested features. + // We also preserve the same order of features here as in metadata.feature_names + repeated FeatureVector results = 2; + + message FeatureVector { + repeated feast.types.Value values = 1; + repeated FieldStatus statuses = 2; + repeated google.protobuf.Timestamp event_timestamps = 3; } } -enum FeastServingType { - FEAST_SERVING_TYPE_INVALID = 0; - // Online serving receives entity data directly and synchronously and will - // respond immediately. - FEAST_SERVING_TYPE_ONLINE = 1; - // Batch serving receives entity data asynchronously and orchestrates the - // retrieval through a staging location. - FEAST_SERVING_TYPE_BATCH = 2; +message GetOnlineFeaturesResponseMetadata { + FeatureList feature_names = 1; +} + +enum FieldStatus { + // Status is unset for this field. + INVALID = 0; + + // Field value is present for this field and age is within max age. + PRESENT = 1; + + // Values could be found for entity key and age is within max age, but + // this field value is assigned a value on ingestion into feast. + NULL_VALUE = 2; + + // Entity key did not return any values as they do not exist in Feast. + // This could suggest that the feature values have not yet been ingested + // into feast or the ingestion failed. + NOT_FOUND = 3; + + // Values could be found for entity key, but field values are outside the maximum + // allowable range. + OUTSIDE_MAX_AGE = 4; } diff --git a/sdk/go/client.go b/sdk/go/client.go index 4deb0a789c..c7251e3319 100644 --- a/sdk/go/client.go +++ b/sdk/go/client.go @@ -110,7 +110,7 @@ func (fc *GrpcClient) GetOnlineFeatures(ctx context.Context, req *OnlineFeatures if err != nil { return nil, err } - resp, err := fc.cli.GetOnlineFeaturesV2(ctx, featuresRequest) + resp, err := fc.cli.GetOnlineFeatures(ctx, featuresRequest) // collect unqiue entity refs from entity rows entityRefs := make(map[string]struct{}) diff --git a/sdk/go/client_test.go b/sdk/go/client_test.go index a94a577e84..95be34af73 100644 --- a/sdk/go/client_test.go +++ b/sdk/go/client_test.go @@ -33,19 +33,26 @@ func TestGetOnlineFeatures(t *testing.T) { Project: "driver_project", }, want: OnlineFeaturesResponse{ - RawResponse: &serving.GetOnlineFeaturesResponse{ - FieldValues: []*serving.GetOnlineFeaturesResponse_FieldValues{ + RawResponse: &serving.GetOnlineFeaturesResponseV2{ + Results: []*serving.GetOnlineFeaturesResponseV2_FeatureVector{ { - Fields: map[string]*types.Value{ - "driver:rating": Int64Val(1), - "driver:null_value": {}, + Values: []*types.Value{Int64Val(1)}, + Statuses: []serving.FieldStatus{ + serving.FieldStatus_PRESENT, }, - Statuses: map[string]serving.GetOnlineFeaturesResponse_FieldStatus{ - "driver:rating": serving.GetOnlineFeaturesResponse_PRESENT, - "driver:null_value": serving.GetOnlineFeaturesResponse_NULL_VALUE, + }, + { + Values: []*types.Value{{}}, + Statuses: []serving.FieldStatus{ + serving.FieldStatus_NULL_VALUE, }, }, }, + Metadata: &serving.GetOnlineFeaturesResponseMetadata{ + FeatureNames: &serving.FeatureList{ + Val: []string{"driver:rating", "driver:null_value"}, + }, + }, }, }, }, @@ -60,7 +67,7 @@ func TestGetOnlineFeatures(t *testing.T) { ctx := context.Background() rawRequest, _ := tc.req.buildRequest() resp := tc.want.RawResponse - cli.EXPECT().GetOnlineFeaturesV2(ctx, rawRequest).Return(resp, nil).Times(1) + cli.EXPECT().GetOnlineFeatures(ctx, rawRequest).Return(resp, nil).Times(1) client := &GrpcClient{ cli: cli, diff --git a/sdk/go/mocks/serving_mock.go b/sdk/go/mocks/serving_mock.go index 00d2e768ef..57ee0c1ea4 100644 --- a/sdk/go/mocks/serving_mock.go +++ b/sdk/go/mocks/serving_mock.go @@ -57,21 +57,21 @@ func (mr *MockServingServiceClientMockRecorder) GetFeastServingInfo(arg0, arg1 i } // GetOnlineFeaturesV2 mocks base method -func (m *MockServingServiceClient) GetOnlineFeaturesV2(arg0 context.Context, arg1 *serving.GetOnlineFeaturesRequestV2, arg2 ...grpc.CallOption) (*serving.GetOnlineFeaturesResponse, error) { +func (m *MockServingServiceClient) GetOnlineFeatures(arg0 context.Context, arg1 *serving.GetOnlineFeaturesRequest, arg2 ...grpc.CallOption) (*serving.GetOnlineFeaturesResponseV2, error) { m.ctrl.T.Helper() varargs := []interface{}{arg0, arg1} for _, a := range arg2 { varargs = append(varargs, a) } - ret := m.ctrl.Call(m, "GetOnlineFeaturesV2", varargs...) - ret0, _ := ret[0].(*serving.GetOnlineFeaturesResponse) + ret := m.ctrl.Call(m, "GetOnlineFeatures", varargs...) + ret0, _ := ret[0].(*serving.GetOnlineFeaturesResponseV2) ret1, _ := ret[1].(error) return ret0, ret1 } // GetOnlineFeaturesV2 indicates an expected call of GetOnlineFeaturesV2 -func (mr *MockServingServiceClientMockRecorder) GetOnlineFeaturesV2(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { +func (mr *MockServingServiceClientMockRecorder) GetOnlineFeatures(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOnlineFeaturesV2", reflect.TypeOf((*MockServingServiceClient)(nil).GetOnlineFeaturesV2), varargs...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOnlineFeatures", reflect.TypeOf((*MockServingServiceClient)(nil).GetOnlineFeatures), varargs...) } diff --git a/sdk/go/protos/feast/core/DataFormat.pb.go b/sdk/go/protos/feast/core/DataFormat.pb.go index 13c6cdda98..6745171c90 100644 --- a/sdk/go/protos/feast/core/DataFormat.pb.go +++ b/sdk/go/protos/feast/core/DataFormat.pb.go @@ -17,7 +17,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v3.11.2 +// protoc v3.17.3 // source: feast/core/DataFormat.proto package core diff --git a/sdk/go/protos/feast/core/DataSource.pb.go b/sdk/go/protos/feast/core/DataSource.pb.go index 8af638a834..83f0bc6736 100644 --- a/sdk/go/protos/feast/core/DataSource.pb.go +++ b/sdk/go/protos/feast/core/DataSource.pb.go @@ -17,7 +17,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v3.11.2 +// protoc v3.17.3 // source: feast/core/DataSource.proto package core diff --git a/sdk/go/protos/feast/core/Entity.pb.go b/sdk/go/protos/feast/core/Entity.pb.go index c6d9014791..87f5b45164 100644 --- a/sdk/go/protos/feast/core/Entity.pb.go +++ b/sdk/go/protos/feast/core/Entity.pb.go @@ -17,7 +17,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v3.11.2 +// protoc v3.17.3 // source: feast/core/Entity.proto package core diff --git a/sdk/go/protos/feast/core/Feature.pb.go b/sdk/go/protos/feast/core/Feature.pb.go index 5d332dddff..50515a822b 100644 --- a/sdk/go/protos/feast/core/Feature.pb.go +++ b/sdk/go/protos/feast/core/Feature.pb.go @@ -17,7 +17,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v3.11.2 +// protoc v3.17.3 // source: feast/core/Feature.proto package core diff --git a/sdk/go/protos/feast/core/FeatureTable.pb.go b/sdk/go/protos/feast/core/FeatureTable.pb.go index 355ef50fb8..0fc3feb0ca 100644 --- a/sdk/go/protos/feast/core/FeatureTable.pb.go +++ b/sdk/go/protos/feast/core/FeatureTable.pb.go @@ -17,7 +17,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v3.11.2 +// protoc v3.17.3 // source: feast/core/FeatureTable.proto package core diff --git a/sdk/go/protos/feast/core/Store.pb.go b/sdk/go/protos/feast/core/Store.pb.go index 6c46f10d24..26e5a5918f 100644 --- a/sdk/go/protos/feast/core/Store.pb.go +++ b/sdk/go/protos/feast/core/Store.pb.go @@ -17,7 +17,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v3.11.2 +// protoc v3.17.3 // source: feast/core/Store.proto package core diff --git a/sdk/go/protos/feast/serving/ServingService.pb.go b/sdk/go/protos/feast/serving/ServingService.pb.go index 32e3461dfd..68e771a31b 100644 --- a/sdk/go/protos/feast/serving/ServingService.pb.go +++ b/sdk/go/protos/feast/serving/ServingService.pb.go @@ -16,7 +16,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v3.11.2 +// protoc v3.17.3 // source: feast/serving/ServingService.proto package serving @@ -24,7 +24,6 @@ package serving import ( context "context" types "github.com/feast-dev/feast/sdk/go/protos/feast/types" - _ "github.com/feast-dev/feast/sdk/go/protos/tensorflow_metadata/proto/v0" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" @@ -42,88 +41,35 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -type FeastServingType int32 - -const ( - FeastServingType_FEAST_SERVING_TYPE_INVALID FeastServingType = 0 - // Online serving receives entity data directly and synchronously and will - // respond immediately. - FeastServingType_FEAST_SERVING_TYPE_ONLINE FeastServingType = 1 - // Batch serving receives entity data asynchronously and orchestrates the - // retrieval through a staging location. - FeastServingType_FEAST_SERVING_TYPE_BATCH FeastServingType = 2 -) - -// Enum value maps for FeastServingType. -var ( - FeastServingType_name = map[int32]string{ - 0: "FEAST_SERVING_TYPE_INVALID", - 1: "FEAST_SERVING_TYPE_ONLINE", - 2: "FEAST_SERVING_TYPE_BATCH", - } - FeastServingType_value = map[string]int32{ - "FEAST_SERVING_TYPE_INVALID": 0, - "FEAST_SERVING_TYPE_ONLINE": 1, - "FEAST_SERVING_TYPE_BATCH": 2, - } -) - -func (x FeastServingType) Enum() *FeastServingType { - p := new(FeastServingType) - *p = x - return p -} - -func (x FeastServingType) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (FeastServingType) Descriptor() protoreflect.EnumDescriptor { - return file_feast_serving_ServingService_proto_enumTypes[0].Descriptor() -} - -func (FeastServingType) Type() protoreflect.EnumType { - return &file_feast_serving_ServingService_proto_enumTypes[0] -} - -func (x FeastServingType) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Use FeastServingType.Descriptor instead. -func (FeastServingType) EnumDescriptor() ([]byte, []int) { - return file_feast_serving_ServingService_proto_rawDescGZIP(), []int{0} -} - -type GetOnlineFeaturesResponse_FieldStatus int32 +type FieldStatus int32 const ( // Status is unset for this field. - GetOnlineFeaturesResponse_INVALID GetOnlineFeaturesResponse_FieldStatus = 0 + FieldStatus_INVALID FieldStatus = 0 // Field value is present for this field and age is within max age. - GetOnlineFeaturesResponse_PRESENT GetOnlineFeaturesResponse_FieldStatus = 1 + FieldStatus_PRESENT FieldStatus = 1 // Values could be found for entity key and age is within max age, but // this field value is assigned a value on ingestion into feast. - GetOnlineFeaturesResponse_NULL_VALUE GetOnlineFeaturesResponse_FieldStatus = 2 + FieldStatus_NULL_VALUE FieldStatus = 2 // Entity key did not return any values as they do not exist in Feast. // This could suggest that the feature values have not yet been ingested // into feast or the ingestion failed. - GetOnlineFeaturesResponse_NOT_FOUND GetOnlineFeaturesResponse_FieldStatus = 3 + FieldStatus_NOT_FOUND FieldStatus = 3 // Values could be found for entity key, but field values are outside the maximum // allowable range. - GetOnlineFeaturesResponse_OUTSIDE_MAX_AGE GetOnlineFeaturesResponse_FieldStatus = 4 + FieldStatus_OUTSIDE_MAX_AGE FieldStatus = 4 ) -// Enum value maps for GetOnlineFeaturesResponse_FieldStatus. +// Enum value maps for FieldStatus. var ( - GetOnlineFeaturesResponse_FieldStatus_name = map[int32]string{ + FieldStatus_name = map[int32]string{ 0: "INVALID", 1: "PRESENT", 2: "NULL_VALUE", 3: "NOT_FOUND", 4: "OUTSIDE_MAX_AGE", } - GetOnlineFeaturesResponse_FieldStatus_value = map[string]int32{ + FieldStatus_value = map[string]int32{ "INVALID": 0, "PRESENT": 1, "NULL_VALUE": 2, @@ -132,31 +78,31 @@ var ( } ) -func (x GetOnlineFeaturesResponse_FieldStatus) Enum() *GetOnlineFeaturesResponse_FieldStatus { - p := new(GetOnlineFeaturesResponse_FieldStatus) +func (x FieldStatus) Enum() *FieldStatus { + p := new(FieldStatus) *p = x return p } -func (x GetOnlineFeaturesResponse_FieldStatus) String() string { +func (x FieldStatus) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } -func (GetOnlineFeaturesResponse_FieldStatus) Descriptor() protoreflect.EnumDescriptor { - return file_feast_serving_ServingService_proto_enumTypes[1].Descriptor() +func (FieldStatus) Descriptor() protoreflect.EnumDescriptor { + return file_feast_serving_ServingService_proto_enumTypes[0].Descriptor() } -func (GetOnlineFeaturesResponse_FieldStatus) Type() protoreflect.EnumType { - return &file_feast_serving_ServingService_proto_enumTypes[1] +func (FieldStatus) Type() protoreflect.EnumType { + return &file_feast_serving_ServingService_proto_enumTypes[0] } -func (x GetOnlineFeaturesResponse_FieldStatus) Number() protoreflect.EnumNumber { +func (x FieldStatus) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } -// Deprecated: Use GetOnlineFeaturesResponse_FieldStatus.Descriptor instead. -func (GetOnlineFeaturesResponse_FieldStatus) EnumDescriptor() ([]byte, []int) { - return file_feast_serving_ServingService_proto_rawDescGZIP(), []int{6, 0} +// Deprecated: Use FieldStatus.Descriptor instead. +func (FieldStatus) EnumDescriptor() ([]byte, []int) { + return file_feast_serving_ServingService_proto_rawDescGZIP(), []int{0} } type GetFeastServingInfoRequest struct { @@ -204,12 +150,6 @@ type GetFeastServingInfoResponse struct { // Feast version of this serving deployment. Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` - // Type of serving deployment, either ONLINE or BATCH. Different store types support different - // feature retrieval methods. - Type FeastServingType `protobuf:"varint,2,opt,name=type,proto3,enum=feast.serving.FeastServingType" json:"type,omitempty"` - // Note: Batch specific options start from 10. - // Staging location for this serving store, if any. - JobStagingLocation string `protobuf:"bytes,10,opt,name=job_staging_location,json=jobStagingLocation,proto3" json:"job_staging_location,omitempty"` } func (x *GetFeastServingInfoResponse) Reset() { @@ -251,29 +191,15 @@ func (x *GetFeastServingInfoResponse) GetVersion() string { return "" } -func (x *GetFeastServingInfoResponse) GetType() FeastServingType { - if x != nil { - return x.Type - } - return FeastServingType_FEAST_SERVING_TYPE_INVALID -} - -func (x *GetFeastServingInfoResponse) GetJobStagingLocation() string { - if x != nil { - return x.JobStagingLocation - } - return "" -} - type FeatureReferenceV2 struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // Name of the Feature Table to retrieve the feature from. - FeatureTable string `protobuf:"bytes,1,opt,name=feature_table,json=featureTable,proto3" json:"feature_table,omitempty"` + // Name of the Feature View to retrieve the feature from. + FeatureViewName string `protobuf:"bytes,1,opt,name=feature_view_name,json=featureViewName,proto3" json:"feature_view_name,omitempty"` // Name of the Feature to retrieve the feature from. - Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + FeatureName string `protobuf:"bytes,2,opt,name=feature_name,json=featureName,proto3" json:"feature_name,omitempty"` } func (x *FeatureReferenceV2) Reset() { @@ -308,20 +234,21 @@ func (*FeatureReferenceV2) Descriptor() ([]byte, []int) { return file_feast_serving_ServingService_proto_rawDescGZIP(), []int{2} } -func (x *FeatureReferenceV2) GetFeatureTable() string { +func (x *FeatureReferenceV2) GetFeatureViewName() string { if x != nil { - return x.FeatureTable + return x.FeatureViewName } return "" } -func (x *FeatureReferenceV2) GetName() string { +func (x *FeatureReferenceV2) GetFeatureName() string { if x != nil { - return x.Name + return x.FeatureName } return "" } +// ToDo (oleksii): remove this message (since it's not used) and move EntityRow on package level type GetOnlineFeaturesRequestV2 struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -453,6 +380,7 @@ type GetOnlineFeaturesRequest struct { // A map of entity name -> list of values Entities map[string]*types.RepeatedValue `protobuf:"bytes,3,rep,name=entities,proto3" json:"entities,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` FullFeatureNames bool `protobuf:"varint,4,opt,name=full_feature_names,json=fullFeatureNames,proto3" json:"full_feature_names,omitempty"` + RequestContext map[string]*types.RepeatedValue `protobuf:"bytes,5,rep,name=request_context,json=requestContext,proto3" json:"request_context,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *GetOnlineFeaturesRequest) Reset() { @@ -522,6 +450,13 @@ func (x *GetOnlineFeaturesRequest) GetFullFeatureNames() bool { return false } +func (x *GetOnlineFeaturesRequest) GetRequestContext() map[string]*types.RepeatedValue { + if x != nil { + return x.RequestContext + } + return nil +} + type isGetOnlineFeaturesRequest_Kind interface { isGetOnlineFeaturesRequest_Kind() } @@ -586,6 +521,108 @@ func (x *GetOnlineFeaturesResponse) GetFieldValues() []*GetOnlineFeaturesRespons return nil } +type GetOnlineFeaturesResponseV2 struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Metadata *GetOnlineFeaturesResponseMetadata `protobuf:"bytes,1,opt,name=metadata,proto3" json:"metadata,omitempty"` + Results []*GetOnlineFeaturesResponseV2_FeatureVector `protobuf:"bytes,2,rep,name=results,proto3" json:"results,omitempty"` +} + +func (x *GetOnlineFeaturesResponseV2) Reset() { + *x = GetOnlineFeaturesResponseV2{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_serving_ServingService_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetOnlineFeaturesResponseV2) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetOnlineFeaturesResponseV2) ProtoMessage() {} + +func (x *GetOnlineFeaturesResponseV2) ProtoReflect() protoreflect.Message { + mi := &file_feast_serving_ServingService_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetOnlineFeaturesResponseV2.ProtoReflect.Descriptor instead. +func (*GetOnlineFeaturesResponseV2) Descriptor() ([]byte, []int) { + return file_feast_serving_ServingService_proto_rawDescGZIP(), []int{7} +} + +func (x *GetOnlineFeaturesResponseV2) GetMetadata() *GetOnlineFeaturesResponseMetadata { + if x != nil { + return x.Metadata + } + return nil +} + +func (x *GetOnlineFeaturesResponseV2) GetResults() []*GetOnlineFeaturesResponseV2_FeatureVector { + if x != nil { + return x.Results + } + return nil +} + +type GetOnlineFeaturesResponseMetadata struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + FeatureNames *FeatureList `protobuf:"bytes,1,opt,name=feature_names,json=featureNames,proto3" json:"feature_names,omitempty"` +} + +func (x *GetOnlineFeaturesResponseMetadata) Reset() { + *x = GetOnlineFeaturesResponseMetadata{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_serving_ServingService_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetOnlineFeaturesResponseMetadata) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetOnlineFeaturesResponseMetadata) ProtoMessage() {} + +func (x *GetOnlineFeaturesResponseMetadata) ProtoReflect() protoreflect.Message { + mi := &file_feast_serving_ServingService_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetOnlineFeaturesResponseMetadata.ProtoReflect.Descriptor instead. +func (*GetOnlineFeaturesResponseMetadata) Descriptor() ([]byte, []int) { + return file_feast_serving_ServingService_proto_rawDescGZIP(), []int{8} +} + +func (x *GetOnlineFeaturesResponseMetadata) GetFeatureNames() *FeatureList { + if x != nil { + return x.FeatureNames + } + return nil +} + type GetOnlineFeaturesRequestV2_EntityRow struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -601,7 +638,7 @@ type GetOnlineFeaturesRequestV2_EntityRow struct { func (x *GetOnlineFeaturesRequestV2_EntityRow) Reset() { *x = GetOnlineFeaturesRequestV2_EntityRow{} if protoimpl.UnsafeEnabled { - mi := &file_feast_serving_ServingService_proto_msgTypes[7] + mi := &file_feast_serving_ServingService_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -614,7 +651,7 @@ func (x *GetOnlineFeaturesRequestV2_EntityRow) String() string { func (*GetOnlineFeaturesRequestV2_EntityRow) ProtoMessage() {} func (x *GetOnlineFeaturesRequestV2_EntityRow) ProtoReflect() protoreflect.Message { - mi := &file_feast_serving_ServingService_proto_msgTypes[7] + mi := &file_feast_serving_ServingService_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -653,13 +690,13 @@ type GetOnlineFeaturesResponse_FieldValues struct { // Timestamps are not returned in this response. Fields map[string]*types.Value `protobuf:"bytes,1,rep,name=fields,proto3" json:"fields,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` // Map of feature or entity name to feature/entity statuses/metadata. - Statuses map[string]GetOnlineFeaturesResponse_FieldStatus `protobuf:"bytes,2,rep,name=statuses,proto3" json:"statuses,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3,enum=feast.serving.GetOnlineFeaturesResponse_FieldStatus"` + Statuses map[string]FieldStatus `protobuf:"bytes,2,rep,name=statuses,proto3" json:"statuses,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3,enum=feast.serving.FieldStatus"` } func (x *GetOnlineFeaturesResponse_FieldValues) Reset() { *x = GetOnlineFeaturesResponse_FieldValues{} if protoimpl.UnsafeEnabled { - mi := &file_feast_serving_ServingService_proto_msgTypes[10] + mi := &file_feast_serving_ServingService_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -672,7 +709,7 @@ func (x *GetOnlineFeaturesResponse_FieldValues) String() string { func (*GetOnlineFeaturesResponse_FieldValues) ProtoMessage() {} func (x *GetOnlineFeaturesResponse_FieldValues) ProtoReflect() protoreflect.Message { - mi := &file_feast_serving_ServingService_proto_msgTypes[10] + mi := &file_feast_serving_ServingService_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -695,13 +732,76 @@ func (x *GetOnlineFeaturesResponse_FieldValues) GetFields() map[string]*types.Va return nil } -func (x *GetOnlineFeaturesResponse_FieldValues) GetStatuses() map[string]GetOnlineFeaturesResponse_FieldStatus { +func (x *GetOnlineFeaturesResponse_FieldValues) GetStatuses() map[string]FieldStatus { if x != nil { return x.Statuses } return nil } +type GetOnlineFeaturesResponseV2_FeatureVector struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Values []*types.Value `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"` + Statuses []FieldStatus `protobuf:"varint,2,rep,packed,name=statuses,proto3,enum=feast.serving.FieldStatus" json:"statuses,omitempty"` + EventTimestamps []*timestamppb.Timestamp `protobuf:"bytes,3,rep,name=event_timestamps,json=eventTimestamps,proto3" json:"event_timestamps,omitempty"` +} + +func (x *GetOnlineFeaturesResponseV2_FeatureVector) Reset() { + *x = GetOnlineFeaturesResponseV2_FeatureVector{} + if protoimpl.UnsafeEnabled { + mi := &file_feast_serving_ServingService_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetOnlineFeaturesResponseV2_FeatureVector) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetOnlineFeaturesResponseV2_FeatureVector) ProtoMessage() {} + +func (x *GetOnlineFeaturesResponseV2_FeatureVector) ProtoReflect() protoreflect.Message { + mi := &file_feast_serving_ServingService_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetOnlineFeaturesResponseV2_FeatureVector.ProtoReflect.Descriptor instead. +func (*GetOnlineFeaturesResponseV2_FeatureVector) Descriptor() ([]byte, []int) { + return file_feast_serving_ServingService_proto_rawDescGZIP(), []int{7, 0} +} + +func (x *GetOnlineFeaturesResponseV2_FeatureVector) GetValues() []*types.Value { + if x != nil { + return x.Values + } + return nil +} + +func (x *GetOnlineFeaturesResponseV2_FeatureVector) GetStatuses() []FieldStatus { + if x != nil { + return x.Statuses + } + return nil +} + +func (x *GetOnlineFeaturesResponseV2_FeatureVector) GetEventTimestamps() []*timestamppb.Timestamp { + if x != nil { + return x.EventTimestamps + } + return nil +} + var File_feast_serving_ServingService_proto protoreflect.FileDescriptor var file_feast_serving_ServingService_proto_rawDesc = []byte{ @@ -711,146 +811,171 @@ var file_feast_serving_ServingService_proto_rawDesc = []byte{ 0x69, 0x6e, 0x67, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x17, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x74, 0x79, 0x70, 0x65, - 0x73, 0x2f, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2d, 0x74, - 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x30, 0x2f, 0x73, 0x74, 0x61, 0x74, - 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x1c, 0x0a, 0x1a, - 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x49, - 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x9e, 0x01, 0x0a, 0x1b, 0x47, + 0x73, 0x2f, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x1c, 0x0a, + 0x1a, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, + 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x37, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x33, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, - 0x6e, 0x67, 0x2e, 0x46, 0x65, 0x61, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x54, - 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x30, 0x0a, 0x14, 0x6a, 0x6f, 0x62, - 0x5f, 0x73, 0x74, 0x61, 0x67, 0x69, 0x6e, 0x67, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x6a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x67, - 0x69, 0x6e, 0x67, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x4d, 0x0a, 0x12, 0x46, - 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x56, - 0x32, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x74, 0x61, 0x62, - 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0xbb, 0x03, 0x0a, 0x1a, 0x47, - 0x65, 0x74, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x56, 0x32, 0x12, 0x3d, 0x0a, 0x08, 0x66, 0x65, 0x61, - 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x66, 0x65, - 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x46, 0x65, 0x61, 0x74, - 0x75, 0x72, 0x65, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x56, 0x32, 0x52, 0x08, - 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x54, 0x0a, 0x0b, 0x65, 0x6e, 0x74, 0x69, - 0x74, 0x79, 0x5f, 0x72, 0x6f, 0x77, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, - 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x47, 0x65, - 0x74, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x56, 0x32, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, - 0x6f, 0x77, 0x52, 0x0a, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x6f, 0x77, 0x73, 0x12, 0x18, - 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x1a, 0xed, 0x01, 0x0a, 0x09, 0x45, 0x6e, 0x74, - 0x69, 0x74, 0x79, 0x52, 0x6f, 0x77, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, - 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, - 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x12, 0x57, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x3f, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, - 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x56, 0x32, 0x2e, 0x45, 0x6e, 0x74, 0x69, - 0x74, 0x79, 0x52, 0x6f, 0x77, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x1a, 0x4d, 0x0a, 0x0b, 0x46, 0x69, 0x65, - 0x6c, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x66, 0x65, 0x61, 0x73, - 0x74, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x1f, 0x0a, 0x0b, 0x46, 0x65, 0x61, 0x74, - 0x75, 0x72, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x76, 0x61, 0x6c, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x76, 0x61, 0x6c, 0x22, 0xe1, 0x02, 0x0a, 0x18, 0x47, 0x65, - 0x74, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x0f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, - 0x00, 0x52, 0x0e, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x12, 0x38, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, - 0x69, 0x6e, 0x67, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x48, - 0x00, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x51, 0x0a, 0x08, 0x65, - 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, - 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x47, 0x65, + 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x63, 0x0a, 0x12, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, + 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x56, 0x32, 0x12, 0x2a, 0x0a, 0x11, 0x66, 0x65, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x56, 0x69, + 0x65, 0x77, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x66, 0x65, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0xbb, 0x03, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x2c, - 0x0a, 0x12, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x6e, - 0x61, 0x6d, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x66, 0x75, 0x6c, 0x6c, - 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x1a, 0x57, 0x0a, 0x0d, - 0x45, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, - 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, - 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x52, 0x65, 0x70, - 0x65, 0x61, 0x74, 0x65, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x06, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x22, 0xdd, 0x04, - 0x0a, 0x19, 0x47, 0x65, 0x74, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, - 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x66, - 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x34, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, - 0x67, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, - 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x46, 0x69, 0x65, 0x6c, - 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x52, 0x0b, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x56, 0x61, - 0x6c, 0x75, 0x65, 0x73, 0x1a, 0x89, 0x03, 0x0a, 0x0b, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x56, 0x61, - 0x6c, 0x75, 0x65, 0x73, 0x12, 0x58, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x40, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x56, 0x32, 0x12, 0x3d, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x66, 0x65, 0x61, + 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x56, 0x32, 0x52, 0x08, 0x66, + 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x54, 0x0a, 0x0b, 0x65, 0x6e, 0x74, 0x69, 0x74, + 0x79, 0x5f, 0x72, 0x6f, 0x77, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x66, + 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x47, 0x65, 0x74, + 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x56, 0x32, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x6f, + 0x77, 0x52, 0x0a, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x6f, 0x77, 0x73, 0x12, 0x18, 0x0a, + 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x1a, 0xed, 0x01, 0x0a, 0x09, 0x45, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x52, 0x6f, 0x77, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, + 0x57, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x3f, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, + 0x47, 0x65, 0x74, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x56, 0x32, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, + 0x79, 0x52, 0x6f, 0x77, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x1a, 0x4d, 0x0a, 0x0b, 0x46, 0x69, 0x65, 0x6c, + 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, + 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x1f, 0x0a, 0x0b, 0x46, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x76, 0x61, 0x6c, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x03, 0x76, 0x61, 0x6c, 0x22, 0xa6, 0x04, 0x0a, 0x18, 0x47, 0x65, 0x74, + 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x0f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, + 0x52, 0x0e, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x12, 0x38, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x6e, 0x67, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, + 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x51, 0x0a, 0x08, 0x65, 0x6e, + 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x66, + 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x47, 0x65, 0x74, + 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x52, 0x08, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x2c, 0x0a, + 0x12, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x66, 0x75, 0x6c, 0x6c, 0x46, + 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x64, 0x0a, 0x0f, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x05, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3b, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x46, 0x65, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x52, 0x0e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, + 0x74, 0x1a, 0x57, 0x0a, 0x0d, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x74, 0x79, 0x70, 0x65, + 0x73, 0x2e, 0x52, 0x65, 0x70, 0x65, 0x61, 0x74, 0x65, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x5d, 0x0a, 0x13, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, + 0x2e, 0x52, 0x65, 0x70, 0x65, 0x61, 0x74, 0x65, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x06, 0x0a, 0x04, 0x6b, 0x69, 0x6e, + 0x64, 0x22, 0xe6, 0x03, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x46, + 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x57, 0x0a, 0x0c, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, + 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x46, + 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, + 0x46, 0x69, 0x65, 0x6c, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x52, 0x0b, 0x66, 0x69, 0x65, + 0x6c, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x1a, 0xef, 0x02, 0x0a, 0x0b, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x58, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, + 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x40, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, + 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x6e, 0x6c, 0x69, + 0x6e, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x2e, 0x46, + 0x69, 0x65, 0x6c, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, + 0x64, 0x73, 0x12, 0x5e, 0x0a, 0x08, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x42, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x46, - 0x69, 0x65, 0x6c, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, - 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x5e, - 0x0a, 0x08, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x42, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, - 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x1a, 0x4d, - 0x0a, 0x0b, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, - 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, - 0x28, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, + 0x69, 0x65, 0x6c, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x65, 0x73, 0x1a, 0x4d, 0x0a, 0x0b, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, + 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, + 0x01, 0x1a, 0x57, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x6e, 0x67, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xfc, 0x02, 0x0a, 0x1b, 0x47, + 0x65, 0x74, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x56, 0x32, 0x12, 0x4c, 0x0a, 0x08, 0x6d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x66, + 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x47, 0x65, 0x74, + 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, + 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x52, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x66, 0x65, 0x61, 0x73, + 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x6e, 0x6c, + 0x69, 0x6e, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x56, 0x32, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x56, 0x65, 0x63, + 0x74, 0x6f, 0x72, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x1a, 0xba, 0x01, 0x0a, + 0x0d, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x56, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x2a, + 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x56, 0x61, 0x6c, - 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x71, 0x0a, - 0x0d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, - 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x4a, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x34, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, - 0x47, 0x65, 0x74, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, - 0x22, 0x5b, 0x0a, 0x0b, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, - 0x0b, 0x0a, 0x07, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, - 0x50, 0x52, 0x45, 0x53, 0x45, 0x4e, 0x54, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x4e, 0x55, 0x4c, - 0x4c, 0x5f, 0x56, 0x41, 0x4c, 0x55, 0x45, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x4e, 0x4f, 0x54, - 0x5f, 0x46, 0x4f, 0x55, 0x4e, 0x44, 0x10, 0x03, 0x12, 0x13, 0x0a, 0x0f, 0x4f, 0x55, 0x54, 0x53, - 0x49, 0x44, 0x45, 0x5f, 0x4d, 0x41, 0x58, 0x5f, 0x41, 0x47, 0x45, 0x10, 0x04, 0x2a, 0x6f, 0x0a, - 0x10, 0x46, 0x65, 0x61, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, - 0x65, 0x12, 0x1e, 0x0a, 0x1a, 0x46, 0x45, 0x41, 0x53, 0x54, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x49, - 0x4e, 0x47, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x10, - 0x00, 0x12, 0x1d, 0x0a, 0x19, 0x46, 0x45, 0x41, 0x53, 0x54, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x49, - 0x4e, 0x47, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4f, 0x4e, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x01, - 0x12, 0x1c, 0x0a, 0x18, 0x46, 0x45, 0x41, 0x53, 0x54, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x49, 0x4e, - 0x47, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x42, 0x41, 0x54, 0x43, 0x48, 0x10, 0x02, 0x32, 0xea, - 0x01, 0x0a, 0x0e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x12, 0x6c, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x73, 0x74, 0x53, 0x65, 0x72, - 0x76, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x29, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, - 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x73, - 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, - 0x69, 0x6e, 0x67, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x6a, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x46, 0x65, 0x61, 0x74, - 0x75, 0x72, 0x65, 0x73, 0x56, 0x32, 0x12, 0x29, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, - 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, - 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x56, - 0x32, 0x1a, 0x28, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, - 0x67, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, - 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x5e, 0x0a, 0x13, 0x66, - 0x65, 0x61, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, - 0x6e, 0x67, 0x42, 0x0f, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x41, 0x50, 0x49, 0x50, 0x72, - 0x6f, 0x74, 0x6f, 0x5a, 0x36, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x66, 0x65, 0x61, 0x73, 0x74, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, - 0x73, 0x64, 0x6b, 0x2f, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x66, 0x65, - 0x61, 0x73, 0x74, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x75, 0x65, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x36, 0x0a, 0x08, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x66, + 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x08, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x65, 0x73, 0x12, 0x45, 0x0a, 0x10, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x54, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x73, 0x22, 0x64, 0x0a, 0x21, 0x47, 0x65, 0x74, + 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x3f, + 0x0a, 0x0d, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, + 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x4c, 0x69, 0x73, + 0x74, 0x52, 0x0c, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x2a, + 0x5b, 0x0a, 0x0b, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0b, + 0x0a, 0x07, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x50, + 0x52, 0x45, 0x53, 0x45, 0x4e, 0x54, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x4e, 0x55, 0x4c, 0x4c, + 0x5f, 0x56, 0x41, 0x4c, 0x55, 0x45, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x4e, 0x4f, 0x54, 0x5f, + 0x46, 0x4f, 0x55, 0x4e, 0x44, 0x10, 0x03, 0x12, 0x13, 0x0a, 0x0f, 0x4f, 0x55, 0x54, 0x53, 0x49, + 0x44, 0x45, 0x5f, 0x4d, 0x41, 0x58, 0x5f, 0x41, 0x47, 0x45, 0x10, 0x04, 0x32, 0xe8, 0x01, 0x0a, + 0x0e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, + 0x6c, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x6e, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x29, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x73, 0x74, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x2a, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, + 0x67, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, + 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x68, 0x0a, + 0x11, 0x47, 0x65, 0x74, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x73, 0x12, 0x27, 0x2e, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x6e, 0x67, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x46, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x66, 0x65, + 0x61, 0x73, 0x74, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x47, 0x65, 0x74, 0x4f, + 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x56, 0x32, 0x42, 0x5e, 0x0a, 0x13, 0x66, 0x65, 0x61, 0x73, 0x74, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x42, 0x0f, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x41, 0x50, 0x49, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x5a, + 0x36, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x66, 0x65, 0x61, 0x73, + 0x74, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, 0x73, 0x64, 0x6b, 0x2f, + 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x66, 0x65, 0x61, 0x73, 0x74, 0x2f, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -865,52 +990,62 @@ func file_feast_serving_ServingService_proto_rawDescGZIP() []byte { return file_feast_serving_ServingService_proto_rawDescData } -var file_feast_serving_ServingService_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_feast_serving_ServingService_proto_msgTypes = make([]protoimpl.MessageInfo, 13) +var file_feast_serving_ServingService_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_feast_serving_ServingService_proto_msgTypes = make([]protoimpl.MessageInfo, 17) var file_feast_serving_ServingService_proto_goTypes = []interface{}{ - (FeastServingType)(0), // 0: feast.serving.FeastServingType - (GetOnlineFeaturesResponse_FieldStatus)(0), // 1: feast.serving.GetOnlineFeaturesResponse.FieldStatus - (*GetFeastServingInfoRequest)(nil), // 2: feast.serving.GetFeastServingInfoRequest - (*GetFeastServingInfoResponse)(nil), // 3: feast.serving.GetFeastServingInfoResponse - (*FeatureReferenceV2)(nil), // 4: feast.serving.FeatureReferenceV2 - (*GetOnlineFeaturesRequestV2)(nil), // 5: feast.serving.GetOnlineFeaturesRequestV2 - (*FeatureList)(nil), // 6: feast.serving.FeatureList - (*GetOnlineFeaturesRequest)(nil), // 7: feast.serving.GetOnlineFeaturesRequest - (*GetOnlineFeaturesResponse)(nil), // 8: feast.serving.GetOnlineFeaturesResponse - (*GetOnlineFeaturesRequestV2_EntityRow)(nil), // 9: feast.serving.GetOnlineFeaturesRequestV2.EntityRow - nil, // 10: feast.serving.GetOnlineFeaturesRequestV2.EntityRow.FieldsEntry - nil, // 11: feast.serving.GetOnlineFeaturesRequest.EntitiesEntry - (*GetOnlineFeaturesResponse_FieldValues)(nil), // 12: feast.serving.GetOnlineFeaturesResponse.FieldValues - nil, // 13: feast.serving.GetOnlineFeaturesResponse.FieldValues.FieldsEntry - nil, // 14: feast.serving.GetOnlineFeaturesResponse.FieldValues.StatusesEntry - (*timestamppb.Timestamp)(nil), // 15: google.protobuf.Timestamp - (*types.Value)(nil), // 16: feast.types.Value - (*types.RepeatedValue)(nil), // 17: feast.types.RepeatedValue + (FieldStatus)(0), // 0: feast.serving.FieldStatus + (*GetFeastServingInfoRequest)(nil), // 1: feast.serving.GetFeastServingInfoRequest + (*GetFeastServingInfoResponse)(nil), // 2: feast.serving.GetFeastServingInfoResponse + (*FeatureReferenceV2)(nil), // 3: feast.serving.FeatureReferenceV2 + (*GetOnlineFeaturesRequestV2)(nil), // 4: feast.serving.GetOnlineFeaturesRequestV2 + (*FeatureList)(nil), // 5: feast.serving.FeatureList + (*GetOnlineFeaturesRequest)(nil), // 6: feast.serving.GetOnlineFeaturesRequest + (*GetOnlineFeaturesResponse)(nil), // 7: feast.serving.GetOnlineFeaturesResponse + (*GetOnlineFeaturesResponseV2)(nil), // 8: feast.serving.GetOnlineFeaturesResponseV2 + (*GetOnlineFeaturesResponseMetadata)(nil), // 9: feast.serving.GetOnlineFeaturesResponseMetadata + (*GetOnlineFeaturesRequestV2_EntityRow)(nil), // 10: feast.serving.GetOnlineFeaturesRequestV2.EntityRow + nil, // 11: feast.serving.GetOnlineFeaturesRequestV2.EntityRow.FieldsEntry + nil, // 12: feast.serving.GetOnlineFeaturesRequest.EntitiesEntry + nil, // 13: feast.serving.GetOnlineFeaturesRequest.RequestContextEntry + (*GetOnlineFeaturesResponse_FieldValues)(nil), // 14: feast.serving.GetOnlineFeaturesResponse.FieldValues + nil, // 15: feast.serving.GetOnlineFeaturesResponse.FieldValues.FieldsEntry + nil, // 16: feast.serving.GetOnlineFeaturesResponse.FieldValues.StatusesEntry + (*GetOnlineFeaturesResponseV2_FeatureVector)(nil), // 17: feast.serving.GetOnlineFeaturesResponseV2.FeatureVector + (*timestamppb.Timestamp)(nil), // 18: google.protobuf.Timestamp + (*types.Value)(nil), // 19: feast.types.Value + (*types.RepeatedValue)(nil), // 20: feast.types.RepeatedValue } var file_feast_serving_ServingService_proto_depIdxs = []int32{ - 0, // 0: feast.serving.GetFeastServingInfoResponse.type:type_name -> feast.serving.FeastServingType - 4, // 1: feast.serving.GetOnlineFeaturesRequestV2.features:type_name -> feast.serving.FeatureReferenceV2 - 9, // 2: feast.serving.GetOnlineFeaturesRequestV2.entity_rows:type_name -> feast.serving.GetOnlineFeaturesRequestV2.EntityRow - 6, // 3: feast.serving.GetOnlineFeaturesRequest.features:type_name -> feast.serving.FeatureList - 11, // 4: feast.serving.GetOnlineFeaturesRequest.entities:type_name -> feast.serving.GetOnlineFeaturesRequest.EntitiesEntry - 12, // 5: feast.serving.GetOnlineFeaturesResponse.field_values:type_name -> feast.serving.GetOnlineFeaturesResponse.FieldValues - 15, // 6: feast.serving.GetOnlineFeaturesRequestV2.EntityRow.timestamp:type_name -> google.protobuf.Timestamp - 10, // 7: feast.serving.GetOnlineFeaturesRequestV2.EntityRow.fields:type_name -> feast.serving.GetOnlineFeaturesRequestV2.EntityRow.FieldsEntry - 16, // 8: feast.serving.GetOnlineFeaturesRequestV2.EntityRow.FieldsEntry.value:type_name -> feast.types.Value - 17, // 9: feast.serving.GetOnlineFeaturesRequest.EntitiesEntry.value:type_name -> feast.types.RepeatedValue - 13, // 10: feast.serving.GetOnlineFeaturesResponse.FieldValues.fields:type_name -> feast.serving.GetOnlineFeaturesResponse.FieldValues.FieldsEntry - 14, // 11: feast.serving.GetOnlineFeaturesResponse.FieldValues.statuses:type_name -> feast.serving.GetOnlineFeaturesResponse.FieldValues.StatusesEntry - 16, // 12: feast.serving.GetOnlineFeaturesResponse.FieldValues.FieldsEntry.value:type_name -> feast.types.Value - 1, // 13: feast.serving.GetOnlineFeaturesResponse.FieldValues.StatusesEntry.value:type_name -> feast.serving.GetOnlineFeaturesResponse.FieldStatus - 2, // 14: feast.serving.ServingService.GetFeastServingInfo:input_type -> feast.serving.GetFeastServingInfoRequest - 5, // 15: feast.serving.ServingService.GetOnlineFeaturesV2:input_type -> feast.serving.GetOnlineFeaturesRequestV2 - 3, // 16: feast.serving.ServingService.GetFeastServingInfo:output_type -> feast.serving.GetFeastServingInfoResponse - 8, // 17: feast.serving.ServingService.GetOnlineFeaturesV2:output_type -> feast.serving.GetOnlineFeaturesResponse - 16, // [16:18] is the sub-list for method output_type - 14, // [14:16] is the sub-list for method input_type - 14, // [14:14] is the sub-list for extension type_name - 14, // [14:14] is the sub-list for extension extendee - 0, // [0:14] is the sub-list for field type_name + 3, // 0: feast.serving.GetOnlineFeaturesRequestV2.features:type_name -> feast.serving.FeatureReferenceV2 + 10, // 1: feast.serving.GetOnlineFeaturesRequestV2.entity_rows:type_name -> feast.serving.GetOnlineFeaturesRequestV2.EntityRow + 5, // 2: feast.serving.GetOnlineFeaturesRequest.features:type_name -> feast.serving.FeatureList + 12, // 3: feast.serving.GetOnlineFeaturesRequest.entities:type_name -> feast.serving.GetOnlineFeaturesRequest.EntitiesEntry + 13, // 4: feast.serving.GetOnlineFeaturesRequest.request_context:type_name -> feast.serving.GetOnlineFeaturesRequest.RequestContextEntry + 14, // 5: feast.serving.GetOnlineFeaturesResponse.field_values:type_name -> feast.serving.GetOnlineFeaturesResponse.FieldValues + 9, // 6: feast.serving.GetOnlineFeaturesResponseV2.metadata:type_name -> feast.serving.GetOnlineFeaturesResponseMetadata + 17, // 7: feast.serving.GetOnlineFeaturesResponseV2.results:type_name -> feast.serving.GetOnlineFeaturesResponseV2.FeatureVector + 5, // 8: feast.serving.GetOnlineFeaturesResponseMetadata.feature_names:type_name -> feast.serving.FeatureList + 18, // 9: feast.serving.GetOnlineFeaturesRequestV2.EntityRow.timestamp:type_name -> google.protobuf.Timestamp + 11, // 10: feast.serving.GetOnlineFeaturesRequestV2.EntityRow.fields:type_name -> feast.serving.GetOnlineFeaturesRequestV2.EntityRow.FieldsEntry + 19, // 11: feast.serving.GetOnlineFeaturesRequestV2.EntityRow.FieldsEntry.value:type_name -> feast.types.Value + 20, // 12: feast.serving.GetOnlineFeaturesRequest.EntitiesEntry.value:type_name -> feast.types.RepeatedValue + 20, // 13: feast.serving.GetOnlineFeaturesRequest.RequestContextEntry.value:type_name -> feast.types.RepeatedValue + 15, // 14: feast.serving.GetOnlineFeaturesResponse.FieldValues.fields:type_name -> feast.serving.GetOnlineFeaturesResponse.FieldValues.FieldsEntry + 16, // 15: feast.serving.GetOnlineFeaturesResponse.FieldValues.statuses:type_name -> feast.serving.GetOnlineFeaturesResponse.FieldValues.StatusesEntry + 19, // 16: feast.serving.GetOnlineFeaturesResponse.FieldValues.FieldsEntry.value:type_name -> feast.types.Value + 0, // 17: feast.serving.GetOnlineFeaturesResponse.FieldValues.StatusesEntry.value:type_name -> feast.serving.FieldStatus + 19, // 18: feast.serving.GetOnlineFeaturesResponseV2.FeatureVector.values:type_name -> feast.types.Value + 0, // 19: feast.serving.GetOnlineFeaturesResponseV2.FeatureVector.statuses:type_name -> feast.serving.FieldStatus + 18, // 20: feast.serving.GetOnlineFeaturesResponseV2.FeatureVector.event_timestamps:type_name -> google.protobuf.Timestamp + 1, // 21: feast.serving.ServingService.GetFeastServingInfo:input_type -> feast.serving.GetFeastServingInfoRequest + 6, // 22: feast.serving.ServingService.GetOnlineFeatures:input_type -> feast.serving.GetOnlineFeaturesRequest + 2, // 23: feast.serving.ServingService.GetFeastServingInfo:output_type -> feast.serving.GetFeastServingInfoResponse + 8, // 24: feast.serving.ServingService.GetOnlineFeatures:output_type -> feast.serving.GetOnlineFeaturesResponseV2 + 23, // [23:25] is the sub-list for method output_type + 21, // [21:23] is the sub-list for method input_type + 21, // [21:21] is the sub-list for extension type_name + 21, // [21:21] is the sub-list for extension extendee + 0, // [0:21] is the sub-list for field type_name } func init() { file_feast_serving_ServingService_proto_init() } @@ -1004,6 +1139,30 @@ func file_feast_serving_ServingService_proto_init() { } } file_feast_serving_ServingService_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetOnlineFeaturesResponseV2); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_serving_ServingService_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetOnlineFeaturesResponseMetadata); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_feast_serving_ServingService_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetOnlineFeaturesRequestV2_EntityRow); i { case 0: return &v.state @@ -1015,7 +1174,7 @@ func file_feast_serving_ServingService_proto_init() { return nil } } - file_feast_serving_ServingService_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + file_feast_serving_ServingService_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetOnlineFeaturesResponse_FieldValues); i { case 0: return &v.state @@ -1027,6 +1186,18 @@ func file_feast_serving_ServingService_proto_init() { return nil } } + file_feast_serving_ServingService_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetOnlineFeaturesResponseV2_FeatureVector); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } file_feast_serving_ServingService_proto_msgTypes[5].OneofWrappers = []interface{}{ (*GetOnlineFeaturesRequest_FeatureService)(nil), @@ -1037,8 +1208,8 @@ func file_feast_serving_ServingService_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_feast_serving_ServingService_proto_rawDesc, - NumEnums: 2, - NumMessages: 13, + NumEnums: 1, + NumMessages: 17, NumExtensions: 0, NumServices: 1, }, @@ -1067,8 +1238,8 @@ const _ = grpc.SupportPackageIsVersion6 type ServingServiceClient interface { // Get information about this Feast serving. GetFeastServingInfo(ctx context.Context, in *GetFeastServingInfoRequest, opts ...grpc.CallOption) (*GetFeastServingInfoResponse, error) - // Get online features (v2) synchronously. - GetOnlineFeaturesV2(ctx context.Context, in *GetOnlineFeaturesRequestV2, opts ...grpc.CallOption) (*GetOnlineFeaturesResponse, error) + // Get online features synchronously. + GetOnlineFeatures(ctx context.Context, in *GetOnlineFeaturesRequest, opts ...grpc.CallOption) (*GetOnlineFeaturesResponseV2, error) } type servingServiceClient struct { @@ -1088,9 +1259,9 @@ func (c *servingServiceClient) GetFeastServingInfo(ctx context.Context, in *GetF return out, nil } -func (c *servingServiceClient) GetOnlineFeaturesV2(ctx context.Context, in *GetOnlineFeaturesRequestV2, opts ...grpc.CallOption) (*GetOnlineFeaturesResponse, error) { - out := new(GetOnlineFeaturesResponse) - err := c.cc.Invoke(ctx, "/feast.serving.ServingService/GetOnlineFeaturesV2", in, out, opts...) +func (c *servingServiceClient) GetOnlineFeatures(ctx context.Context, in *GetOnlineFeaturesRequest, opts ...grpc.CallOption) (*GetOnlineFeaturesResponseV2, error) { + out := new(GetOnlineFeaturesResponseV2) + err := c.cc.Invoke(ctx, "/feast.serving.ServingService/GetOnlineFeatures", in, out, opts...) if err != nil { return nil, err } @@ -1101,8 +1272,8 @@ func (c *servingServiceClient) GetOnlineFeaturesV2(ctx context.Context, in *GetO type ServingServiceServer interface { // Get information about this Feast serving. GetFeastServingInfo(context.Context, *GetFeastServingInfoRequest) (*GetFeastServingInfoResponse, error) - // Get online features (v2) synchronously. - GetOnlineFeaturesV2(context.Context, *GetOnlineFeaturesRequestV2) (*GetOnlineFeaturesResponse, error) + // Get online features synchronously. + GetOnlineFeatures(context.Context, *GetOnlineFeaturesRequest) (*GetOnlineFeaturesResponseV2, error) } // UnimplementedServingServiceServer can be embedded to have forward compatible implementations. @@ -1112,8 +1283,8 @@ type UnimplementedServingServiceServer struct { func (*UnimplementedServingServiceServer) GetFeastServingInfo(context.Context, *GetFeastServingInfoRequest) (*GetFeastServingInfoResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetFeastServingInfo not implemented") } -func (*UnimplementedServingServiceServer) GetOnlineFeaturesV2(context.Context, *GetOnlineFeaturesRequestV2) (*GetOnlineFeaturesResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetOnlineFeaturesV2 not implemented") +func (*UnimplementedServingServiceServer) GetOnlineFeatures(context.Context, *GetOnlineFeaturesRequest) (*GetOnlineFeaturesResponseV2, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetOnlineFeatures not implemented") } func RegisterServingServiceServer(s *grpc.Server, srv ServingServiceServer) { @@ -1138,20 +1309,20 @@ func _ServingService_GetFeastServingInfo_Handler(srv interface{}, ctx context.Co return interceptor(ctx, in, info, handler) } -func _ServingService_GetOnlineFeaturesV2_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GetOnlineFeaturesRequestV2) +func _ServingService_GetOnlineFeatures_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetOnlineFeaturesRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(ServingServiceServer).GetOnlineFeaturesV2(ctx, in) + return srv.(ServingServiceServer).GetOnlineFeatures(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/feast.serving.ServingService/GetOnlineFeaturesV2", + FullMethod: "/feast.serving.ServingService/GetOnlineFeatures", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ServingServiceServer).GetOnlineFeaturesV2(ctx, req.(*GetOnlineFeaturesRequestV2)) + return srv.(ServingServiceServer).GetOnlineFeatures(ctx, req.(*GetOnlineFeaturesRequest)) } return interceptor(ctx, in, info, handler) } @@ -1165,8 +1336,8 @@ var _ServingService_serviceDesc = grpc.ServiceDesc{ Handler: _ServingService_GetFeastServingInfo_Handler, }, { - MethodName: "GetOnlineFeaturesV2", - Handler: _ServingService_GetOnlineFeaturesV2_Handler, + MethodName: "GetOnlineFeatures", + Handler: _ServingService_GetOnlineFeatures_Handler, }, }, Streams: []grpc.StreamDesc{}, diff --git a/sdk/go/protos/feast/storage/Redis.pb.go b/sdk/go/protos/feast/storage/Redis.pb.go index 08ee629b7c..8fff34e517 100644 --- a/sdk/go/protos/feast/storage/Redis.pb.go +++ b/sdk/go/protos/feast/storage/Redis.pb.go @@ -16,7 +16,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v3.11.2 +// protoc v3.17.3 // source: feast/storage/Redis.proto package storage diff --git a/sdk/go/protos/feast/types/Field.pb.go b/sdk/go/protos/feast/types/Field.pb.go index 73f46bb1ac..c529d76153 100644 --- a/sdk/go/protos/feast/types/Field.pb.go +++ b/sdk/go/protos/feast/types/Field.pb.go @@ -16,7 +16,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v3.11.2 +// protoc v3.17.3 // source: feast/types/Field.proto package types diff --git a/sdk/go/protos/feast/types/Value.pb.go b/sdk/go/protos/feast/types/Value.pb.go index 9ae2806d51..fe53c2ec29 100644 --- a/sdk/go/protos/feast/types/Value.pb.go +++ b/sdk/go/protos/feast/types/Value.pb.go @@ -16,7 +16,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 -// protoc v3.11.2 +// protoc v3.17.3 // source: feast/types/Value.proto package types diff --git a/sdk/go/request.go b/sdk/go/request.go index 360603b3a3..e6da10ff9b 100644 --- a/sdk/go/request.go +++ b/sdk/go/request.go @@ -3,6 +3,7 @@ package feast import ( "fmt" "github.com/feast-dev/feast/sdk/go/protos/feast/serving" + "github.com/feast-dev/feast/sdk/go/protos/feast/types" "strings" ) @@ -29,24 +30,44 @@ type OnlineFeaturesRequest struct { } // Builds the feast-specified request payload from the wrapper. -func (r OnlineFeaturesRequest) buildRequest() (*serving.GetOnlineFeaturesRequestV2, error) { - featureRefs, err := buildFeatureRefs(r.Features) +func (r OnlineFeaturesRequest) buildRequest() (*serving.GetOnlineFeaturesRequest, error) { + _, err := buildFeatureRefs(r.Features) if err != nil { return nil, err } + if len(r.Entities) == 0 { + return nil, fmt.Errorf("Entities must be provided") + } + + firstRow := r.Entities[0] + columnSize := len(firstRow) // build request entity rows from native entities - entityRows := make([]*serving.GetOnlineFeaturesRequestV2_EntityRow, len(r.Entities)) - for i, entity := range r.Entities { - entityRows[i] = &serving.GetOnlineFeaturesRequestV2_EntityRow{ - Fields: entity, + entityColumns := make(map[string][]*types.Value, columnSize) + for rowIdx, entityRow := range r.Entities { + for name, val := range entityRow { + if _, ok := entityColumns[name]; !ok { + entityColumns[name] = make([]*types.Value, len(r.Entities)) + } + + entityColumns[name][rowIdx] = val + } + } + + entities := make(map[string]*types.RepeatedValue, len(entityColumns)) + for column, values := range entityColumns { + entities[column] = &types.RepeatedValue{ + Val: values, } } - return &serving.GetOnlineFeaturesRequestV2{ - Features: featureRefs, - EntityRows: entityRows, - Project: r.Project, + return &serving.GetOnlineFeaturesRequest{ + Kind: &serving.GetOnlineFeaturesRequest_Features{ + Features: &serving.FeatureList{ + Val: r.Features, + }, + }, + Entities: entities, }, nil } @@ -84,9 +105,9 @@ func parseFeatureRef(featureRefStr string) (*serving.FeatureReferenceV2, error) // parse featuretable if specified if strings.Contains(featureRefStr, ":") { refSplit := strings.Split(featureRefStr, ":") - featureRef.FeatureTable, featureRefStr = refSplit[0], refSplit[1] + featureRef.FeatureViewName, featureRefStr = refSplit[0], refSplit[1] } - featureRef.Name = featureRefStr + featureRef.FeatureName = featureRefStr return &featureRef, nil } diff --git a/sdk/go/request_test.go b/sdk/go/request_test.go index 0e9b89d119..6beb15f7f7 100644 --- a/sdk/go/request_test.go +++ b/sdk/go/request_test.go @@ -13,7 +13,7 @@ func TestGetOnlineFeaturesRequest(t *testing.T) { tt := []struct { name string req OnlineFeaturesRequest - want *serving.GetOnlineFeaturesRequestV2 + want *serving.GetOnlineFeaturesRequest wantErr bool err error }{ @@ -30,34 +30,24 @@ func TestGetOnlineFeaturesRequest(t *testing.T) { }, Project: "driver_project", }, - want: &serving.GetOnlineFeaturesRequestV2{ - Features: []*serving.FeatureReferenceV2{ - { - FeatureTable: "driver", - Name: "driver_id", + want: &serving.GetOnlineFeaturesRequest{ + Kind: &serving.GetOnlineFeaturesRequest_Features{ + Features: &serving.FeatureList{ + Val: []string{"driver:driver_id"}, }, }, - EntityRows: []*serving.GetOnlineFeaturesRequestV2_EntityRow{ - { - Fields: map[string]*types.Value{ - "entity1": Int64Val(1), - "entity2": StrVal("bob"), + Entities: map[string]*types.RepeatedValue{ + "entity1": &types.RepeatedValue{ + Val: []*types.Value{ + Int64Val(1), Int64Val(1), Int64Val(1), }, }, - { - Fields: map[string]*types.Value{ - "entity1": Int64Val(1), - "entity2": StrVal("annie"), - }, - }, - { - Fields: map[string]*types.Value{ - "entity1": Int64Val(1), - "entity2": StrVal("jane"), + "entity2": &types.RepeatedValue{ + Val: []*types.Value{ + StrVal("bob"), StrVal("annie"), StrVal("jane"), }, }, }, - Project: "driver_project", }, wantErr: false, err: nil, diff --git a/sdk/go/response.go b/sdk/go/response.go index 7fa50761b6..49c8904ab7 100644 --- a/sdk/go/response.go +++ b/sdk/go/response.go @@ -19,51 +19,88 @@ var ( // OnlineFeaturesResponse is a wrapper around serving.GetOnlineFeaturesResponse. type OnlineFeaturesResponse struct { - RawResponse *serving.GetOnlineFeaturesResponse + RawResponse *serving.GetOnlineFeaturesResponseV2 } // Rows retrieves the result of the request as a list of Rows. func (r OnlineFeaturesResponse) Rows() []Row { - rows := make([]Row, len(r.RawResponse.FieldValues)) - for i, fieldValues := range r.RawResponse.FieldValues { - rows[i] = fieldValues.Fields + if len(r.RawResponse.Results) == 0 { + return []Row{} + } + + rowsCount := len(r.RawResponse.Results[0].Values) + rows := make([]Row, rowsCount) + for rowIdx := 0; rowIdx < rowsCount; rowIdx++ { + row := make(map[string]*types.Value) + for featureIdx := 0; featureIdx < len(r.RawResponse.Results); featureIdx++ { + row[r.RawResponse.Metadata.FeatureNames.Val[featureIdx]] = r.RawResponse.Results[featureIdx].Values[rowIdx] + } + + rows[rowIdx] = row } return rows } // Statuses retrieves field level status metadata for each row in Rows(). // Each status map returned maps status 1:1 to each returned row from Rows() -func (r OnlineFeaturesResponse) Statuses() []map[string]serving.GetOnlineFeaturesResponse_FieldStatus { - statuses := make([]map[string]serving.GetOnlineFeaturesResponse_FieldStatus, len(r.RawResponse.FieldValues)) - for i, fieldValues := range r.RawResponse.FieldValues { - statuses[i] = fieldValues.Statuses +func (r OnlineFeaturesResponse) Statuses() []map[string]serving.FieldStatus { + if len(r.RawResponse.Results) == 0 { + return []map[string]serving.FieldStatus{} + } + + rowsCount := len(r.RawResponse.Results[0].Statuses) + rows := make([]map[string]serving.FieldStatus, rowsCount) + + for rowIdx := 0; rowIdx < rowsCount; rowIdx++ { + row := make(map[string]serving.FieldStatus) + for featureIdx := 0; featureIdx < len(r.RawResponse.Results); featureIdx++ { + row[r.RawResponse.Metadata.FeatureNames.Val[featureIdx]] = r.RawResponse.Results[featureIdx].Statuses[rowIdx] + } + + rows[rowIdx] = row } - return statuses + return rows } // Int64Arrays retrieves the result of the request as a list of int64 slices. Any missing values will be filled // with the missing values provided. func (r OnlineFeaturesResponse) Int64Arrays(order []string, fillNa []int64) ([][]int64, error) { - rows := make([][]int64, len(r.RawResponse.FieldValues)) if len(fillNa) != len(order) { return nil, fmt.Errorf(ErrLengthMismatch, len(fillNa), len(order)) } - for i, fieldValues := range r.RawResponse.FieldValues { - rows[i] = make([]int64, len(order)) - for j, fname := range order { - value, exists := fieldValues.Fields[fname] + + if len(r.RawResponse.Results) == 0 { + return [][]int64{}, nil + } + + rowsCount := len(r.RawResponse.Results[0].Values) + rows := make([][]int64, rowsCount) + + featureNameToIdx := make(map[string]int) + + for idx, featureName := range r.RawResponse.Metadata.FeatureNames.Val { + featureNameToIdx[featureName] = idx + } + + for rowIdx := 0; rowIdx < rowsCount; rowIdx++ { + row := make([]int64, len(order)) + for idx, feature := range order { + featureIdx, exists := featureNameToIdx[feature] if !exists { - return nil, fmt.Errorf(ErrFeatureNotFound, fname) + return nil, fmt.Errorf(ErrFeatureNotFound, feature) } - valType := value.GetVal() + + valType := r.RawResponse.Results[featureIdx].Values[rowIdx].GetVal() if valType == nil { - rows[i][j] = fillNa[j] + row[idx] = fillNa[idx] } else if int64Val, ok := valType.(*types.Value_Int64Val); ok { - rows[i][j] = int64Val.Int64Val + row[idx] = int64Val.Int64Val } else { return nil, fmt.Errorf(ErrTypeMismatch, "int64") } } + + rows[rowIdx] = row } return rows, nil } @@ -71,26 +108,42 @@ func (r OnlineFeaturesResponse) Int64Arrays(order []string, fillNa []int64) ([][ // Float64Arrays retrieves the result of the request as a list of float64 slices. Any missing values will be filled // with the missing values provided. func (r OnlineFeaturesResponse) Float64Arrays(order []string, fillNa []float64) ([][]float64, error) { - rows := make([][]float64, len(r.RawResponse.FieldValues)) if len(fillNa) != len(order) { return nil, fmt.Errorf(ErrLengthMismatch, len(fillNa), len(order)) } - for i, records := range r.RawResponse.FieldValues { - rows[i] = make([]float64, len(order)) - for j, fname := range order { - value, exists := records.Fields[fname] + + if len(r.RawResponse.Results) == 0 { + return [][]float64{}, nil + } + + rowsCount := len(r.RawResponse.Results[0].Values) + rows := make([][]float64, rowsCount) + + featureNameToIdx := make(map[string]int) + + for idx, featureName := range r.RawResponse.Metadata.FeatureNames.Val { + featureNameToIdx[featureName] = idx + } + + for rowIdx := 0; rowIdx < rowsCount; rowIdx++ { + row := make([]float64, len(order)) + for idx, feature := range order { + featureIdx, exists := featureNameToIdx[feature] if !exists { - return nil, fmt.Errorf(ErrFeatureNotFound, fname) + return nil, fmt.Errorf(ErrFeatureNotFound, feature) } - valType := value.GetVal() + + valType := r.RawResponse.Results[featureIdx].Values[rowIdx].GetVal() if valType == nil { - rows[i][j] = fillNa[j] + row[idx] = fillNa[idx] } else if doubleVal, ok := valType.(*types.Value_DoubleVal); ok { - rows[i][j] = doubleVal.DoubleVal + row[idx] = doubleVal.DoubleVal } else { return nil, fmt.Errorf(ErrTypeMismatch, "float64") } } + + rows[rowIdx] = row } return rows, nil } diff --git a/sdk/go/response_test.go b/sdk/go/response_test.go index a617652745..e9a9bc1605 100644 --- a/sdk/go/response_test.go +++ b/sdk/go/response_test.go @@ -9,29 +9,28 @@ import ( ) var response = OnlineFeaturesResponse{ - RawResponse: &serving.GetOnlineFeaturesResponse{ - FieldValues: []*serving.GetOnlineFeaturesResponse_FieldValues{ + RawResponse: &serving.GetOnlineFeaturesResponseV2{ + Results: []*serving.GetOnlineFeaturesResponseV2_FeatureVector{ { - Fields: map[string]*types.Value{ - "featuretable1:feature1": Int64Val(1), - "featuretable1:feature2": {}, - }, - Statuses: map[string]serving.GetOnlineFeaturesResponse_FieldStatus{ - "featuretable1:feature1": serving.GetOnlineFeaturesResponse_PRESENT, - "featuretable1:feature2": serving.GetOnlineFeaturesResponse_NULL_VALUE, + Values: []*types.Value{Int64Val(1), Int64Val(2)}, + Statuses: []serving.FieldStatus{ + serving.FieldStatus_PRESENT, + serving.FieldStatus_PRESENT, }, }, { - Fields: map[string]*types.Value{ - "featuretable1:feature1": Int64Val(2), - "featuretable1:feature2": Int64Val(2), - }, - Statuses: map[string]serving.GetOnlineFeaturesResponse_FieldStatus{ - "featuretable1:feature1": serving.GetOnlineFeaturesResponse_PRESENT, - "featuretable1:feature2": serving.GetOnlineFeaturesResponse_PRESENT, + Values: []*types.Value{{}, Int64Val(2)}, + Statuses: []serving.FieldStatus{ + serving.FieldStatus_NULL_VALUE, + serving.FieldStatus_PRESENT, }, }, }, + Metadata: &serving.GetOnlineFeaturesResponseMetadata{ + FeatureNames: &serving.FeatureList{ + Val: []string{"featuretable1:feature1", "featuretable1:feature2"}, + }, + }, }, } @@ -53,14 +52,14 @@ func TestOnlineFeaturesResponseToRow(t *testing.T) { func TestOnlineFeaturesResponseoToStatuses(t *testing.T) { actual := response.Statuses() - expected := []map[string]serving.GetOnlineFeaturesResponse_FieldStatus{ + expected := []map[string]serving.FieldStatus{ { - "featuretable1:feature1": serving.GetOnlineFeaturesResponse_PRESENT, - "featuretable1:feature2": serving.GetOnlineFeaturesResponse_NULL_VALUE, + "featuretable1:feature1": serving.FieldStatus_PRESENT, + "featuretable1:feature2": serving.FieldStatus_NULL_VALUE, }, { - "featuretable1:feature1": serving.GetOnlineFeaturesResponse_PRESENT, - "featuretable1:feature2": serving.GetOnlineFeaturesResponse_PRESENT, + "featuretable1:feature1": serving.FieldStatus_PRESENT, + "featuretable1:feature2": serving.FieldStatus_PRESENT, }, } if len(expected) != len(actual) { diff --git a/sdk/python/feast/feature_store.py b/sdk/python/feast/feature_store.py index ce8125520e..da849784a9 100644 --- a/sdk/python/feast/feature_store.py +++ b/sdk/python/feast/feature_store.py @@ -65,6 +65,7 @@ from feast.online_response import OnlineResponse, _infer_online_entity_rows from feast.protos.feast.core.Registry_pb2 import Registry as RegistryProto from feast.protos.feast.serving.ServingService_pb2 import ( + FieldStatus, GetOnlineFeaturesRequestV2, GetOnlineFeaturesResponse, ) @@ -1215,9 +1216,7 @@ def _populate_odfv_dependencies( for row_idx, proto_value in enumerate(proto_values): result_row = result_rows[row_idx] result_row.fields[feature_name].CopyFrom(proto_value) - result_row.statuses[ - feature_name - ] = GetOnlineFeaturesResponse.FieldStatus.PRESENT + result_row.statuses[feature_name] = FieldStatus.PRESENT # Add data if odfv requests specific feature views as dependencies if len(grouped_odfv_refs) > 0: @@ -1303,9 +1302,7 @@ def _populate_result_rows_from_feature_view( if full_feature_names else feature_name ) - result_row.statuses[ - feature_ref - ] = GetOnlineFeaturesResponse.FieldStatus.NOT_FOUND + result_row.statuses[feature_ref] = FieldStatus.NOT_FOUND else: for feature_name in feature_data: feature_ref = ( @@ -1317,9 +1314,7 @@ def _populate_result_rows_from_feature_view( result_row.fields[feature_ref].CopyFrom( feature_data[feature_name] ) - result_row.statuses[ - feature_ref - ] = GetOnlineFeaturesResponse.FieldStatus.PRESENT + result_row.statuses[feature_ref] = FieldStatus.PRESENT def _augment_response_with_on_demand_transforms( self, @@ -1394,9 +1389,7 @@ def _augment_response_with_on_demand_transforms( result_row.fields[transformed_feature].CopyFrom( proto_values_by_column[transformed_feature][row_idx] ) - result_row.statuses[ - transformed_feature - ] = GetOnlineFeaturesResponse.FieldStatus.PRESENT + result_row.statuses[transformed_feature] = FieldStatus.PRESENT # Drop values that aren't needed unneeded_features = [ @@ -1507,7 +1500,7 @@ def _entity_row_to_field_values( result = GetOnlineFeaturesResponse.FieldValues() for k in row.fields: result.fields[k].CopyFrom(row.fields[k]) - result.statuses[k] = GetOnlineFeaturesResponse.FieldStatus.PRESENT + result.statuses[k] = FieldStatus.PRESENT return result