Skip to content

Commit 4de7faf

Browse files
authored
feat: Add gunicorn for serve with multiprocess (feast-dev#3636)
* feat: Add gunicorn for serve with multiprocess Signed-off-by: phil.park <bakjeeone@hotmail.com> * feat: Add gunicorn to requirements for ci Signed-off-by: phil.park <bakjeeone@hotmail.com> --------- Signed-off-by: phil.park <bakjeeone@hotmail.com>
1 parent bf740d2 commit 4de7faf

10 files changed

+81
-10
lines changed

sdk/python/feast/cli.py

+26-1
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,21 @@ def init_command(project_directory, minimal: bool, template: str):
649649
show_default=True,
650650
help="Disable logging served features",
651651
)
652+
@click.option(
653+
"--workers",
654+
"-w",
655+
type=click.INT,
656+
default=1,
657+
show_default=True,
658+
help="Number of worker",
659+
)
660+
@click.option(
661+
"--keep-alive-timeout",
662+
type=click.INT,
663+
default=5,
664+
show_default=True,
665+
help="Timeout for keep alive",
666+
)
652667
@click.pass_context
653668
def serve_command(
654669
ctx: click.Context,
@@ -657,11 +672,21 @@ def serve_command(
657672
type_: str,
658673
no_access_log: bool,
659674
no_feature_log: bool,
675+
workers: int,
676+
keep_alive_timeout: int,
660677
):
661678
"""Start a feature server locally on a given port."""
662679
store = create_feature_store(ctx)
663680

664-
store.serve(host, port, type_, no_access_log, no_feature_log)
681+
store.serve(
682+
host=host,
683+
port=port,
684+
type_=type_,
685+
no_access_log=no_access_log,
686+
no_feature_log=no_feature_log,
687+
workers=workers,
688+
keep_alive_timeout=keep_alive_timeout,
689+
)
665690

666691

667692
@cli.command("serve_transformations")

sdk/python/feast/feature_server.py

+31-4
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
import traceback
33
import warnings
44

5+
import gunicorn.app.base
56
import pandas as pd
6-
import uvicorn
77
from fastapi import FastAPI, HTTPException, Request, Response, status
88
from fastapi.logger import logger
99
from fastapi.params import Depends
@@ -137,8 +137,35 @@ def health():
137137
return app
138138

139139

140+
class FeastServeApplication(gunicorn.app.base.BaseApplication):
141+
def __init__(self, store: "feast.FeatureStore", **options):
142+
self._app = get_app(store=store)
143+
self._options = options
144+
super().__init__()
145+
146+
def load_config(self):
147+
for key, value in self._options.items():
148+
if key.lower() in self.cfg.settings and value is not None:
149+
self.cfg.set(key.lower(), value)
150+
151+
self.cfg.set("worker_class", "uvicorn.workers.UvicornWorker")
152+
153+
def load(self):
154+
return self._app
155+
156+
140157
def start_server(
141-
store: "feast.FeatureStore", host: str, port: int, no_access_log: bool
158+
store: "feast.FeatureStore",
159+
host: str,
160+
port: int,
161+
no_access_log: bool,
162+
workers: int,
163+
keep_alive_timeout: int,
142164
):
143-
app = get_app(store)
144-
uvicorn.run(app, host=host, port=port, access_log=(not no_access_log))
165+
FeastServeApplication(
166+
store=store,
167+
bind=f"{host}:{port}",
168+
accesslog=None if no_access_log else "-",
169+
workers=workers,
170+
keepalive=keep_alive_timeout,
171+
).run()

sdk/python/feast/feature_store.py

+10-2
Original file line numberDiff line numberDiff line change
@@ -2152,7 +2152,6 @@ def _get_feature_views_to_use(
21522152
allow_cache=False,
21532153
hide_dummy_entity: bool = True,
21542154
) -> Tuple[List[FeatureView], List[RequestFeatureView], List[OnDemandFeatureView]]:
2155-
21562155
fvs = {
21572156
fv.name: fv
21582157
for fv in [
@@ -2223,6 +2222,8 @@ def serve(
22232222
type_: str,
22242223
no_access_log: bool,
22252224
no_feature_log: bool,
2225+
workers: int,
2226+
keep_alive_timeout: int,
22262227
) -> None:
22272228
"""Start the feature consumption server locally on a given port."""
22282229
type_ = type_.lower()
@@ -2231,7 +2232,14 @@ def serve(
22312232
f"Python server only supports 'http'. Got '{type_}' instead."
22322233
)
22332234
# Start the python server
2234-
feature_server.start_server(self, host, port, no_access_log)
2235+
feature_server.start_server(
2236+
self,
2237+
host=host,
2238+
port=port,
2239+
no_access_log=no_access_log,
2240+
workers=workers,
2241+
keep_alive_timeout=keep_alive_timeout,
2242+
)
22352243

22362244
@log_exceptions_and_usage
22372245
def get_feature_server_endpoint(self) -> Optional[str]:

sdk/python/requirements/py3.10-ci-requirements.txt

+2
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,8 @@ grpcio-testing==1.54.2
326326
# via feast (setup.py)
327327
grpcio-tools==1.54.2
328328
# via feast (setup.py)
329+
gunicorn==20.1.0
330+
# via feast (setup.py)
329331
h11==0.14.0
330332
# via
331333
# httpcore

sdk/python/requirements/py3.10-requirements.txt

+2
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ grpcio==1.54.2
5959
# grpcio-reflection
6060
grpcio-reflection==1.54.2
6161
# via feast (setup.py)
62+
gunicorn==20.1.0
63+
# via feast (setup.py)
6264
h11==0.14.0
6365
# via
6466
# httpcore

sdk/python/requirements/py3.8-ci-requirements.txt

+2
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,8 @@ grpcio-testing==1.54.2
330330
# via feast (setup.py)
331331
grpcio-tools==1.54.2
332332
# via feast (setup.py)
333+
gunicorn==20.1.0
334+
# via feast (setup.py)
333335
h11==0.14.0
334336
# via
335337
# httpcore

sdk/python/requirements/py3.8-requirements.txt

+2
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ grpcio==1.54.2
5959
# grpcio-reflection
6060
grpcio-reflection==1.54.2
6161
# via feast (setup.py)
62+
gunicorn==20.1.0
63+
# via feast (setup.py)
6264
h11==0.14.0
6365
# via
6466
# httpcore

sdk/python/requirements/py3.9-ci-requirements.txt

+2
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,8 @@ grpcio-testing==1.54.2
326326
# via feast (setup.py)
327327
grpcio-tools==1.54.2
328328
# via feast (setup.py)
329+
gunicorn==20.1.0
330+
# via feast (setup.py)
329331
h11==0.14.0
330332
# via
331333
# httpcore

sdk/python/requirements/py3.9-requirements.txt

+2
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ grpcio==1.54.2
5959
# grpcio-reflection
6060
grpcio-reflection==1.54.2
6161
# via feast (setup.py)
62+
gunicorn==20.1.0
63+
# via feast (setup.py)
6264
h11==0.14.0
6365
# via
6466
# httpcore

setup.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
"typeguard==2.13.3",
7171
"fastapi>=0.68.0,<1",
7272
"uvicorn[standard]>=0.14.0,<1",
73+
"gunicorn",
7374
"dask>=2021.1.0",
7475
"bowler", # Needed for automatic repo upgrades
7576
# FastAPI does not correctly pull starlette dependency on httpx see thread(https://github.com/tiangolo/fastapi/issues/5656).
@@ -103,9 +104,7 @@
103104
"pyspark>=3.0.0,<4",
104105
]
105106

106-
TRINO_REQUIRED = [
107-
"trino>=0.305.0,<0.400.0", "regex"
108-
]
107+
TRINO_REQUIRED = ["trino>=0.305.0,<0.400.0", "regex"]
109108

110109
POSTGRES_REQUIRED = [
111110
"psycopg2-binary>=2.8.3,<3",

0 commit comments

Comments
 (0)