AI & ML/MLOps Study

[머신러닝 플랫폼 스터디] 챕터 4 — MLflow와 Feast로 실험과 피처 관리하기

백곰곰 2026. 10. 31. 17:36
728x90
반응형

머신러닝 모델을 훈련할 때는 어떤 데이터와 설정으로 실험했고, 어떤 결과가 나왔는지 기록해야 합니다. 이번 글에서는 『머신러닝 플랫폼 엔지니어링』 4챕터의 실습을 로컬 Kubernetes 클러스터에서 직접 진행한 과정과 결과를 정리합니다.

MLflow로 실험 결과를 기록하고 모델을 등록합니다. Feast에서는 피처를 정의하고 온라인 저장소에 적재한 뒤 SDK와 피처 서버로 조회합니다.

실습은 참고 자료에 링크한 블로그 글을 따라 진행했습니다. Kubernetes 버전과 로컬 포트 등은 사용한 Mac 환경에 맞게 조정했습니다.

1. 실습 환경

전체 구성

실습 환경은 로컬 Kubernetes 클러스터 하나에 필요한 서비스를 모두 올리는 방식입니다. 학습 코드는 로컬에서 실행하고, 파이프라인의 각 단계는 클러스터 안의 별도 Pod에서 실행합니다.

로컬 PC → kind 클러스터

JupyterLab
MLflow 클라이언트
Feast SDK
localhost:15000 · 실험 기록
MLflow 서버
메타데이터 기록
PostgreSQL
localhost:9000 (가이드 기본값) · 정의 기록 / 원천 데이터 읽기
SeaweedFS · S3 호환 API
   
localhost:6379 (가이드 기본값) · materialize 적재 / 조회
Redis · 온라인 스토어
   
브라우저
localhost:8080 (가이드 기본값) · 파이프라인 관리
Kubeflow Pipelines
단계별 실행
컴포넌트 Pod
피처 서버를 통한 HTTP 조회
HTTP 클라이언트
127.0.0.1:6566 · 조회 요청
Feast 피처 서버 · 로컬에서 실행
온라인 피처 조회
Redis · 온라인 스토어

설명을 위해 단순화한 구성도입니다.

사용한 도구와 버전

구분 도구 버전
클러스터 kind(Kubernetes in Docker) kind 0.22.0, 노드 Kubernetes v1.24.17
파이프라인 Kubeflow Pipelines(KFP) 서버 / SDK 2.4.1 / 2.9.0
실험 추적 MLflow 3.0.0 (백엔드 PostgreSQL)
피처 스토어 Feast 0.40.1 (온라인 스토어 Redis)
객체 저장소 SeaweedFS (S3 호환) 3.85
모델 scikit-learn, XGBoost 1.5.2, 1.7.6

Apple Silicon 맥에서 확인한 점

  • amd64 전용 이미지: KFP 2.4.1 서버 이미지 중 상당수가 amd64만 제공합니다. Docker Desktop의 Rosetta 에뮬레이션으로 실행했습니다. 직접 빌드하는 MLflow와 컴포넌트 이미지는 arm64로 빌드됩니다.
  • 포트 충돌: macOS의 AirPlay 수신기가 5000번 포트를 사용해서, MLflow는 로컬 15000번으로 포워딩했습니다.

노트북 실행 준비

이 실습에는 Docker Desktop, Git, Python 3.11, uv, kind, kubectl이 필요합니다. 아래 절차는 Apple Silicon Mac에서 진행한 구성을 기준으로 합니다. 다른 운영체제에서는 도구 설치 방법과 충돌하는 포트가 다를 수 있습니다.

이 실습 환경은 5장에서도 그대로 사용할 예정이므로 컴퓨터 자원을 넉넉히 준비합니다.

설치된 도구와 Docker의 실행 상태는 다음 명령으로 확인할 수 있습니다. 도구가 없다면 각 프로젝트의 공식 설치 안내에 따라 먼저 준비합니다.

git --version
python3.11 --version
uv --version
kind version
kubectl version --client
docker info

실습 환경과 4장 코드는 각각 다음 공개 저장소에서 받았습니다. 코드 버전에 따라 실습 내용이 달라질 수 있어, 이번 실습에서 사용한 커밋을 지정했습니다.

git clone https://github.com/johklo/ml-platform-book-env.git
cd ml-platform-book-env
git checkout e9048a3cdc32da84782be3682617754db3fe8f5f

mkdir -p upstream
git clone https://github.com/practical-mlops/chapter-4.git upstream/chapter-4
git -C upstream/chapter-4 checkout b67a8228336781c933dd68bb7dc520bd484c3371

첫 번째 저장소에는 실습 환경과 노트북이 있고, 두 번째 저장소에는 책의 4장 코드와 데이터가 있습니다. 이후 명령은 ml-platform-book-env 디렉터리를 기준으로 실행합니다.

내려받은 bookenv.py, 매니페스트와 노트북에서 실행할 코드를 먼저 확인합니다. 설치와 기동 명령은 로컬에 패키지와 컨테이너 이미지를 내려받고 Kubernetes 리소스를 생성합니다.

이어서 Python 가상환경과 패키지를 준비합니다. 아래 명령은 uv가 설치되어 있는 경우입니다.

uv venv .venv-book --python 3.11
uv pip install --python .venv-book -r book/requirements.txt
source .venv-book/bin/activate

이번 실습에서는 책에 맞춰 클러스터 생성 전에 cluster/kind.yaml의 노드 이미지를 kindest/node:v1.24.17로 변경하고, 해당 노드 버전을 지원하는 kind 0.22.0과 kubectl 1.24.17을 준비했습니다. 환경 저장소의 기본 설정과 다른 부분입니다. 기존 Kubernetes 환경을 사용하고 있다면 별도의 실습용 kubeconfig를 사용하고, 명령을 실행하기 전 현재 context가 실습 클러스터인지 확인합니다.

실습용 kubeconfig는 저장소 안에 별도로 둘 수 있습니다. 저장소 루트에서 다음을 실행하고, kubectl이나 실습 클러스터 관련 명령을 실행하는 새 터미널마다 같은 설정을 적용합니다.

mkdir -p .kube
export KUBECONFIG="$PWD/.kube/config"

Docker Desktop을 실행한 상태에서 실습용 클러스터와 서비스를 구성합니다.

python bookenv.py up
python bookenv.py status

서비스가 준비되면 실습 클러스터의 노드와 Pod 상태를 확인합니다. kubectl이 실습용 kubeconfig를 바라보는 터미널에서 실행합니다.

kubectl config current-context
kubectl get nodes
kubectl get pods -A

노드의 Kubernetes 버전과 MLflow·Redis·객체 저장소 Pod의 상태를 확인한 뒤 다음 단계로 넘어갑니다. Pod가 아직 시작 중이면 준비될 때까지 기다리고, 오류가 계속되면 해당 Pod의 이벤트와 로그를 확인합니다.

실습에서 MLflow는 포트 충돌을 피하기 위해 기본 로컬 포트 5000 대신 15000으로 변경했습니다. 포트 포워딩을 실행하기 전에 bookenv.py의 전달 설정 5000:5000을 15000:5000으로, MLflow 노트북의 로컬 접속 주소를 http://localhost:15000으로 바꿨습니다. 콜론 앞은 로컬 포트이고 뒤는 클러스터 서비스의 포트입니다.

다음으로 별도 터미널에서 포트 포워딩을 실행하고, 노트북을 사용하는 동안 유지합니다.

python bookenv.py forward

포트 포워딩을 실행한 뒤 브라우저에서 http://localhost:15000의 MLflow 화면과 http://localhost:8888/buckets/의 저장소 화면을 엽니다. 저장소 화면의 New Folder로 MLflow용 mlflow-datasets, mlflow-artifacts와 Feast용 feature-data-sets, feature-registry 버킷을 준비합니다.

노트북은 S3 호환 저장소와 Redis에 접근할 수 있어야 합니다. 실습 환경의 접속 설정을 확인하고 필요한 비밀값은 운영체제 자격 증명 저장소 등 안전한 경로로 주입합니다. 비밀번호나 키를 노트북, 블로그, 로그에 복사하지 않습니다. 이 글은 비밀값 주입 방법까지 검증한 실행 가이드는 아니므로, 해당 환경에 맞는 안전한 설정이 준비되어야 다음 단계로 진행할 수 있습니다.

가상환경을 활성화한 다른 터미널에서 JupyterLab을 실행합니다.

source .venv-book/bin/activate
jupyter lab --notebook-dir book --port 8890

JupyterLab에서는 .venv-book 가상환경의 Python 커널을 선택합니다. ch04/01_mlflow_tracking.ipynb을 먼저 실행해 실험과 모델 등록을 확인하고, 이어서 ch04/02_feast_feature_store.ipynb에서 피처 등록과 조회를 진행합니다. 피처 서버의 HTTP 조회는 노트북과 별도로 서버를 띄워 확인합니다.

MLflow 노트북에서는 입력 데이터 경로와 추적 서버 주소를 확인한 뒤 데이터 탐색, 모델 훈련·평가, 모델 등록 셀을 순서대로 실행합니다. 실행 후 MLflow의 income-classifier Experiment에 EDA Run과 모델 Run이 보이는지 확인합니다.

Feast 노트북에서는 원천 Parquet 파일을 feature-data-sets 버킷에 올린 뒤 피처 정의를 준비합니다. 이어서 feast apply로 정의를 등록하고 과거 시점 조회, feast materialize, 온라인 조회를 순서대로 실행합니다. 원천 데이터의 기간에 맞춰 적재했는지 확인해야 온라인 조회에서 값을 얻을 수 있습니다.

환경 구성과 버전별 설정의 상세 절차는 참고 자료의 실습 가이드에서 확인할 수 있습니다.


2. 실험 추적: MLflow

실습에서 풀어볼 문제

실습에서는 Adult 소득 데이터의 고용 형태, 학력, 직업 등의 정보를 이용해 연 소득이 50,000달러를 초과하는지 분류합니다. 모델에 입력하는 정보는 피처, 예측할 소득 구간은 타깃입니다.

이번 노트북에서 읽은 데이터는 30,162행이며, 입력에 사용하는 범주형 열 8개와 정답을 나타내는 Target 열 1개로 구성되어 있습니다.

의사결정 트리, 랜덤 포레스트, XGBoost 모델을 각각 훈련하고 평가 결과를 MLflow에 남깁니다. 세 모델의 결과를 살펴본 뒤 하나를 등록합니다.

노트북이 하는 일

4챕터 첫 번째 노트북은 소득 데이터를 탐색하고, 모델 세 개를 학습해 MLflow에 기록한 뒤, 가장 좋은 모델을 모델 레지스트리(Model Registry)에 등록합니다.

모델 실험

소득 데이터 탐색 · 플롯
범주형 데이터 변환 · 데이터 분할
3개 모델 훈련
정확도 · AUC 평가
MLflow 기록 · 모델 등록
4장 노트북에서 수행하는 실험의 주요 순서입니다.

MLflow에는 Experiment 아래에 Run이 쌓입니다. 이번 실습의 구성은 다음과 같습니다.

구분 이번 실습에서 기록한 내용
Experiment income-classifier
EDA Run 탐색 그래프 8개
모델 Run Decision Tree, Random Forest, XGBoost 훈련 및 평가 결과

Experiment는 관련 실험을 묶는 단위이고, Run은 개별 실행을 기록하는 단위입니다. Run에는 설정값인 파라미터, 정확도와 같은 지표, 그래프·모델 파일 같은 아티팩트를 남길 수 있습니다.

실습에서는 먼저 추적 서버와 Experiment를 지정하고, Run 안에서 결과를 기록했습니다. 아래는 코드의 핵심 부분을 발췌한 것입니다.

mlflow.set_tracking_uri("http://localhost:15000")
mlflow.set_experiment("income-classifier")

with mlflow.start_run() as run:
    # 모델 훈련과 평가는 이 블록 안에서 수행
    mlflow.log_metric("roc_auc_score_test", roc_auc_score_test)
    mlflow.sklearn.log_model(tree, "income-classifier")

set_experiment()는 기록할 실험을 선택하고, start_run()은 하나의 실행을 시작합니다. log_metric()은 평가 지표를, log_model()은 훈련된 모델을 기록합니다. 이 발췌 코드에서 tree와 점수는 앞서 훈련·계산했다고 가정합니다.

MLflow의 income-classifier Experiment와 EDA Run 목록입니다.

플롯으로 데이터 살펴보기

플롯(Plot)은 데이터를 눈으로 볼 수 있게 그린 그래프입니다. 실습에서는 범주형 열마다 소득 구간의 분포를 막대그래프로 그립니다. 색은 소득 구간을 구분하고, position='fill'은 각 막대를 같은 길이로 맞춰 범주별 구성 비율을 비교하게 합니다. coord_flip()은 축을 바꿔 긴 범주 이름을 읽기 쉽게 합니다. 생성한 그래프는 MLflow에 아티팩트로 기록합니다.

 

MLflow에 기록한 학력별 소득 구간 비율 플롯입니다. 색은 각 소득 구간을 나타냅니다.

범주형 데이터를 모델 입력으로 바꾸기

고용 형태나 학력처럼 값이 몇 가지 범주로 나뉘는 데이터를 범주형 변수라고 합니다. 예를 들어 학력이 고졸·대졸·대학원 중 하나라면 다음처럼 해당 여부를 열로 바꿀 수 있습니다.

학력 대졸 여부 대학원 여부
고졸 0 0
대졸 1 0
대학원 0 1

각 열은 특정 범주에 해당하면 1, 아니면 0인 더미 변수입니다. True와 False로 같은 해당 여부를 표현할 수도 있습니다. 불리언은 참·거짓을 담는 자료형이고, 지시 변수는 조건에 해당하는지 표시하는 역할을 뜻합니다. 이 예시에서는 고졸 열을 생략했습니다. 대졸과 대학원 여부가 모두 0이면 고졸을 뜻합니다.

범주마다 해당 여부를 0과 1로 나타내는 방법을 원-핫 인코딩(One-hot Encoding)이라고 합니다. 모든 범주에 열을 만들면 한 행에서 해당하는 범주의 열만 1이 됩니다.

실습에서는 pd.get_dummies(..., drop_first=True, dtype=float)로 범주별 열을 만들고 첫 범주는 생략합니다. 결과는 0.0과 1.0으로 표현됩니다. 훈련과 추론에서 입력 열의 구성과 순서가 일치하는지도 중요합니다.

모델링 라이브러리

라이브러리 역할 이번 실습
scikit-learn 머신러닝 모델, 데이터 분할, 평가지표 제공 Decision Tree와 Random Forest를 학습하고 모델을 평가
XGBoost 트리를 순차적으로 결합하는 부스팅 라이브러리 XGBoost 분류 모델 학습
TensorFlow 신경망을 비롯한 머신러닝 모델 구축·학습 이번 노트북에서는 사용하지 않음
PyTorch 신경망을 비롯한 머신러닝 모델 구축·학습 이번 노트북에서는 사용하지 않음

모델별 학습 방식

세 모델 모두 훈련 세트로만 학습하고, 훈련·테스트 세트 양쪽에서 AUC와 정확도를 계산해 기록합니다.

데이터를 훈련용과 테스트용으로 나눌 때는 순서를 무작위로 섞습니다. 난수 시드(Random Seed)는 이런 무작위 작업의 출발점입니다. 같은 데이터와 조건에서 시드를 같은 값으로 고정하면 같은 방식으로 데이터를 다시 나눌 수 있습니다.

이번 실습에서는 데이터 분할에 시드를 지정하지 않았습니다. 따라서 셀을 다시 실행하면 훈련·테스트 데이터 구성이 달라져 점수도 달라질 수 있습니다. 분할의 시드를 고정하더라도 모델 학습에 사용되는 다른 무작위 요소까지 자동으로 고정되는 것은 아닙니다.

실습에서 데이터를 나눈 코드는 다음과 같습니다. train_size=0.80은 전체 데이터의 80%를 훈련용으로 쓰고, shuffle=True는 분할 전에 데이터를 섞는다는 뜻입니다.

X_train, X_test, y_train, y_test = train_test_split(
    dummyfied_df.reindex(columns=col_list, fill_value=0),
    target,
    train_size=0.80,
    shuffle=True,
)

random_state가 없으므로 데이터를 나누는 방식이 실행마다 달라질 수 있습니다.

예를 들어 Random Forest는 다음과 같이 훈련용 데이터로 학습합니다. 테스트 데이터는 학습에 넣지 않고 학습한 모델을 평가할 때 사용합니다.

forest = RandomForestClassifier()
forest.fit(X_train, y_train.ravel())

roc_auc_score_test = roc_auc_score(
    y_test == 1,
    forest.predict_proba(X_test)[:, 1],
)
test_accuracy = forest.score(X_test, y_test)

predict_proba(X_test)[:, 1]은 각 입력이 두 번째 클래스에 속할 예측 확률을 가져옵니다. 이 실습에서는 이를 테스트 AUC 계산에 사용했습니다.

모델 학습 방식 이 실습의 설정
Decision Tree 질문으로 데이터를 반복해서 나누는 트리 하나 기본값. 깊이 제한 없음
Random Forest 서로 다른 표본과 특성 일부로 만든 트리 여러 개의 평균 기본값. 트리 100개
XGBoost 트리를 순서대로 추가하며 앞선 오차를 보정하는 부스팅 첫 번째 학습: binary:logistic, 깊이 10, 학습률 1, 30라운드. 이후 다른 설정으로 재학습

정확도와 AUC

정확도는 최종 분류 결과 중 실제 정답과 일치한 비율입니다. ROC AUC는 분류 기준을 바꿔 가며 모델의 점수가 두 소득 구간을 얼마나 잘 구분하는지 살펴보는 지표입니다. 대체로 높은 점수를 받아야 하는 대상에 더 높은 점수를 주는지를 평가한다고 이해할 수 있습니다. 무작위 순서의 기대 기준은 0.5이고, 1에 가까울수록 구분이 잘 된 것입니다.

ROC AUC는 보통 예측 확률이나 연속적인 점수로 계산합니다. 이 실습의 XGBoost는 0 또는 1로 확정된 예측값을 사용하므로, 다른 모델의 확률 기반 AUC와 같은 조건으로 비교하기 어렵습니다.

실험 결과

모델 학습 AUC 테스트 AUC 학습 정확도 테스트 정확도
Decision Tree 0.9323 0.8156 0.8624 0.8221
Random Forest 0.9286 0.8503 0.8624 0.8245
XGBoost 0.7585 0.7173 0.8465 0.8218

위 값은 이번 실행의 결과이며, 데이터 분할과 환경에 따라 달라집니다.

테스트 AUC가 0.8을 넘는 두 Run을 필터링해 지표를 비교한 화면입니다.

  • Decision Tree는 깊이를 제한하지 않아 학습 AUC와 테스트 AUC의 차이가 큽니다.
  • Random Forest의 테스트 AUC가 가장 높아서 등록 대상이 됩니다.
  • XGBoost 셀은 먼저 binary:logistic 모델의 확률을 구하지만, 이후 binary:hinge 모델을 다시 학습해 예측값을 덮어씁니다. 최종 AUC는 두 번째 모델의 0 또는 1 출력으로 계산되므로 다른 두 모델의 확률 기반 AUC와 같은 조건으로 비교하기 어렵습니다. MLflow에 남은 파라미터는 첫 번째 모델의 값입니다.

이번 실습에서는 모델 훈련 Run의 이름을 지정하지 않아 MLflow가 임의의 이름을 생성했습니다. 어떤 알고리즘으로 학습한 Run인지 bootstrap, objective 등의 파라미터를 보고 구분했습니다. Run을 만들 때 run_name을 지정하면 알아보기 쉬운 이름을 사용할 수 있습니다.

실행 중 데이터셋의 원천 경로를 여러 방식으로 해석할 수 있다는 MLflow 경고도 나왔습니다. 노트북은 버킷 이름으로 데이터셋 출처를 기록하고 있으므로, 데이터가 저장됐는지뿐 아니라 MLflow에 표시된 출처가 의도한 경로인지도 따로 확인해야 합니다.

모델 등록과 stage

이번 실행에서는 등록 셀이 테스트 AUC가 0.8보다 큰 Run 중 가장 높은 Run을 골라 random-forest-classifier라는 이름으로 등록합니다. 5챕터 파이프라인이 Production stage의 모델을 찾기 때문에, 등록한 버전의 stage를 Production으로 지정했습니다. MLflow의 model stage는 사용 중단 예정(deprecated)이며, 이후 구성에서는 모델 별칭(alias)을 활용할 수 있습니다. 여기서는 5챕터 코드가 stage에 의존하므로 그대로 사용했습니다.

실습에서는 테스트 점수로 모델을 선택했습니다. 실제 모델 개발에서는 모델 선택에 검증 데이터를 사용하고, 테스트 데이터는 최종 평가용으로 남겨두는 편이 평가의 신뢰도를 높이는 데 도움이 됩니다.

실습의 모델 등록 코드는 테스트 AUC가 0.8보다 큰 Run을 점수 순으로 조회합니다. 아래는 등록에 필요한 부분을 발췌한 것입니다.

mlflow_client = MlflowClient()
experiment = mlflow_client.get_experiment_by_name("income-classifier")

run_object = mlflow_client.search_runs(
    experiment_ids=experiment.experiment_id,
    filter_string="metrics.roc_auc_score_test > 0.8",
    max_results=1,
    order_by=["metrics.roc_auc_score_test DESC"],
)[0]

model_uri = f"runs:/{run_object.info.run_id}/income-classifier"
registered = mlflow.register_model(model_uri, "random-forest-classifier")

조건에 맞는 Run이 없다면 [0]으로 결과를 선택할 수 없습니다. 이번 실행에서는 Random Forest Run이 선택되어 버전 1로 등록됐습니다.

다만 등록 이름은 random-forest-classifier로 고정되어 있고, 선택한 Run이 실제로 Random Forest인지 확인하지는 않습니다. 예를 들어 Decision Tree Run의 테스트 AUC가 가장 높았다면 Decision Tree 모델이 random-forest-classifier라는 이름으로 등록될 수 있습니다. 이번 실행에서는 선택된 모델과 등록 이름이 일치했습니다.

등록 과정에서는 코드에 지정한 아티팩트 경로를 찾지 못했다는 경고도 출력됐습니다. 이번 실행의 MLflow 3.0.0은 같은 Run에 연결된 모델을 대신 사용했고, 모델 버전 1이 생성됐습니다. 결과가 등록됐더라도 경고를 보고 실제 연결된 모델을 확인할 필요가 있습니다.

등록 결과는 다음과 같은 관계로 정리됩니다.

Registered Model: random-forest-classifier
└─ Version 1        stage: Production
     ├─ 모델 파일       (model.pkl 등)
     └─ 원본 Run        Random Forest Run
          └─ column_list.pkl   추론할 때 입력 열 순서를 맞추는 데 사용

3. 피처 스토어: Feast

피처 스토어 설계

피처 스토어는 모델에 쓰는 피처의 정의와 조회를 관리하는 구성입니다. 이번 실습에서 Feast는 피처를 직접 생성하는 단독 데이터베이스라기보다 원천 데이터와 온라인 저장소를 연결하는 역할을 합니다.

  • Entity: 피처가 어느 사용자에 속하는지 식별하는 기준
  • Feature View: 관련 피처와 그 원천을 묶은 정의
  • Registry: Entity와 Feature View 등의 정의를 보관하는 곳
  • Offline Store: 과거 시점의 피처를 조회하는 경로
  • Online Store: 온라인 조회용 피처 값을 제공하는 저장소

Feast UI에서 user_id Entity와 연결된 세 Feature View를 확인한 화면입니다.

화면에서 user_id는 피처를 연결할 대상을 구분하는 키입니다. 실습에서 등록한 Feature View는 다음 세 묶음입니다.

Feature View 포함된 피처 예시 원천 파일
demographic Sex, Race, Native_country demographic_features.parquet
relationship Relationship, Marital-Status relationship_features.parquet
occupation Workclass, Education, Occupation occupational_features.parquet

정의와 저장

Feast Entity · Feature View 정의
Registry · S3에 정의 보관

피처 데이터

S3 호환 저장소 · 과거 원천 데이터
Redis · 온라인 조회용 최신 값
피처 정의와 피처 값은 서로 다른 역할로 관리됩니다.

저장소 구성

구성 설정 실제 위치
오프라인 스토어 종류 file s3://feature-data-sets/…parquet
온라인 스토어 종류 redis 클러스터 안 Redis
레지스트리 s3://feature-registry/registry_local.db 같은 S3 호환 저장소

오프라인 스토어가 file이라는 것은 Parquet 파일을 읽어 pandas로 조인하는 가장 단순한 방식이라는 뜻입니다. 파일이 로컬 디스크가 아니라 S3 호환 저장소에 있을 뿐입니다.

오프라인에서 온라인으로

materialize는 오프라인 원천 데이터에서 지정한 기간의 피처를 읽어 온라인 저장소에 적재하는 과정입니다. 이번 실습에서는 Feast SDK가 SeaweedFS의 Parquet 데이터를 읽고, 사용자별 온라인 조회에 필요한 최신 피처 값을 Redis에 씁니다.

materialize 데이터 흐름

SeaweedFS · Parquet 원천 데이터
Feast SDK · 원천 데이터를 읽어 적재
Redis · 온라인 조회용 최신 피처
Feast가 원천 데이터를 읽어 온라인 저장소에 적재합니다. 두 저장소가 직접 데이터를 주고받는 흐름은 아닙니다.

피처 관리와 조회

feast apply · 정의 등록
과거 시점 조회 · 원천 데이터
materialize · Redis에 적재
SDK 또는 서버로 온라인 조회
과거 조회와 온라인 조회는 목적과 입력이 다릅니다.

실습에서는 피처 정의가 있는 디렉터리에서 다음 명령을 실행해 세 Feature View를 등록했습니다.

feast apply

실행 결과에는 relationship, occupation, demographic 세 Feature View의 인프라 준비 메시지가 출력됐습니다. 이 시점에는 피처 정의가 등록됐지만 온라인 조회용 데이터 적재는 아직 별도로 수행해야 합니다.

이어서 원천 데이터의 지정된 기간을 Redis에 적재했습니다. 두 시각은 이번 실습에서 사용한 범위입니다.

feast materialize 2022-09-16T00:00:00 2023-09-17T00:00:00

과거 시점과 온라인 피처를 조회할 때의 SDK 호출은 다음과 같습니다. 아래 코드는 두 조회 방식의 차이를 보여 주기 위한 축약 예시이며, store와 entity_df는 앞에서 준비한 객체입니다.

# entity_df에는 조회할 사용자 ID와 기준 시각이 들어 있습니다.
historical = store.get_historical_features(
    entity_df=entity_df,
    features=["demographic:Sex"],
).to_df()

# 온라인 조회에는 기준 시각 대신 사용자 ID를 전달합니다.
online = store.get_online_features(
    features=["demographic:Sex"],
    entity_rows=[{"user_id": "<조회할 사용자 ID>"}],
).to_df()

과거 조회는 기준 시각에 맞는 원천 피처를 찾아오고, 온라인 조회는 Redis에 적재된 값을 가져옵니다. 사용자 ID는 설명을 위한 자리표시자입니다.

과거 조회와 온라인 조회는 입력과 목적이 달라서, 둘의 결과를 비교해 조회 방식의 차이라고 결론 내리면 안 됩니다.

피처 서버

Python 코드에서는 Feast SDK의 get_online_features()를 직접 호출할 수 있습니다. SDK를 직접 사용하기 어려운 애플리케이션은 feast serve로 실행한 피처 서버에 HTTP 요청을 보내 조회할 수 있습니다. 이번 실습에서는 두 방식 모두 Redis의 온라인 피처 값을 가져옵니다.

이번 실습에서는 별도 터미널에서 피처 서버를 로컬 주소의 6566번 포트로 실행했습니다.

feast serve --host 127.0.0.1 --port 6566

서버는 피처 정의가 있는 디렉터리와 필요한 연결 설정을 사용합니다. 서버를 실행한 뒤 HTTP 클라이언트에서 /get-online-features로 요청해 응답을 확인했습니다.

다음은 요청 형식을 보여 주는 예시입니다. 사용자 ID는 실제 조회할 값으로 바꿔야 합니다.

curl -X POST http://127.0.0.1:6566/get-online-features \
  -H 'Content-Type: application/json' \
  -d '{"features":["demographic:Sex"],"entities":{"user_id":["<조회할 사용자 ID>"]}}'

여기서 실행한 온라인 피처 서버는 과거 시점 조회 API가 아닙니다. 오프라인 원천 데이터를 온라인 조회에 사용하려면 먼저 materialize로 Redis에 적재합니다. Feast에는 별도의 오프라인 피처 서버도 있습니다.

적재 후 서버에 demographic:Sex를 요청했을 때 HTTP 200과 함께 값 Male, 상태 PRESENT가 돌아왔습니다. 노트북의 SDK 조회 결과와 같았습니다.

피처 서버는 Mac의 별도 프로세스로 127.0.0.1:6566에서 실행했습니다. 4장 노트북은 SDK로 직접 조회하므로 피처 서버 실행이 필수는 아니며, 서버를 통한 HTTP 조회를 별도로 확인했습니다.


4. 정리

이번 글에서 확인한 흐름은 다음과 같습니다.

  • MLflow는 실험 결과를 기록하고, 등록한 모델 버전에 stage를 지정해 후속 작업이 찾을 수 있게 합니다.
  • Feast는 피처를 정의하고, 오프라인 데이터를 온라인 스토어로 옮겨 서버로 조회할 수 있게 합니다. 적재는 apply와 별개입니다.

참고 자료

728x90