第39回 モデル運用の可観測性設計 — ログ・メトリクス・トレースをPythonで実装する実務手順

はじめに:運用で “見えない” に悩んでいませんか

モデルをデプロイしてから「なぜ精度が下がったのか」「どのリクエストで例外が出ているのか」「レイテンシが上がったタイミングはいつか」が分からず、切り分けに時間がかかる――こうした悩みは多くの現場で聞かれます。本記事では、ログ・メトリクス・分散トレースの実務的な設計と、Pythonでの実装手順を、現場でそのまま使えるチェックリストとコード片で示します。

1 要件定義(まず何を「見える化」するか)

可観測性の第一歩は、観測対象と稀少性(どれだけ詳細に記録するか)を決めることです。下表は典型的な観測対象の例です。

カテゴリ 観測対象 目的 短期での優先度
ログ リクエストID、入力特徴量のメタ、モデル予測、エラー 障害の再現と原因切り分け
メトリクス レイテンシ、スループット、エラーレート、モデル精度指標 性能劣化・容量問題の検知
トレース リクエストの遅延分解(ネットワーク/前処理/推論/後処理) ボトルネック特定

2 ログ設計:イベント / 操作 / エラーの区別

ログは用途により分けて設計します。読みやすく検索しやすい構造化ログ(JSON)が実務では有効です。

ログ種別 必須フィールド(推奨) 備考
イベントログ モデル起動、バージョン切替 timestamp, service, event_type, version 運用上のライフサイクル記録
操作ログ 各リクエストの処理開始/終了 timestamp, request_id, user_id(任意), latency_ms, status トレースと紐づけると有用
エラーログ 例外、データ不整合 timestamp, request_id, error_type, stack, sample_input 機密データはマスクする

3 構造化ログの実装例(Python)

簡潔に、標準 logging を使って JSON 出力する例と、structlog を使った実装例を示します。出力は検索しやすいフィールド中心に設計してください。

標準 logging + JSON 出力

import logging
import json

class JsonFormatter(logging.Formatter):
    def format(self, record):
        payload = {
            "timestamp": self.formatTime(record),
            "level": record.levelname,
            "message": record.getMessage(),
        }
        if hasattr(record, 'extra'):
            payload.update(record.extra)
        return json.dumps(payload)

logger = logging.getLogger('model-service')
handler = logging.StreamHandler()
handler.setFormatter(JsonFormatter())
logger.addHandler(handler)
logger.setLevel(logging.INFO)

logger.info('prediction', extra={'extra': {'request_id':'r1','latency_ms':123,'model_version':'v1'}})

structlog を使った例(よりフレキシブル)

import structlog
import sys

structlog.configure(
    processors=[
        structlog.processors.TimeStamper(fmt="iso"),
        structlog.processors.JSONRenderer()
    ],
    logger_factory=structlog.PrintLoggerFactory()
)
log = structlog.get_logger('model-service')

log.info('prediction', request_id='r2', latency_ms=98, model_version='v1')

4 メトリクス設計と Prometheus 連携

Prometheus はオンプレ/クラウド問わず使いやすく、Prometheus Client for Python でメトリクスを公開します。定義するべき主要メトリクス:

  • request_count (カウント, ラベル: status, endpoint, model_version)
  • request_latency_seconds (ヒストグラム)
  • error_count (カウント, ラベル: error_type)
  • model_accuracy (ゲージ、バッチで更新)

Prometheus client の例

from prometheus_client import Counter, Histogram, Gauge, start_http_server

REQUEST_COUNT = Counter('request_count', 'Total requests', ['status','endpoint','model_version'])
REQUEST_LATENCY = Histogram('request_latency_seconds', 'Request latency', ['endpoint','model_version'])
ERROR_COUNT = Counter('error_count', 'Error count', ['error_type'])
MODEL_ACCURACY = Gauge('model_accuracy', 'Model accuracy')

# メトリクス公開用の HTTP サーバ
start_http_server(8000)

# ハンドラ内での更新例
with REQUEST_LATENCY.labels(endpoint='/predict', model_version='v1').time():
    # 推論処理
    pass
REQUEST_COUNT.labels(status='200', endpoint='/predict', model_version='v1').inc()

5 分散トレース導入(OpenTelemetry の導入手順)

分散トレースはリクエストの各処理時間を細かく把握できます。OpenTelemetry は標準的な実装です。ポイントはトレースIDをログに付与して紐付けることです。

簡易的な導入手順(要点)

  • OpenTelemetry SDK をサービスに導入
  • インストルメンテーションで Flask/FastAPI 等をラップ
  • Exporter(Jaeger, OTLP)を設定して収集先へ送る
  • ログに trace_id / span_id を出力してログとトレースを結合

最小限の Python コード例(概念)

from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter

tracer_provider = TracerProvider()
tracer_provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))
trace.set_tracer_provider(tracer_provider)
tracer = trace.get_tracer(__name__)

with tracer.start_as_current_span('handle_request') as span:
    span.set_attribute('model_version', 'v1')
    # 推論処理

6 保存・検索とダッシュボードの実務的選択

実務では検索性、コスト、運用負荷を天秤にかけます。下表は代表的な選択肢の比較です。

スタック 長所 短所 現場での向き不向き
Elasticsearch + Kibana 強力な全文検索と可視化、エコシステム豊富 運用コスト・ディスク量が重い ログ分析を深く行うチーム向け
Grafana + Loki + Prometheus メトリクスとログの統合、低コストでスケール 全文検索は弱め コスト重視・SRE小規模向け
クラウド標準ログ(Cloud Logging 等) 管理が容易、オートスケール コスト管理とクエリ制限に注意 インフラをクラウドに寄せている場合に有利

7 アラートと SLO 設計

メトリクスに基づくアラートはノイズを避けるため慎重に設定します。SLO を定義するとアラートの優先度決定に役立ちます。

目的 指標例 具体的設定例
可用性 成功リクエスト割合 SLO: 99.5%/30日。アラート: 24h の失敗率が 1% を超えたら通知
性能 p95 レイテンシ SLO: p95 < 500ms。アラート: 1h の p95 が 500ms を超えたら通知
データ品質 入力特徴量の欠損率、分布シフト指標 しきい値超過でアラート、即時トリアージ

8 データ保護・保持ポリシー

ログには個人情報や機密情報が含まれる可能性があります。保持期間とアクセス制御、マスキングが必須です。

  • 敏感データはログに残さない、または即時マスキングする
  • 保持期間は法規と業務要件で決定(例:30日・90日・365日)
  • 監査ログは別保管し、アクセスを限定する

9 運用上の落とし穴と対処法(早見表)

問題 原因 対処法
ログ量爆発 詳細すぎるデバッグログを本番でも出力 サンプリング、ログレベル制御、集約ログの導入
個人情報漏洩のリスク 生の入力をそのまま保存 マスキング、ハッシュ化、保存ポリシーの適用
ログスキーマの非互換 フィールド追加でクエリが壊れる バージョン付きスキーマ、変更管理の運用

10 実施ロードマップ(短期・中期タスク)

期間 主なタスク 期待成果
短期(1〜2週) 最小メトリクス決定、構造化ログを導入、Prometheus exporter を立てる 基本的な可観測性、簡易ダッシュボード
中期(1〜2か月) トレース導入、ダッシュボードと主要アラート設定、保持ポリシー確定 迅速な切り分け、SLO に基づく運用

11 運用チェックリスト(現場ですぐ使える)

項目 完了/未完了 備考
構造化ログ(JSON)での出力 request_id を必ず含める
Prometheus で主要メトリクスを公開 latency, count, error
トレースの導入(OpenTelemetry) trace_id をログに連携
ダッシュボードとアラート設定 SLO に基づく閾値設定
保持ポリシーとマスキング実装 法令・社内規定に従う

まとめ

本記事では、デプロイ済みモデルの挙動を早く正確に把握するための実務的な可観測性設計を示しました。重要なのは「何を観測するか」を最初に決め、段階的に導入することです。まずは最小限のメトリクスと構造化ログを実装し、ログとトレースを結び付けて切り分け精度を上げてください。そこからダッシュボードとアラートを整備し、保持・コスト・プライバシーの方針を確立することで、安定した運用が可能になります。

次回は、観測で得られた洞察を使った実験設計(第38回)や、継続的な再学習ワークフロー(第36回)につなげる運用フローの具体例を扱います。まずは今日から試せる短期タスクから始めてみてください。