MLOps 성숙도 모델 기반 파이프라인 아키텍처 설계

프로덕션 환경에서의 머신러닝 시스템 실패는 대부분 모델의 정확도 문제가 아닌, 인프라스트럭처와 운영 프로세스의 결함에서 기인합니다. Google의 "Hidden Technical Debt in Machine Learning Systems" 논문이 지적했듯, 실제 ML 코드 비중은 전체 시스템의 5% 미만입니다. 나머지 95%는 서빙 인프라, 모니터링, 데이터 파이프라인 검증 로직이 차지합니다. Jupyter Notebook 환경에서의 실험 코드를 그대로 운영 서버에 배포하는 'Level 0' 상태는 의존성 지옥(Dependency Hell)과 재현성(Reproducibility) 결여로 인해 필연적으로 장애를 유발합니다.

MLOps 성숙도 모델 레벨 1: 파이프라인 자동화 (CT)

수동 프로세스(Level 0)에서 벗어나는 첫 번째 단계는 지속적 학습(Continuous Training, CT) 파이프라인의 구축입니다. 이는 데이터 추출, 전처리, 모델 학습, 평가의 과정을 DAG(Directed Acyclic Graph) 형태로 오케스트레이션하는 것을 의미합니다. 단순한 Crontab 스크립트 실행이 아닌, Kubeflow Pipeline이나 Airflow와 같은 워크플로우 엔진을 도입하여 각 단계의 상태를 관리하고 메타데이터를 추적해야 합니다.

오케스트레이션의 핵심 가치

파이프라인의 각 컴포넌트는 독립적인 컨테이너로 실행되어야 합니다. 이는 특정 단계(예: GPU가 필요한 학습 단계)에만 고사양 리소스를 할당하고, 전처리 단계는 메모리 최적화 노드에 할당하는 등 리소스 효율성을 극대화할 수 있게 해줍니다.

Kubeflow Pipeline 컴포넌트 설계

다음은 Kubeflow SDK를 사용하여 전처리 컴포넌트를 정의하고 파이프라인을 구성하는 아키텍처 예시입니다. 각 단계는 격리된 환경에서 수행되며 입력과 출력이 명시적으로 정의됩니다.

import kfp
from kfp import dsl

# 데이터 전처리 컴포넌트 정의
@dsl.component(
    base_image='python:3.9',
    packages_to_install=['pandas', 'scikit-learn']
)
def preprocess_data(
    input_path: str, 
    output_path: str
) -> None:
    import pandas as pd
    from sklearn.preprocessing import StandardScaler
    import pickle

    # 데이터 로드 및 결측치 처리
    df = pd.read_csv(input_path)
    df.fillna(0, inplace=True)
    
    # 스케일링 로직
    scaler = StandardScaler()
    processed_data = scaler.fit_transform(df)
    
    # 아티팩트 저장 (재현성 확보)
    with open(output_path, 'wb') as f:
        pickle.dump(processed_data, f)

# 파이프라인 정의
@dsl.pipeline(
    name='Enterprise Training Pipeline',
    description='End-to-end training pipeline with drift detection'
)
def training_pipeline(data_source: str):
    # 전처리 단계 실행
    preprocess_task = preprocess_data(
        input_path=data_source,
        output_path='/mnt/data/processed.pkl'
    )
    
    # 리소스 제한 설정 (Node Affinity 등)
    preprocess_task.set_cpu_request('2').set_memory_request('4Gi')

MLOps 성숙도 모델 레벨 2: CI/CD 파이프라인 통합

모델 학습의 자동화를 넘어, 전체 시스템의 배포 자동화를 달성하는 단계입니다. 여기서는 소스 코드 변경 시 자동으로 파이프라인을 빌드/테스트/배포하는 CI/CD가 필수적입니다. 특히 ML 시스템에서는 코드뿐만 아니라 데이터와 모델 아티팩트(Artifact) 버전 관리까지 통합되어야 합니다.

MLflow를 활용한 모델 레지스트리 구축

학습된 모델을 파일 시스템에 단순히 저장하는 것은 안티 패턴입니다. MLflow와 같은 모델 레지스트리를 사용하여 모델의 버전, 단계(Staging/Production), 학습 파라미터, 메트릭을 중앙에서 관리해야 합니다. 이는 롤백(Rollback) 시나리오에서 결정적인 역할을 합니다.

구분 Level 0 (수동) Level 2 (자동화)
빌드 및 테스트 로컬 실행, 수동 검증 Git Trigger 기반 CI, 자동화된 단위/통합 테스트
배포 (Deploy) 스크립트 실행 또는 수동 복사 CD 파이프라인, 카나리(Canary) 또는 블루/그린 배포
모델 추적 엑셀 또는 비정형 로그 MLflow/Weights & Biases를 통한 전체 메타데이터 추적

데이터 드리프트(Data Drift) 감지와 폐쇄 루프

배포된 모델은 시간이 지남에 따라 성능이 저하됩니다. 입력 데이터의 통계적 속성이 변하는 데이터 드리프트(Data Drift)나, 입력과 출력 간의 관계가 변하는 컨셉 드리프트(Concept Drift) 때문입니다. 성숙한 MLOps 시스템은 이를 감지하여 자동으로 재학습 트리거를 발생시켜야 합니다.

주의: 훈련-서빙 편향(Training-Serving Skew)

학습 시 사용한 피처 엔지니어링 로직과 서빙 시 실시간으로 처리하는 로직이 다를 경우 심각한 성능 저하가 발생합니다. 이를 방지하기 위해 Feature Store(예: Feast)를 도입하여 피처 로직을 일원화해야 합니다.

Drift Detection 구현 로직

Evidently AI나 Alibi Detect와 같은 라이브러리를 사용하여 분포 차이를 계산할 수 있습니다. 아래는 KS 테스트(Kolmogorov-Smirnov test)를 이용해 드리프트를 감지하는 개념 코드입니다.

from scipy.stats import ks_2samp
import numpy as np

def detect_drift(
    reference_data: np.ndarray, 
    current_data: np.ndarray, 
    threshold: float = 0.05
) -> bool:
    """
    참조 데이터(학습 시)와 현재 데이터(추론 시)의 분포 비교
    P-value가 임계값보다 낮으면 드리프트 발생으로 간주
    """
    statistic, p_value = ks_2samp(reference_data, current_data)
    
    if p_value < threshold:
        print(f"Drift Detected! p-value: {p_value}")
        return True
    return False

# 드리프트 감지 시 재학습 파이프라인 트리거 로직 연결
if detect_drift(train_dist, inference_dist):
    trigger_kfp_pipeline(pipeline_id="training-pipeline-v2")

인프라스트럭처 설계 및 결론

최종적인 아키텍처는 Kubernetes 클러스터 위에 Kubeflow(학습), MLflow(관리), Seldon Core 또는 KServe(서빙)가 결합된 형태입니다. 여기에 Prometheus와 Grafana를 연동하여 모델의 Latency, Throughput, 그리고 사용자 정의 비즈니스 메트릭을 실시간으로 모니터링해야 합니다. MLOps 도입은 단순한 도구의 적용이 아니라, 실험실의 모델을 비즈니스 가치를 창출하는 프로덕션 서비스로 전환하는 엔지니어링 규율의 확립입니다.

초기 구축 비용이 높을 수 있으나, 파이프라인 자동화를 통해 얻는 모델 배포 주기의 단축과 장애 복구 속도의 향상은 장기적으로 기술 부채를 상쇄하고 남습니다.

Post a Comment