観測可能性の設計
観測可能性の設計
Section titled “観測可能性の設計”障害は「再現」ではなく「観測」で解決する。観測可能性を高める設計を詳しく解説します。
各処理にトレースID(request_id)を通し、入出力境界ごとにJSON構造化ログを吐く。
# ✅ 良い例: 構造化ログとトレースIDimport uuidimport structlogfrom 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などのコンテキストを自動的に含める
メトリクス設計
Section titled “メトリクス設計”成功率・レイテンシ・リトライ回数・プール使用率を定常監視する。
# ✅ 良い例: Prometheusを使用したメトリクス収集from prometheus_client import Counter, Histogram, Gauge, generate_latestfrom 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
トレース設計
Section titled “トレース設計”外部API・DBアクセス単位にSpanを埋め、分散トレース可能にする。
# ✅ 良い例: OpenTelemetryを使用した分散トレースfrom opentelemetry import tracefrom opentelemetry.sdk.trace import TracerProviderfrom 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を埋め、分散トレース可能にする
これらの設計により、障害を「再現」ではなく「観測」で解決できます。