Skip to content

Commit 40bdd06

Browse files
Chen Zhilingwoop
authored andcommitted
0.3 dev serving api change (#253)
* [WIP] Simplify serving API signature * Add noop job service for online serving deployments * Fix generated job name * Ignore staleness if maxAge not set * Add comments to new types
1 parent 5bca082 commit 40bdd06

13 files changed

Lines changed: 278 additions & 390 deletions

File tree

core/src/main/java/feast/core/service/JobCoordinatorService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,6 @@ public void updateJobStatus(String jobId, JobStatus status) {
184184
public String createJobId(String featureSetName, String storeName) {
185185
String dateSuffix = String.valueOf(Instant.now().toEpochMilli());
186186
String jobId = String.format("%s-to-%s", featureSetName, storeName) + dateSuffix;
187-
return jobId.replaceAll("-", "_");
187+
return jobId.replaceAll("_", "-");
188188
}
189189
}

protos/feast/serving/ServingService.proto

Lines changed: 14 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,10 @@ message GetFeaturesRequest {
6767
// List of feature sets and their features that are being retrieved
6868
repeated FeatureSet feature_sets = 1;
6969

70-
// Dataset containing timestamp and entity id data. Used during retrieval of feature rows
71-
// and for joining feature rows into a final dataset
72-
EntityDataset entity_dataset = 2;
70+
// List of entity rows, containing entity id and timestamp data.
71+
// Used during retrieval of feature rows and for joining feature
72+
// rows into a final dataset
73+
repeated EntityRow entity_rows = 2;
7374

7475
message FeatureSet {
7576
// Feature set name
@@ -89,67 +90,24 @@ message GetFeaturesRequest {
8990
google.protobuf.Duration max_age = 4;
9091
}
9192

92-
message EntityDataset {
93-
// List of entity names contained within this incoming request. Each entity name is globally
94-
// unique within Feast. The user is assumed to have used the exact column name in their
95-
// EntityDataset if they are providing this dataset through a batch process.
96-
repeated string entity_names = 1;
9793

98-
// List of Unix epoch entity_timestamp and entity_id values
99-
repeated EntityDatasetRow entity_dataset_rows = 2;
100-
}
101-
102-
// EntityDatasetRow specifies:
103-
// - the timestamp range over which feature values should be retrieved (required for batch serving)
104-
// - the specific entity ids that should be retrieved (required for online serving)
105-
//
106-
// If there are duplicate entity ids for the same timestamp range, only the
107-
// one with the latest event_timestamp will be retrieved.
108-
//
109-
// Entity ids may be ommitted for batch features retrieval. In this case,
110-
// all entities with distinct entity ids within the valid timestamp range
111-
// will be retrieved.
112-
message EntityDatasetRow {
113-
// entity_timestamp is the upper bound of the timestamp range over
114-
// which the feature values should be retrieved.
115-
//
116-
// For online serving entity_timestamp is optional (ignored), as the
117-
// latest is always retrieved.
118-
//
119-
// The timestamp range is defined as follows:
120-
// entity_timestamp - max_age <= event_timestamp <= entity_timestamp
94+
message EntityRow {
95+
// Request timestamp of this row. This value will be used, together with maxAge,
96+
// to determine feature staleness.
12197
google.protobuf.Timestamp entity_timestamp = 1;
12298

123-
// The entity ids for which the feature values should be retrieved.
124-
//
125-
// The order of the values should follow that in entity_names in EntityDataset.
126-
// For online serving, it is required to specify entity_ids.
127-
// For batch serving, it is optional.
128-
repeated feast.types.Value entity_ids = 2;
99+
// Map containing mapping of entity name to entity value.
100+
map<string,feast.types.Value> fields = 2;
129101
}
130102
}
131103

132104
message GetOnlineFeaturesResponse {
133-
// A FeatureDataSet is returned for each feature set in the incoming request
134-
repeated FeatureDataset feature_datasets = 2;
135-
136-
// The FeatureDataSet contains information about the Feature Set in the incoming request,
137-
// as well as feature data that can be joined to the incoming EntityDataSet. The row count
138-
// for the returning FeatureDataSet will match that of the row count for the incoming
139-
// EntityDataSet.
140-
// If any of the keys do not have values, empty feature rows will be returned.
141-
message FeatureDataset {
142-
// Feature set name
143-
string name = 1;
144-
145-
// Feature set version
146-
int32 version = 2;
105+
repeated FieldValues field_values = 1;
147106

148-
// Each feature data set contains a list of feature rows. The timestamps within the row
149-
// are the original event timestamps from when that row was written to the backing store.
150-
// When these FeatureRows are joined to the EntityDataSetRows, the FeatureRow timestamps
151-
// will be dropped in favour of the EntityDataSetRow timestamp.
152-
repeated feast.types.FeatureRow feature_rows = 3;
107+
// TODO: update this comment
108+
// does not include timestamp, includes features and entities
109+
message FieldValues {
110+
map<string,feast.types.Value> fields = 1;
153111
}
154112
}
155113

serving/src/main/java/feast/serving/configuration/JobServiceConfig.java

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import feast.core.StoreProto.Store.StoreType;
99
import feast.serving.FeastProperties;
1010
import feast.serving.service.JobService;
11+
import feast.serving.service.NoopJobService;
1112
import feast.serving.service.RedisBackedJobService;
1213
import feast.serving.service.SpecService;
1314
import org.springframework.beans.factory.annotation.Autowired;
@@ -27,23 +28,15 @@ public JobServiceConfig(FeastProperties feastProperties) {
2728

2829
@Bean
2930
public JobService jobService(SpecService specService) {
30-
String jobStoreName = feastProperties.getJobStoreName();
31-
GetStoresResponse storesResponse =
32-
specService.getStores(
33-
GetStoresRequest.newBuilder()
34-
.setFilter(Filter.newBuilder().setName(jobStoreName).build())
35-
.build());
36-
37-
if (storesResponse.getStoreCount() < 1) {
38-
throw new IllegalArgumentException(
39-
String.format(
40-
"Cannot resolve Store from store name '%s'. Ensure the store name exists in Feast.",
41-
jobStoreName));
31+
String storeName = feastProperties.getStoreName();
32+
Store store = getStore(specService, storeName);
33+
if (store.getType() == StoreType.REDIS) {
34+
return new NoopJobService();
4235
}
4336

44-
assert storesResponse.getStoreCount() == 1;
45-
Store store = storesResponse.getStore(0);
46-
StoreType storeType = store.getType();
37+
String jobStoreName = feastProperties.getJobStoreName();
38+
Store jobStore = getStore(specService, jobStoreName);
39+
StoreType storeType = jobStore.getType();
4740
JobService jobService = null;
4841

4942
switch (storeType) {
@@ -63,4 +56,22 @@ public JobService jobService(SpecService specService) {
6356

6457
return jobService;
6558
}
59+
60+
private Store getStore(SpecService specService, String jobStoreName) {
61+
GetStoresResponse storesResponse =
62+
specService.getStores(
63+
GetStoresRequest.newBuilder()
64+
.setFilter(Filter.newBuilder().setName(jobStoreName).build())
65+
.build());
66+
67+
if (storesResponse.getStoreCount() < 1) {
68+
throw new IllegalArgumentException(
69+
String.format(
70+
"Cannot resolve Store from store name '%s'. Ensure the store name exists in Feast.",
71+
jobStoreName));
72+
}
73+
74+
assert storesResponse.getStoreCount() == 1;
75+
return storesResponse.getStore(0);
76+
}
6677
}

serving/src/main/java/feast/serving/configuration/ServingServiceConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public ServingService servingService(
7777
poolConfig.setMaxIdle(feastProperties.getRedisPoolMaxIdle());
7878
JedisPool jedisPool =
7979
new JedisPool(
80-
poolConfig, store.getRedisConfig().getHost(), store.getRedisConfig().getPort());
80+
poolConfig, redisConfig.getHost(), redisConfig.getPort());
8181
servingService = new RedisServingService(jedisPool, specService, tracer);
8282
break;
8383
case BIGQUERY:

serving/src/main/java/feast/serving/controller/ServingServiceGRpcController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public void getBatchFeatures(
7979
responseObserver.onError(e);
8080
}
8181
}
82-
82+
8383
@Override
8484
public void reloadJob(
8585
ReloadJobRequest request, StreamObserver<ReloadJobResponse> responseObserver) {

serving/src/main/java/feast/serving/service/BigQueryServingService.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import com.google.cloud.storage.Blob;
1111
import com.google.cloud.storage.Storage;
1212
import com.google.cloud.storage.Storage.BlobListOption;
13+
import com.google.common.collect.Lists;
1314
import feast.core.CoreServiceProto.GetFeatureSetsRequest;
1415
import feast.core.CoreServiceProto.GetFeatureSetsRequest.Filter;
1516
import feast.core.FeatureSetProto.FeatureSetSpec;
@@ -20,7 +21,7 @@
2021
import feast.serving.ServingAPIProto.GetFeastServingTypeRequest;
2122
import feast.serving.ServingAPIProto.GetFeastServingTypeResponse;
2223
import feast.serving.ServingAPIProto.GetFeaturesRequest;
23-
import feast.serving.ServingAPIProto.GetFeaturesRequest.EntityDatasetRow;
24+
import feast.serving.ServingAPIProto.GetFeaturesRequest.EntityRow;
2425
import feast.serving.ServingAPIProto.GetOnlineFeaturesResponse;
2526
import feast.serving.ServingAPIProto.JobStatus;
2627
import feast.serving.ServingAPIProto.JobType;
@@ -102,16 +103,16 @@ public GetBatchFeaturesResponse getBatchFeatures(GetFeaturesRequest getFeaturesR
102103
.asRuntimeException();
103104
}
104105

105-
if (getFeaturesRequest.getEntityDataset().getEntityDatasetRowsCount() < 1) {
106+
if (getFeaturesRequest.getEntityRowsCount() < 1) {
106107
throw Status.INVALID_ARGUMENT
107108
.withDescription(
108109
"entity_dataset_rows is required for batch retrieval in order to filter the retrieved entities.")
109110
.asRuntimeException();
110111
}
111112

112-
for (EntityDatasetRow entityDatasetRow :
113-
getFeaturesRequest.getEntityDataset().getEntityDatasetRowsList()) {
114-
if (entityDatasetRow.getEntityTimestamp().getSeconds() == 0) {
113+
for (EntityRow entityRow :
114+
getFeaturesRequest.getEntityRowsList()) {
115+
if (entityRow.getEntityTimestamp().getSeconds() == 0) {
115116
throw Status.INVALID_ARGUMENT
116117
.withDescription(
117118
"entity_timestamp field in entity_dataset_row is required for batch retrieval.")
@@ -123,8 +124,8 @@ public GetBatchFeaturesResponse getBatchFeatures(GetFeaturesRequest getFeaturesR
123124
BigQueryUtil.createQuery(
124125
getFeaturesRequest.getFeatureSetsList(),
125126
featureSetSpecs,
126-
getFeaturesRequest.getEntityDataset().getEntityNamesList(),
127-
getFeaturesRequest.getEntityDataset().getEntityDatasetRowsList(),
127+
Lists.newArrayList(getFeaturesRequest.getEntityRows(0).getFieldsMap().keySet()),
128+
getFeaturesRequest.getEntityRowsList(),
128129
datasetId);
129130
log.debug("Running BigQuery query: {}", query);
130131

serving/src/main/java/feast/serving/service/JobService.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ public interface JobService {
1313
* @param id job id
1414
* @return feast.serving.ServingAPIProto.Job
1515
*/
16-
public Optional<Job> get(String id);
16+
Optional<Job> get(String id);
1717

1818
/**
1919
* Update or create a job (if not exists)
2020
*
2121
* @param job feast.serving.ServingAPIProto.Job
2222
*/
23-
public void upsert(Job job);
23+
void upsert(Job job);
2424
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package feast.serving.service;
2+
3+
import feast.serving.ServingAPIProto.Job;
4+
import java.util.Optional;
5+
6+
// No-op implementation of the JobService, for online serving stores.
7+
public class NoopJobService implements JobService {
8+
9+
@Override
10+
public Optional<Job> get(String id) {
11+
return Optional.empty();
12+
}
13+
14+
@Override
15+
public void upsert(Job job) {
16+
17+
}
18+
}

0 commit comments

Comments
 (0)