ML 모델을 노트북에서 학습하는 것과 프로덕션에서 안정적으로 운영하는 것은 전혀 다른 문제입니다. 연구 환경에서는 한 번 좋은 결과를 얻으면 끝이지만, 프로덕션에서는 데이터가 바뀌고, 모델이 낡아가고, 시스템이 장애를 일으킵니다. MLOps는 이 간극을 메우는 실천 체계입니다. 이 글에서는 데이터 수집부터 학습, 검증, 레지스트리 등록, 배포, 그리고 모니터링까지 각 단계의 핵심 원칙과 실무 도구를 단계별로 정리합니다.
MLOps 파이프라인 전체 구조
MLOps 파이프라인은 크게 여섯 단계로 구성됩니다. 각 단계는 독립적으로 실행될 수 있지만, 실제로는 오케스트레이터(Airflow, Kubeflow Pipelines, Prefect 등)가 이를 DAG(Directed Acyclic Graph) 형태로 묶어 자동 실행합니다.
| 단계 | 주요 활동 | 대표 도구 | 산출물 |
|---|---|---|---|
| 1. 데이터 수집/전처리 | 피처 엔지니어링, 데이터 검증 | Great Expectations, dbt, Feast | 버전된 데이터셋, 피처 스토어 |
| 2. 모델 학습 | 하이퍼파라미터 탐색, 실험 추적 | MLflow, W&B, Optuna | 학습된 모델 아티팩트, 메트릭 |
| 3. 모델 검증 | 성능 평가, 공정성/편향 검사 | Evidently AI, Deepchecks | 검증 리포트, 게이팅 결과 |
| 4. 모델 레지스트리 | 버전 관리, 스테이지 전환 | MLflow Registry, Vertex AI Registry | 버전 태그, 메타데이터 |
| 5. 배포 | 컨테이너화, 카나리/블루그린 | Seldon Core, BentoML, KServe | 서빙 엔드포인트 |
| 6. 모니터링 | 드리프트 감지, 성능 추적 | Evidently, Prometheus, Grafana | 알림, 재학습 트리거 |
재현성과 버전 관리: MLOps의 기반
MLOps의 가장 근본적인 요구사항은 재현성(Reproducibility)입니다. “지난달에 학습한 모델을 다시 만들 수 있는가?”라는 질문에 자신 있게 “Yes”라고 답할 수 있어야 합니다. 재현성을 위해서는 코드, 데이터, 환경, 하이퍼파라미터 네 가지를 모두 버전 관리해야 합니다.
코드는 Git으로 관리하되, 학습 실행과 커밋 해시를 연결해 두세요. 데이터는 DVC(Data Version Control)나 Delta Lake로 버전을 태깅하고, 학습에 사용한 데이터 스냅샷을 메타데이터로 기록합니다. 환경은 Docker 이미지로 고정하고, 이미지 다이제스트(SHA256)를 실험 메타데이터에 포함시킵니다. 하이퍼파라미터와 메트릭은 MLflow나 W&B 같은 실험 추적 도구에 자동으로 기록되도록 코드에 통합하세요.
# MLflow 실험 추적 통합 예시
import mlflow
import mlflow.pytorch
def train(config: dict):
mlflow.set_experiment("product-recommendation-v2")
with mlflow.start_run(tags={"git_commit": os.environ.get("GIT_SHA", "local")}):
# 하이퍼파라미터 자동 기록
mlflow.log_params(config)
model = build_model(config)
trainer = Trainer(model, config)
metrics = trainer.fit(train_dataset, val_dataset)
# 메트릭 기록
mlflow.log_metrics({
"val_auc": metrics["auc"],
"val_ndcg@10": metrics["ndcg"],
"train_loss": metrics["train_loss"],
})
# 모델 아티팩트 저장 (서명 포함)
signature = mlflow.models.infer_signature(
model_input=sample_input,
model_output=model.predict(sample_input)
)
mlflow.pytorch.log_model(
model, "model",
signature=signature,
registered_model_name="recommendation-model"
)
모델 검증과 레지스트리: 게이팅 메커니즘
새로 학습된 모델이 자동으로 프로덕션에 배포되어서는 안 됩니다. 모델 검증(Model Validation) 단계에서 여러 기준을 통과해야만 레지스트리에 “Staging” 또는 “Production” 스테이지로 승격됩니다. 이를 모델 게이팅(Model Gating)이라 합니다.
검증 체크리스트는 일반적으로 다음을 포함합니다. 첫째, 성능 회귀 방지: 현재 프로덕션 모델 대비 핵심 메트릭이 일정 수준 이상이어야 합니다(예: AUC 0.01 이상 개선 또는 동일 수준 유지). 둘째, 데이터 슬라이스 검증: 특정 인구통계, 지역, 기간 슬라이스에서 성능이 급격히 나빠지지 않는지 확인합니다. 셋째, 추론 지연 시간 검증: 새 모델이 서빙 SLO를 만족하는지 스테이징 환경에서 부하 테스트를 실행합니다.
# 자동 모델 게이팅 파이프라인 (Prefect 기반)
from prefect import flow, task
import mlflow
@task
def evaluate_model(run_id: str, threshold: dict) -> bool:
client = mlflow.MlflowClient()
run = client.get_run(run_id)
metrics = run.data.metrics
checks = {
"auc_threshold": metrics.get("val_auc", 0) >= threshold["min_auc"],
"ndcg_threshold": metrics.get("val_ndcg@10", 0) >= threshold["min_ndcg"],
"latency_ok": metrics.get("p99_latency_ms", 9999) <= threshold["max_p99_ms"],
}
failed = [k for k, v in checks.items() if not v]
if failed:
raise ValueError(f"모델 게이팅 실패: {failed}")
return True
@task
def promote_to_staging(run_id: str, model_name: str):
client = mlflow.MlflowClient()
# 가장 최근 버전 조회 후 Staging 승격
versions = client.search_model_versions(f"name='{model_name}'")
latest = max(versions, key=lambda v: int(v.version))
client.transition_model_version_stage(
name=model_name,
version=latest.version,
stage="Staging",
archive_existing_versions=False
)
@flow
def model_promotion_pipeline(run_id: str):
passed = evaluate_model(run_id, threshold={
"min_auc": 0.82, "min_ndcg": 0.31, "max_p99_ms": 150
})
if passed:
promote_to_staging(run_id, "recommendation-model")
배포 전략: 블루/그린과 카나리
모델을 레지스트리에서 가져와 서빙 환경에 배포할 때는 점진적 롤아웃(Progressive Rollout)이 안전합니다. 두 가지 주요 전략은 블루/그린 배포와 카나리 배포입니다.
블루/그린 배포는 구버전(Blue)과 신버전(Green) 서버를 동시에 운영하다가 로드밸런서를 한 번에 전환합니다. 롤백이 즉각적(로드밸런서 전환만 필요)이지만, GPU 비용이 일시적으로 2배가 됩니다. 카나리 배포는 전체 트래픽의 1~5%를 신버전으로 먼저 보내고, 에러율과 메트릭을 모니터링하면서 점진적으로 비율을 높입니다. Kubernetes에서는 Argo Rollouts나 Flagger가 이 자동화를 지원합니다. 카나리는 비용 효율적이지만, 신버전이 프로덕션 트래픽을 받는 동안 일부 사용자가 열화된 경험을 할 수 있다는 리스크가 있습니다.
모델 드리프트 감지와 재학습 트리거
배포 후 시간이 지나면 모델 성능이 저하됩니다. 이를 모델 드리프트(Model Drift)라 하며, 두 가지 원인으로 나뉩니다. 데이터 드리프트(Data Drift)는 입력 피처의 분포가 학습 시점과 달라지는 현상이고, 컨셉 드리프트(Concept Drift)는 입력과 레이블의 관계 자체가 변하는 현상입니다(예: 소비자 행동 패턴 변화).
드리프트 감지는 통계적 방법으로 수행합니다. 연속형 피처는 Kolmogorov-Smirnov 검정이나 Population Stability Index(PSI)를 사용합니다. 일반적으로 PSI > 0.2이면 심각한 드리프트로 판단합니다. 범주형 피처는 카이제곱 검정이나 JS 발산을 씁니다. 이런 검사를 Evidently AI 같은 도구로 자동화하고, 임계값 초과 시 Airflow DAG나 GitHub Actions를 통해 재학습 파이프라인을 자동 트리거하는 구조가 성숙한 MLOps 시스템의 표준입니다.
# Evidently AI를 활용한 데이터 드리프트 자동 감지
from evidently.report import Report
from evidently.metric_preset import DataDriftPreset, TargetDriftPreset
import pandas as pd
def run_drift_check(reference_df: pd.DataFrame,
current_df: pd.DataFrame,
psi_threshold: float = 0.2) -> dict:
report = Report(metrics=[
DataDriftPreset(drift_share=0.3), # 30% 이상 피처가 드리프트 시 알림
TargetDriftPreset(),
])
report.run(reference_data=reference_df, current_data=current_df)
result = report.as_dict()
drift_detected = result["metrics"][0]["result"]["dataset_drift"]
# PSI 계산 (별도 유틸)
psi_values = compute_psi(reference_df, current_df)
severe_drift_features = [f for f, psi in psi_values.items() if psi > psi_threshold]
return {
"dataset_drift": drift_detected,
"severe_drift_features": severe_drift_features,
"should_retrain": drift_detected or len(severe_drift_features) > 0,
"report_path": "drift_report.html",
}
# Airflow에서 호출 예시
def trigger_retraining_if_needed(**context):
result = run_drift_check(reference_df, current_df)
if result["should_retrain"]:
# 재학습 DAG를 프로그래매틱하게 트리거
from airflow.api.client.local_client import Client
c = Client(None, None)
c.trigger_dag(dag_id="model_training_pipeline",
conf={"trigger_reason": "drift_detected",
"drifted_features": result["severe_drift_features"]})
실전 팁: MLOps 도입 시 흔한 실수와 해결책
- 피처 스토어 없이 학습/서빙 분리: 학습 시 사용한 피처 계산 로직과 서빙 시 계산 로직이 달라지는 피처 스큐(Feature Skew)가 발생합니다. Feast나 Tecton 같은 피처 스토어로 계산 로직을 단일화하세요.
- 모델 레지스트리 없이 파일 공유: “최신 모델이 어디에 있는가?”를 팀 전체가 알 수 없게 됩니다. MLflow Registry나 SageMaker Model Registry로 단일 진실 공급원을 만드세요.
- 재학습 기준 없는 스케줄 재학습: 매주 재학습한다고 성능이 항상 유지되지는 않습니다. 드리프트 감지 기반 트리거와 함께, 재학습 후 반드시 게이팅을 통과해야만 배포되도록 설계하세요.
- 레이블 지연(Label Delay) 무시: 실시간 예측 결과의 실제 정답(레이블)은 며칠 또는 몇 주 후에나 수집됩니다. 모니터링 지표를 레이블 없이도 측정 가능한 대리 지표(proxy metrics)와 병행해서 설계하세요.
- 단일 환경 파이프라인: 개발/스테이징/프로덕션 환경을 파이프라인 코드 레벨에서 분리하지 않으면 실수로 프로덕션 데이터를 학습에 쓰거나, 스테이징 모델이 프로덕션에 배포되는 사고가 납니다.
마무리
MLOps 파이프라인은 한 번에 완성되지 않습니다. 보통 팀이 처음에는 수동 학습과 수동 배포로 시작하고, 문제가 반복될 때마다 자동화를 추가하는 방식으로 성숙해집니다. 데이터 버전 관리와 실험 추적을 먼저 도입하고, 이후 자동 재학습과 드리프트 모니터링을 추가하는 단계적 접근이 현실적입니다. 완벽한 파이프라인보다 지금 팀이 직면한 가장 큰 고통을 해결하는 파이프라인을 목표로 하는 것이 MLOps 도입의 핵심입니다.
자주 묻는 질문
Q. MLflow와 W&B 중 어떤 실험 추적 도구를 선택해야 하나요?
A. 온프레미스 또는 자체 관리 환경이 필요하다면 MLflow가 적합합니다. 오픈소스이고 다양한 ML 프레임워크와 통합이 잘 됩니다. W&B(Weights & Biases)는 SaaS 형태로 협업 기능과 시각화가 뛰어나며, 특히 딥러닝 실험 추적에 강점이 있습니다. 팀 규모가 작고 빠른 시작이 필요하다면 W&B, 데이터 보안 요건이 강하고 레지스트리까지 통합하려면 MLflow를 추천합니다.
Q. 모델 재학습은 얼마나 자주 해야 하나요?
A. 고정 스케줄보다 드리프트 감지 기반 트리거가 더 바람직합니다. 실무 기준으로는 PSI > 0.2이거나 서빙 품질 메트릭이 기준치 대비 5% 이상 하락할 때 재학습을 트리거합니다. 비즈니스 도메인에 따라 다르지만, 소비자 행동 관련 모델은 주간~월간, 금융 리스크 모델은 일간~주간 재학습이 일반적입니다. 재학습 비용도 함께 고려해 최적 주기를 실험적으로 결정하세요.
Q. 소규모 팀에서 MLOps 파이프라인을 어디서부터 시작해야 하나요?
A. 우선순위는 다음 순서를 권장합니다. (1) 실험 추적(MLflow 또는 W&B) — 가장 낮은 비용으로 즉각적인 효과. (2) 모델 레지스트리 — “현재 프로덕션 모델이 어느 버전인가”를 명확히. (3) CI/CD 통합 — GitHub Actions로 학습 코드 변경 시 자동 검증. 이 세 가지가 갖춰지면 드리프트 모니터링과 자동 재학습을 점진적으로 추가하세요. Kubeflow나 SageMaker Pipelines 같은 풀 플랫폼은 팀이 파이프라인의 가치를 확인한 후에 도입해도 늦지 않습니다.