Skip to content

観測可能性の設計

障害は「再現」ではなく「観測」で解決する。観測可能性を高める設計を詳しく解説します。

各処理にトレースID(request_id)を通し、入出力境界ごとにJSON構造化ログを吐く。

# ✅ 良い例: 構造化ログとトレースID
import uuid
import structlog
from django.utils.deprecation import MiddlewareMixin
logger = structlog.get_logger()
class TraceIDMiddleware(MiddlewareMixin):
def process_request(self, request):
# トレースIDを生成(またはリクエストヘッダーから取得)
trace_id = request.headers.get('X-Trace-Id') or str(uuid.uuid4())
request.trace_id = trace_id
# すべてのログにトレースIDを自動的に含める
structlog.contextvars.clear_contextvars()
structlog.contextvars.bind_contextvars(
trace_id=trace_id,
user_id=get_user_id(request)
)
def process_response(self, request, response):
response['X-Trace-Id'] = request.trace_id
return response
def create_order(request):
trace_id = request.trace_id
# 入出力境界でログを出力
logger.info("Order creation started",
order_data=order_data,
timestamp=timezone.now().isoformat(),
)
order = create_order_in_db(order_data, trace_id)
logger.info("Order creation completed",
order_id=order.id,
status=order.status,
duration=(timezone.now() - start_time).total_seconds(),
)
return JsonResponse({'id': order.id})

ログの出力例:

{
"timestamp": "2024-01-01T10:00:00.000Z",
"level": "info",
"trace_id": "abc123",
"user_id": "user456",
"order_data": {
"amount": 10000,
"items": [...]
},
"message": "Order creation started"
}

なぜ重要か:

  • トレーサビリティ: トレースIDでリクエスト全体を追跡可能
  • 構造化ログ: JSON形式で検索・分析が容易
  • コンテキスト: ユーザーIDなどのコンテキストを自動的に含める

成功率・レイテンシ・リトライ回数・プール使用率を定常監視する。

# ✅ 良い例: Prometheusを使用したメトリクス収集
from prometheus_client import Counter, Histogram, Gauge, generate_latest
from django.http import HttpResponse
# カウンター: 注文作成回数
order_creation_counter = Counter(
'orders_created_total',
'Total number of orders created',
['status']
)
# ヒストグラム: 注文作成のレイテンシ
order_creation_duration = Histogram(
'orders_creation_duration_seconds',
'Order creation duration in seconds',
buckets=[0.1, 0.5, 1, 2, 5]
)
# ゲージ: 接続プールの使用率
connection_pool_gauge = Gauge(
'db_connection_pool_active',
'Active database connections'
)
def create_order(request):
with order_creation_duration.time():
try:
order = create_order_in_db(order_data, request.trace_id)
# 成功時のメトリクス
order_creation_counter.labels(status='success').inc()
return JsonResponse({'id': order.id})
except Exception as e:
# 失敗時のメトリクス
order_creation_counter.labels(status='failure').inc()
raise
# メトリクスエンドポイント
def metrics(request):
return HttpResponse(generate_latest(), content_type='text/plain')

監視すべきメトリクス:

  • 成功率: orders_created_total{status="success"} / orders_created_total{status="failure"}
  • レイテンシ: orders_creation_duration_seconds (p50, p95, p99)
  • リトライ回数: orders_retry_total
  • プール使用率: db_connection_pool_active / db_connection_pool_max

外部API・DBアクセス単位にSpanを埋め、分散トレース可能にする。

# ✅ 良い例: OpenTelemetryを使用した分散トレース
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import ConsoleSpanExporter, BatchSpanProcessor
trace.set_tracer_provider(TracerProvider())
tracer = trace.get_tracer(__name__)
def create_order(request):
# 親Spanを作成
with tracer.start_as_current_span("create-order") as span:
span.set_attribute("order.amount", str(order_data['amount']))
span.set_attribute("trace.id", request.trace_id)
# DB操作のSpan
with tracer.start_as_current_span("db.save-order") as db_span:
order = Order.objects.create(**order_data)
db_span.set_attribute("order.id", order.id)
return JsonResponse({'id': order.id})

トレースの出力例:

Trace: abc123
├─ Span: create-order (duration: 150ms)
│ ├─ Span: db.save-order (duration: 50ms)
│ └─ Span: payment-api.charge (duration: 100ms)

なぜ重要か:

  • 分散トレース: 複数サービス間のリクエストフローを追跡可能
  • ボトルネック特定: どの処理が遅いかを特定可能
  • エラー追跡: エラーが発生した箇所を特定可能

観測可能性の設計のポイント:

  • ログ: トレースIDを通し、構造化ログを出力
  • メトリクス: 成功率・レイテンシ・リトライ回数・プール使用率を監視
  • トレース: 外部API・DBアクセス単位にSpanを埋め、分散トレース可能にする

これらの設計により、障害を「再現」ではなく「観測」で解決できます。