> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sifi.life/llms.txt
> Use this file to discover all available pages before exploring further.

# 운영: 트래픽 분할, 비용, 트러블슈팅

> 이 문서는 **[머신러닝](/training/09-machine-learning/overview)** 섹션의 일부입니다.

## 트래픽 분할 및 A/B 테스트

하나의 엔드포인트에 여러 모델 버전을 동시에 배포하고, 트래픽 비율을 조절하여 **A/B 테스트** 를 수행할 수 있습니다.

| 구성 요소           | 트래픽 비율 | 설명                            |
| --------------- | ------ | ----------------------------- |
| 클라이언트 요청        | 100%   | 엔드포인트(fraud-detection)로 전송됩니다 |
| 모델 v3 (현재 프로덕션) | 80%    | 현재 안정된 프로덕션 모델입니다             |
| 모델 v4 (챌린저)     | 20%    | 성능을 검증 중인 새 버전 모델입니다          |
| 응답              | -      | 두 모델의 응답이 클라이언트에 반환됩니다        |

```python theme={null}
# 트래픽 분할 설정
client = mlflow.deployments.get_deploy_client("databricks")

client.update_endpoint(
    endpoint="fraud-detection-v2",
    config={
        "served_entities": [
            {
                "entity_name": "catalog.schema.fraud_detection_custom",
                "entity_version": "3",
                "workload_size": "Small",
                "scale_to_zero_enabled": False
            },
            {
                "entity_name": "catalog.schema.fraud_detection_custom",
                "entity_version": "4",
                "workload_size": "Small",
                "scale_to_zero_enabled": False
            }
        ],
        "traffic_config": {
            "routes": [
                {"served_model_name": "fraud_detection_custom-3", "traffic_percentage": 80},
                {"served_model_name": "fraud_detection_custom-4", "traffic_percentage": 20}
            ]
        }
    }
)
```

> **블루-그린 배포**: 새 모델 버전을 100% 트래픽으로 전환하면서도 이전 버전을 유지하는 방식입니다. 문제 발생 시 즉시 이전 버전으로 롤백할 수 있습니다.

***

## 모니터링 (Monitoring)

### Inference Table (추론 로그 테이블)

**Inference Table** 은 엔드포인트의 모든 요청/응답을 Unity Catalog 테이블에 자동으로 저장합니다. 모델 드리프트 감지, 디버깅, 감사 로그 목적으로 활용합니다.

```python theme={null}
# Inference Table 활성화 (엔드포인트 생성 또는 업데이트 시)
config = {
    "served_entities": [...],
    "auto_capture_config": {
        "catalog_name": "main",
        "schema_name": "serving_logs",
        "table_name_prefix": "fraud_endpoint",   # 테이블명: fraud_endpoint_payload
        "enabled": True
    }
}
```

```sql theme={null}
-- Inference Table 쿼리 예시
-- 테이블명: {catalog}.{schema}.{prefix}_payload
SELECT
    timestamp,
    request_id,
    databricks_request_id,
    -- 요청 데이터 파싱
    request:dataframe_records[0].amount::double AS amount,
    request:dataframe_records[0].merchant_category::string AS merchant_category,
    -- 응답 데이터 파싱
    response:predictions[0].fraud_probability::double AS fraud_probability,
    response:predictions[0].is_fraud::int AS is_fraud,
    -- 메타데이터
    status_code,
    execution_duration_ms
FROM main.serving_logs.fraud_endpoint_payload
WHERE timestamp >= current_date() - interval 7 days
ORDER BY timestamp DESC;
```

```python theme={null}
# Python에서 Inference Table 조회
from databricks.connect import DatabricksSession

spark = DatabricksSession.builder.getOrCreate()

# 최근 1시간 요청 분석
df = spark.sql("""
    SELECT
        date_trunc('minute', timestamp) AS minute,
        count(*) AS request_count,
        avg(execution_duration_ms) AS avg_latency_ms,
        percentile_approx(execution_duration_ms, 0.95) AS p95_latency_ms,
        sum(CASE WHEN status_code != 200 THEN 1 ELSE 0 END) AS error_count
    FROM main.serving_logs.fraud_endpoint_payload
    WHERE timestamp >= now() - interval 1 hour
    GROUP BY 1
    ORDER BY 1
""")
df.display()
```

### 엔드포인트 메트릭 확인

```python theme={null}
from databricks.sdk import WorkspaceClient

w = WorkspaceClient()

# 엔드포인트 상태 조회
endpoint = w.serving_endpoints.get(name="fraud-detection-v2")
print(f"State: {endpoint.state.ready}")
print(f"Config update: {endpoint.state.config_update}")

# 이벤트 로그 조회 (디버깅용)
for event in w.serving_endpoints.list_serving_endpoint_events(name="fraud-detection-v2"):
    print(f"[{event.timestamp}] {event.type}: {event.message}")
```

***

## 콜드 스타트 최적화

**콜드 스타트 (Cold Start)** 는 `scale_to_zero_enabled=True` 설정 시 트래픽이 없다가 첫 요청이 들어올 때 컨테이너를 다시 시작하는 지연 시간입니다. 일반적으로 30초\~5분이 소요됩니다.

| 전략                          | 방법                            | 트레이드오프            |
| --------------------------- | ----------------------------- | ----------------- |
| **Scale-to-zero 비활성화**      | `scale_to_zero_enabled=False` | 비용 증가, 콜드 스타트 없음  |
| **Provisioned Concurrency** | 최소 인스턴스 수 설정                  | 비용 증가, 항상 warm 상태 |
| **모델 경량화**                  | ONNX 변환, 양자화 (Quantization)   | 로드 시간 단축          |
| **의존성 최소화**                 | 불필요한 패키지 제거                   | 컨테이너 빌드/로드 속도 향상  |

```python theme={null}
# Provisioned Concurrency 설정 (최소 인스턴스 보장)
endpoint = client.create_endpoint(
    name="fraud-detection-prod",
    config={
        "served_entities": [{
            "entity_name": "catalog.schema.fraud_detection_custom",
            "entity_version": "3",
            "workload_size": "Small",
            "scale_to_zero_enabled": False,     # 항상 최소 1개 인스턴스 유지
            "workload_type": "CPU"
        }]
    }
)
```

> **load\_context() 최적화**: 무거운 초기화(모델 로드, 토크나이저 초기화 등)는 반드시 `load_context()`에서 수행하고 `predict()`에서는 수행하지 마십시오. `load_context()`는 인스턴스 시작 시 한 번만 실행되지만, `predict()`는 매 요청마다 실행됩니다.

***

## 비용 관리

커스텀 모델 서빙은 **인스턴스 가동 시간** 기준으로 과금됩니다. Foundation Model API의 토큰 기반 과금과 다른 비용 구조를 갖습니다.

| 비용 요소                  | 설명                              | 절감 방법                      |
| ---------------------- | ------------------------------- | -------------------------- |
| **인스턴스 크기**            | Small \< Medium \< Large 순으로 과금 | 실제 트래픽에 맞는 크기 선택           |
| **Scale-to-zero**      | 트래픽 없을 때 인스턴스 종료                | 개발/비업무 시간에 활성화             |
| **GPU 비용**             | CPU 대비 5\~10배 비용                | 실제 GPU 필요 여부 사전 검증         |
| **Inference Table 저장** | Delta Lake 스토리지 비용              | 보존 기간 설정 (`TBLPROPERTIES`) |

```sql theme={null}
-- Inference Table 데이터 보존 기간 설정 (90일 후 자동 삭제)
ALTER TABLE main.serving_logs.fraud_endpoint_payload
SET TBLPROPERTIES (
    'delta.deletedFileRetentionDuration' = 'interval 90 days'
);
```

```python theme={null}
# 비용 절감: 개발 환경은 scale_to_zero 활성화, 프로덕션은 비활성화
def create_endpoint_by_env(env: str, model_name: str, version: str):
    is_prod = env == "production"
    return client.create_endpoint(
        name=f"fraud-detection-{env}",
        config={
            "served_entities": [{
                "entity_name": model_name,
                "entity_version": version,
                "workload_size": "Medium" if is_prod else "Small",
                "scale_to_zero_enabled": not is_prod,
                "workload_type": "CPU"
            }]
        }
    )
```

***

## 실습: 모델 서빙 엔드포인트 생성 및 호출

### 엔드포인트 호출 방법

```python theme={null}
import requests
import json

# 방법 1: requests 라이브러리
workspace_url = "https://<workspace-url>"
token = dbutils.notebook.entry_point.getDbutils().notebook().getContext().apiToken().get()

url = f"{workspace_url}/serving-endpoints/fraud-detection-v2/invocations"
headers = {
    "Authorization": f"Bearer {token}",
    "Content-Type": "application/json"
}

# DataFrame 레코드 형식
payload = {
    "dataframe_records": [
        {"amount": 50000, "merchant_category": "online_retail", "hour": 3, "is_international": 1},
        {"amount": 25, "merchant_category": "grocery", "hour": 12, "is_international": 0}
    ]
}

response = requests.post(url, json=payload, headers=headers)
predictions = response.json()
print(json.dumps(predictions, indent=2))
```

```python theme={null}
# 방법 2: Databricks SDK
from databricks.sdk import WorkspaceClient

w = WorkspaceClient()

response = w.serving_endpoints.query(
    name="fraud-detection-v2",
    dataframe_records=[
        {"amount": 50000, "merchant_category": "online_retail", "hour": 3, "is_international": 1}
    ]
)
print(response.predictions)
```

```python theme={null}
# 방법 3: MLflow Deployments Client
client = mlflow.deployments.get_deploy_client("databricks")

response = client.predict(
    endpoint="fraud-detection-v2",
    inputs={
        "dataframe_records": [
            {"amount": 50000, "merchant_category": "online_retail", "hour": 3}
        ]
    }
)
print(response)
```

***

## 베스트 프랙티스와 흔한 실수

### 베스트 프랙티스

| 항목                      | 권장 사항                                                     |
| ----------------------- | --------------------------------------------------------- |
| **시그니처 필수 정의**          | `infer_signature()`로 항상 입출력 스키마를 명시합니다                    |
| **의존성 버전 고정**           | `scikit-learn==1.4.0`처럼 정확한 버전을 `pip_requirements`에 명시합니다 |
| **load\_context 최적화**   | 모델 로드 등 무거운 작업은 `load_context()`에서 처리합니다                  |
| **input\_example 제공**   | `log_model()` 시 실제 샘플 데이터를 `input_example`로 제공합니다         |
| **Inference Table 활성화** | 프로덕션에서는 항상 `auto_capture_config`를 활성화합니다                  |
| **환경별 scale\_to\_zero** | 개발은 `True`, 프로덕션은 `False`로 설정합니다                          |

### 흔한 실수

| 실수                          | 증상                       | 해결 방법                                 |
| --------------------------- | ------------------------ | ------------------------------------- |
| **의존성 미명시**                 | `ModuleNotFoundError` 발생 | `pip_requirements`에 모든 패키지 버전 명시      |
| **모델 크기 초과**                | 엔드포인트 `PENDING` 상태에서 멈춤  | 커스텀 모델 크기는 일반적으로 수 GB 이하 권장           |
| **predict()에서 무거운 로드**      | 요청마다 수 초 지연              | 초기화 로직을 `load_context()`로 이동          |
| **시그니처 불일치**                | `400 Bad Request` 오류     | 요청 형식을 `input_example`과 동일하게 맞춤       |
| **타임아웃 미고려**                | 기본 타임아웃(60초) 초과 오류       | 모델 최적화 또는 비동기 처리 패턴 도입                |
| **GPU 없이 GPU 모델 실행**        | CUDA 오류 발생               | `workload_type="GPU_SMALL"` 이상으로 설정   |
| **scale\_to\_zero + 저레이턴시** | 첫 요청 30초\~수 분 지연         | 프로덕션에서는 `scale_to_zero_enabled=False` |

***

## 트러블슈팅

### 자주 발생하는 문제와 해결 방법

| 증상                           | 원인                          | 해결 방법                                        |
| ---------------------------- | --------------------------- | -------------------------------------------- |
| **엔드포인트가 `PENDING` 상태에서 멈춤** | 모델 로드 실패 또는 의존성 문제          | 엔드포인트 이벤트 로그 확인. `conda_env`에 정확한 패키지 버전 명시  |
| **`ModuleNotFoundError`**    | 서빙 환경에 필요한 패키지 미설치          | `pip_requirements` 또는 `conda_env`에 모든 의존성 추가 |
| **입력 스키마 불일치**               | 요청 데이터의 컬럼명/타입이 모델 시그니처와 다름 | `input_example`과 동일한 형식으로 요청. 모델 시그니처 확인     |
| **Scale-to-zero 후 첫 요청 느림**  | Cold Start (컨테이너 부팅 시간)     | 프로덕션에서는 `scale_to_zero_enabled=False` 사용     |
| **메모리 부족 (OOM)**             | 모델 크기 대비 워크로드 크기가 작음        | `workload_size`를 Medium/Large로 변경            |
| **GPU 할당 실패**                | GPU 리소스 부족                  | 다른 GPU 타입 시도 또는 워크스페이스 관리자에 문의               |

### 엔드포인트 상태 확인

```python theme={null}
from databricks.sdk import WorkspaceClient

w = WorkspaceClient()

# 엔드포인트 상태 조회
endpoint = w.serving_endpoints.get(name="fraud-detection-v2")
print(f"State: {endpoint.state.ready}")
print(f"Config update: {endpoint.state.config_update}")

# 이벤트 로그 조회 (디버깅용)
for event in w.serving_endpoints.list_serving_endpoint_events(name="fraud-detection-v2"):
    print(f"[{event.timestamp}] {event.type}: {event.message}")
```

> **프로덕션 배포 체크리스트**:
>
> * 모델 시그니처(input/output schema)가 올바른지 확인
> * `pip_requirements`에 모든 의존성과 정확한 버전을 명시
> * Scale-to-zero 비활성화 (지연 시간 민감한 경우)
> * Inference Table을 활성화하여 요청/응답 로깅
> * 엔드포인트 모니터링 알림 설정

***

## 정리

| 항목               | 핵심 포인트                                                      |
| ---------------- | ----------------------------------------------------------- |
| **커스텀 vs FMAPI** | 파인튜닝 모델, 전후처리, 데이터 거버넌스가 필요하면 커스텀 배포를 선택합니다                 |
| **모델 시그니처**      | `infer_signature()`로 입출력 스키마를 명시하여 런타임 오류를 사전 방지합니다         |
| **커스텀 PyFunc**   | `PythonModel`을 상속하여 전처리/후처리/앙상블 등 자유로운 추론 로직을 구현합니다         |
| **엔드포인트 생성**     | SDK, REST API, UI 세 가지 방법으로 생성할 수 있습니다                      |
| **트래픽 분할**       | A/B 테스트와 블루-그린 배포로 안전하게 모델을 교체합니다                           |
| **GPU 서빙**       | `workload_type`으로 T4/A10G/A100 등 GPU를 선택합니다                 |
| **콜드 스타트**       | 프로덕션에서는 `scale_to_zero_enabled=False`로 지연 시간을 예측 가능하게 유지합니다 |
| **모니터링**         | Inference Table에서 요청/응답을 SQL로 분석하고, 레이턴시와 에러율을 추적합니다        |
| **비용 관리**        | 환경별 workload\_size와 scale\_to\_zero 설정으로 불필요한 비용을 절감합니다     |
| **의존성 관리**       | `conda_env` 또는 `pip_requirements`에 정확한 버전을 명시하는 것이 핵심입니다    |

***

## 참고 링크

* [Databricks: Deploy custom models](https://docs.databricks.com/aws/en/machine-learning/model-serving/create-manage-serving-endpoints.html)
* [Databricks: Custom Python models (PyFunc)](https://docs.databricks.com/aws/en/machine-learning/model-serving/custom-models.html)
* [Databricks: Model Serving GPU workloads](https://docs.databricks.com/aws/en/machine-learning/model-serving/gpu.html)
* [Databricks: A/B testing with serving endpoints](https://docs.databricks.com/aws/en/machine-learning/model-serving/traffic-config.html)
* [Databricks: Inference Tables](https://docs.databricks.com/aws/en/machine-learning/model-serving/inference-tables.html)
* [Databricks: Model Serving scale-to-zero](https://docs.databricks.com/aws/en/machine-learning/model-serving/scale-to-zero.html)
* [MLflow: Custom PyFunc Models](https://mlflow.org/docs/latest/python_api/mlflow.pyfunc.html)
* [MLflow: Model Signatures](https://mlflow.org/docs/latest/models.html#model-signature-and-input-example)
