Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Asynchronously refresh registry for the feast ui command #2672

Merged
merged 4 commits into from
May 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 29 additions & 3 deletions sdk/python/feast/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,16 +110,42 @@ def version():


@cli.command()
@click.option(
"--host",
"-h",
type=click.STRING,
default="0.0.0.0",
help="Specify a host for the server [default: 0.0.0.0]",
)
@click.option(
"--port",
"-p",
type=click.INT,
default=8888,
help="Specify a port for the server [default: 8888]",
)
@click.option(
"--registry_ttl_sec",
"-r",
help="Number of seconds after which the registry is refreshed. Default is 5 seconds.",
type=int,
default=5,
)
@click.pass_context
def ui(ctx: click.Context):
def ui(ctx: click.Context, host: str, port: int, registry_ttl_sec: int):
"""
Shows the Feast UI over the current directory
"""
repo = ctx.obj["CHDIR"]
cli_check_repo(repo)
store = FeatureStore(repo_path=str(repo))
repo_config = load_repo_config(repo)
store.serve_ui(registry_dump(repo_config, repo_path=repo))
# Pass in the registry_dump method to get around a circular dependency
store.serve_ui(
host=host,
port=port,
get_registry_dump=registry_dump,
registry_ttl_sec=registry_ttl_sec,
)


@cli.command()
Expand Down
14 changes: 12 additions & 2 deletions sdk/python/feast/feature_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from typing import (
TYPE_CHECKING,
Any,
Callable,
Dict,
Iterable,
List,
Expand Down Expand Up @@ -1995,14 +1996,23 @@ def get_feature_server_endpoint(self) -> Optional[str]:
return self._provider.get_feature_server_endpoint()

@log_exceptions_and_usage
def serve_ui(self, registry_dump: str) -> None:
def serve_ui(
self, host: str, port: int, get_registry_dump: Callable, registry_ttl_sec: int
) -> None:
"""Start the UI server locally"""
warnings.warn(
"The Feast UI is an experimental feature. "
"We do not guarantee that future changes will maintain backward compatibility.",
RuntimeWarning,
)
ui_server.start_server(self, registry_dump, self.config.project)
ui_server.start_server(
self,
host=host,
port=port,
get_registry_dump=get_registry_dump,
project_id=self.config.project,
registry_ttl_sec=registry_ttl_sec,
)

@log_exceptions_and_usage
def serve_transformations(self, port: int) -> None:
Expand Down
2 changes: 1 addition & 1 deletion sdk/python/feast/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"@elastic/datemath": "^5.0.3",
"@elastic/eui": "^57.0.0",
"@emotion/react": "^11.9.0",
"@feast-dev/feast-ui": "^0.20.2",
"@feast-dev/feast-ui": "^0.20.3",
adchia marked this conversation as resolved.
Show resolved Hide resolved
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.2.0",
"@testing-library/user-event": "^13.5.0",
Expand Down
48 changes: 43 additions & 5 deletions sdk/python/feast/ui_server.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import json
import threading
from typing import Callable, Optional

import pkg_resources
import uvicorn
Expand All @@ -9,7 +11,12 @@
import feast


def get_app(store: "feast.FeatureStore", registry_dump: str, project_id: str):
def get_app(
store: "feast.FeatureStore",
get_registry_dump: Callable,
project_id: str,
registry_ttl_secs: int,
):
ui_dir = pkg_resources.resource_filename(__name__, "ui/build/")

app = FastAPI()
Expand All @@ -22,9 +29,33 @@ def get_app(store: "feast.FeatureStore", registry_dump: str, project_id: str):
allow_headers=["*"],
)

# Asynchronously refresh registry, notifying shutdown and canceling the active timer if the app is shutting down
registry_json = ""
shutting_down = False
active_timer: Optional[threading.Timer] = None

def async_refresh():
store.refresh_registry()
nonlocal registry_json
registry_json = get_registry_dump(store.config, store.repo_path)
if shutting_down:
return
nonlocal active_timer
active_timer = threading.Timer(registry_ttl_secs, async_refresh)
active_timer.start()

@app.on_event("shutdown")
def shutdown_event():
nonlocal shutting_down
shutting_down = True
if active_timer:
active_timer.cancel()

async_refresh()

@app.get("/registry")
def read_registry():
return json.loads(registry_dump)
return json.loads(registry_json)

# Generate projects-list json that points to the current repo's project
# TODO(adchia): Enable users to also add project name + description fields in feature_store.yaml
Expand Down Expand Up @@ -59,6 +90,13 @@ def catch_all():
return app


def start_server(store: "feast.FeatureStore", registry_dump: str, project_id: str):
app = get_app(store, registry_dump, project_id)
uvicorn.run(app, host="0.0.0.0", port=8888)
def start_server(
store: "feast.FeatureStore",
host: str,
port: int,
get_registry_dump: Callable,
project_id: str,
registry_ttl_sec: int,
):
app = get_app(store, get_registry_dump, project_id, registry_ttl_sec)
uvicorn.run(app, host=host, port=port)
2 changes: 1 addition & 1 deletion ui/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@feast-dev/feast-ui",
"version": "0.20.2",
"version": "0.20.3",
"private": false,
"files": [
"dist"
Expand Down
7 changes: 0 additions & 7 deletions ui/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,6 @@ ReactDOM.render(
reactQueryClient={queryClient}
feastUIConfigs={{
tabsRegistry: tabsRegistry,
projectListPromise: fetch("http://0.0.0.0:8888/projects-list", {
headers: {
"Content-Type": "application/json",
},
}).then((res) => {
return res.json();
})
}}
/>
</React.StrictMode>,
Expand Down