호돌찌의 AI 연구소
article thumbnail

이번 글은 Feast Server를 생성하는데, CLI 환경위에서 진행합니다.

 

1. Local에서 Feast Server 실행하기

feast를 설치하고, init 후 새 dir을 만들어서 apply를 수행합니다.

pip install feast
feast init feature_repo
cd feature_repo
feast apply

 

마찬가지로 materialize-incremental을 수행합니다. 동작방식의 뜻은 마지막으로 materialize를 시작한 시점부터 최근까지 feature를 online store에 적재한다는 뜻이라고 저번 글에 이야길 했습니다.

feast materialize-incremental $(date +%Y-%m-%d)
feast serve

 

그럼 아마 아래와 같은 에러가 나타날 것 입니다. 

feast.errors.ExperimentalFeatureNotEnabled: 
You are attempting to use an experimental feature that is not enabled. 
Please run `feast alpha enable python_feature_server`

 

이 부분은 feast가 update 되면 없어질 문제입니다. 일단 시키는 대로 수행합니다. 

feast alpha enable python_feature_server

 

이제 local에서 서버가 띄워졌습니다. 새 터미널을 켜서 curl로 쿼리를 실행해보겠습니다.

curl -X POST \
  "http://localhost:6566/get-online-features" \
  -d '{
    "features": [
      "driver_hourly_stats:conv_rate",
      "driver_hourly_stats:acc_rate",
      "driver_hourly_stats:avg_daily_trips"
    ],
    "entities": {
      "driver_id": [1001, 1002, 1003]
    }
  }'

 

 

 

 

 

 

 

 

 

2. Docker Container로 Feast server 실행하기

우선 도커 이미지를 빌드합니다. 그 전에 새 폴더 하나 만들고 requirements.txt를 만듭니다.

mkdir -p docker
cd docker
sudo vim requirements.

 

requirements.txt는 아래처럼 구성합니다.

feast
scikit-learn
mlflow

 

이제 아래와 같은 Dockerfile을 생성합니다. run 부분을 보면 이전 글에서 했던 내용의 중복이라고 생각하면 됩니다.

# syntax=docker/dockerfile:1
FROM jupyter/base-notebook
WORKDIR /home/jovyan
COPY . /home/jovyan

RUN pip3 install -r requirements.txt

USER jovyan
RUN feast init feature_repo && \
		cd feature_repo && \
		feast apply && \
		feast materialize-incremental $(date +%Y-%m-%d) && \
		feast alpha enable python_feature_server

COPY feature_server.py /opt/conda/lib/python3.9/site-packages/feast/feature_server.py
CMD [ "/bin/sh", "-c", "cd /home/jovyan/feature_repo && feast serve"]

WORKDIR /home/jovyan

 

 

 

마지막 준비물인 feature_server.py 파일을 만듭니다.

import click
import uvicorn
from fastapi import FastAPI, HTTPException, Request
from fastapi.logger import logger
from google.protobuf.json_format import MessageToDict, Parse

import feast
from feast import proto_json
from feast.protos.feast.serving.ServingService_pb2 import GetOnlineFeaturesRequest
from feast.type_map import feast_value_type_to_python_type


def get_app(store: "feast.FeatureStore"):
    proto_json.patch()

    app = FastAPI()

    @app.post("/get-online-features")
    async def get_online_features(request: Request):
        try:
            # Validate and parse the request data into GetOnlineFeaturesRequest Protobuf object
            body = await request.body()
            request_proto = GetOnlineFeaturesRequest()
            Parse(body, request_proto)

            # Initialize parameters for FeatureStore.get_online_features(...) call
            if request_proto.HasField("feature_service"):
                features = store.get_feature_service(request_proto.feature_service)
            else:
                features = list(request_proto.features.val)

            full_feature_names = request_proto.full_feature_names

            batch_sizes = [len(v.val) for v in request_proto.entities.values()]
            num_entities = batch_sizes[0]
            if any(batch_size != num_entities for batch_size in batch_sizes):
                raise HTTPException(status_code=500, detail="Uneven number of columns")

            entity_rows = [
                {
                    k: feast_value_type_to_python_type(v.val[idx])
                    for k, v in request_proto.entities.items()
                }
                for idx in range(num_entities)
            ]

            response_proto = store.get_online_features(
                features, entity_rows, full_feature_names=full_feature_names
            ).proto

            # Convert the Protobuf object to JSON and return it
            return MessageToDict(  # type: ignore
                response_proto, preserving_proto_field_name=True, float_precision=18
            )
        except Exception as e:
            # Print the original exception on the server side
            logger.exception(e)
            # Raise HTTPException to return the error message to the client
            raise HTTPException(status_code=500, detail=str(e))

    return app


def start_server(store: "feast.FeatureStore", port: int):
    app = get_app(store)
    click.echo(
        "This is an "
        + click.style("experimental", fg="yellow", bold=True, underline=True)
        + " feature. It's intended for early testing and feedback, and could change without warnings in future releases."
    )
    uvicorn.run(app, host="0.0.0.0", port=port)

 

이제 재료들을 완성하였고, 도커 build를 하겠습니다. tag는 feast-docker로 하겠습니다.

docker build --tag feast-docker .

 

 

많은 시간이 걸립니다. 빌드가 전부 다 되었다면 이제 feast docker container를 run 하겠습니다.

docker run -d --name feast-jupyter -p 8888:8888 -p 6566:6566 -p 5001:5001 -e JUPYTER_TOKEN='password' \
-v "$PWD":/home/jovyan/jupyter \
--user root \
-it feast-docker:latest

docker ps -a

 

curl을 이용해 테스트를 해보겠습니다.

curl -X POST \
  "http://localhost:6566/get-online-features" \
  -d '{
    "features": [
      "driver_hourly_stats:conv_rate",
      "driver_hourly_stats:acc_rate",
      "driver_hourly_stats:avg_daily_trips"
    ],
    "entities": {
      "driver_id": [1001, 1002, 1003]
    }
  }'

 

정상적으로 수행되는 것을 알 수 있다.

 

만약에 jupyter lab으로 추가 실행하고 싶다면 아래 명령어를 수행하면 됩니다.

docker exec -it feast-jupyter start.sh jupyter lab &

 

 

 


https://bit.ly/37BpXiC

 

패스트캠퍼스 [직장인 실무교육]

프로그래밍, 영상편집, UX/UI, 마케팅, 데이터 분석, 엑셀강의, The RED, 국비지원, 기업교육, 서비스 제공.

fastcampus.co.kr

 

* 본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성되었습니다.

 

 

 


profile

호돌찌의 AI 연구소

@hotorch's AI Labs

포스팅이 도움이 되셨다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!