11import logging
2+ import threading
23from concurrent import futures
4+ from typing import Optional
35
46import grpc
57import pandas as pd
68from grpc_health .v1 import health , health_pb2_grpc
79
810from feast .data_source import PushMode
9- from feast .errors import PushSourceNotFoundException
11+ from feast .errors import FeatureServiceNotFoundException , PushSourceNotFoundException
1012from feast .feature_store import FeatureStore
1113from feast .protos .feast .serving .GrpcServer_pb2 import (
1214 PushResponse ,
1618 GrpcFeatureServerServicer ,
1719 add_GrpcFeatureServerServicer_to_server ,
1820)
21+ from feast .protos .feast .serving .ServingService_pb2 import (
22+ GetOnlineFeaturesRequest ,
23+ GetOnlineFeaturesResponse ,
24+ )
25+
26+ logger = logging .getLogger (__name__ )
1927
2028
2129def parse (features ):
@@ -28,10 +36,16 @@ def parse(features):
2836class GrpcFeatureServer (GrpcFeatureServerServicer ):
2937 fs : FeatureStore
3038
31- def __init__ (self , fs : FeatureStore ):
39+ _shuting_down : bool = False
40+ _active_timer : Optional [threading .Timer ] = None
41+
42+ def __init__ (self , fs : FeatureStore , registry_ttl_sec : int = 5 ):
3243 self .fs = fs
44+ self .registry_ttl_sec = registry_ttl_sec
3345 super ().__init__ ()
3446
47+ self ._async_refresh ()
48+
3549 def Push (self , request , context ):
3650 try :
3751 df = parse (request .features )
@@ -53,19 +67,19 @@ def Push(self, request, context):
5367 to = to ,
5468 )
5569 except PushSourceNotFoundException as e :
56- logging .exception (str (e ))
70+ logger .exception (str (e ))
5771 context .set_code (grpc .StatusCode .INVALID_ARGUMENT )
5872 context .set_details (str (e ))
5973 return PushResponse (status = False )
6074 except Exception as e :
61- logging .exception (str (e ))
75+ logger .exception (str (e ))
6276 context .set_code (grpc .StatusCode .INTERNAL )
6377 context .set_details (str (e ))
6478 return PushResponse (status = False )
6579 return PushResponse (status = True )
6680
6781 def WriteToOnlineStore (self , request , context ):
68- logging .warning (
82+ logger .warning (
6983 "write_to_online_store is deprecated. Please consider using Push instead"
7084 )
7185 try :
@@ -76,16 +90,55 @@ def WriteToOnlineStore(self, request, context):
7690 allow_registry_cache = request .allow_registry_cache ,
7791 )
7892 except Exception as e :
79- logging .exception (str (e ))
93+ logger .exception (str (e ))
8094 context .set_code (grpc .StatusCode .INTERNAL )
8195 context .set_details (str (e ))
8296 return PushResponse (status = False )
8397 return WriteToOnlineStoreResponse (status = True )
8498
99+ def GetOnlineFeatures (self , request : GetOnlineFeaturesRequest , context ):
100+ if request .HasField ("feature_service" ):
101+ logger .info (f"Requesting feature service: { request .feature_service } " )
102+ try :
103+ features = self .fs .get_feature_service (
104+ request .feature_service , allow_cache = True
105+ )
106+ except FeatureServiceNotFoundException as e :
107+ logger .error (f"Feature service { request .feature_service } not found" )
108+ context .set_code (grpc .StatusCode .INTERNAL )
109+ context .set_details (str (e ))
110+ return GetOnlineFeaturesResponse ()
111+ else :
112+ features = list (request .features .val )
113+
114+ result = self .fs ._get_online_features (
115+ features ,
116+ request .entities ,
117+ request .full_feature_names ,
118+ ).proto
119+
120+ return result
121+
122+ def _async_refresh (self ):
123+ self .fs .refresh_registry ()
124+ if self ._shuting_down :
125+ return
126+ self ._active_timer = threading .Timer (self .registry_ttl_sec , self ._async_refresh )
127+ self ._active_timer .start ()
85128
86- def get_grpc_server (address : str , fs : FeatureStore , max_workers : int ):
129+
130+ def get_grpc_server (
131+ address : str ,
132+ fs : FeatureStore ,
133+ max_workers : int ,
134+ registry_ttl_sec : int ,
135+ ):
136+ logger .info (f"Initializing gRPC server on { address } " )
87137 server = grpc .server (futures .ThreadPoolExecutor (max_workers = max_workers ))
88- add_GrpcFeatureServerServicer_to_server (GrpcFeatureServer (fs ), server )
138+ add_GrpcFeatureServerServicer_to_server (
139+ GrpcFeatureServer (fs , registry_ttl_sec = registry_ttl_sec ),
140+ server ,
141+ )
89142 health_servicer = health .HealthServicer (
90143 experimental_non_blocking = True ,
91144 experimental_thread_pool = futures .ThreadPoolExecutor (max_workers = max_workers ),
0 commit comments