LabHub

ブログ

OpenTelemetryオブザーバビリティブループリント:Metrics、Logs、Traces統合

한국어English日本語

OpenTelemetry オブザーバビリティブループリント: Metrics、Logs、Traces 統合

ブループリントが必要な理由

可観測性システムを設計するとき最もよくある失敗は、Metrics、Logs、Traces を個別のツールとして別々に構築することだ。Prometheus でメトリクスを収集し、ELK でログを集め、Jaeger でトレースを保存すると、三つのデータがそれぞれ独立して存在することになる。障害が起きると「メトリクスでエラー率の上昇を確認 -> ログでエラーメッセージを検索 -> トレースで遅いリクエストを追跡」という流れを手作業でつなぐ必要があり、この過程で平均 15-30 分が費やされる。

OpenTelemetry (OTel) はこの三つのシグナル (signal) を、一つの SDK、一つのプロトコル (OTLP)、一つの属性体系 (semantic conventions) に統合する。このブループリントでは、OTel をベースに Metrics、Logs、Traces を統合的に構築する全体アーキテクチャを設計する。

全体アーキテクチャ

                    [Application Pods]
                    ┌──────────────────┐
OTel SDK                      (Auto + Manual)                    │  ┌─────────────┐ │
                    │  │ Traces      │ │
                    │  │ Metrics     │ │
                    │  │ Logs        │ │
                    │  └──────┬──────┘ │
                    └─────────┼────────┘
OTLP (gRPC)
                    ┌──────────────────┐
OTel Collector                      (DaemonSet)                    │  ┌────────────┐  │
                    │  │ Receivers  │  │
                    │  │ Processors │  │
                    │  │ Exporters  │  │
                    │  └────────────┘  │
                    └────────┬─────────┘
OTLP
                    ┌──────────────────┐
OTel Collector                      (Gateway)- Sampling- Enrichment- Routing                    └──┬─────┬──────┬──┘
                       │     │      │
                ┌──────┘     │      └──────┐
                ▼            ▼             ▼
          ┌──────────┐ ┌──────────┐ ┌──────────┐
Tempo    │ │ Mimir    │ │ Loki           (Traces) (Metrics) (Logs)          └──────────┘ └──────────┘ └──────────┘
                └──────────┼──────────┘
                    ┌──────────────┐
Grafana                      (Unified)                    └──────────────┘

アーキテクチャの設計原則

  1. Agent-Gateway の 2-tier 構成: DaemonSet Collector (agent) がローカルで収集し、Gateway Collector が中央で処理とルーティングを行う。Agent は軽量に保ち、重い処理は Gateway が担当する。
  2. OTLP 単一プロトコル: すべてのシグナルが OTLP で送信されるため、ネットワーク設定が単純になる。
  3. Semantic Conventions の遵守: service.nameservice.versiondeployment.environment などの標準属性をすべてのシグナルに同じように適用し、cross-signal correlation を可能にする。

Collector の設定: Agent (DaemonSet)

# otel-collector-agent.yaml
# DaemonSet としてデプロイ。各ノードでのローカル収集を担当。
apiVersion: v1
kind: ConfigMap
metadata:
  name: otel-collector-agent
data:
  config.yaml: |
    receivers:
      otlp:
        protocols:
          grpc:
            endpoint: 0.0.0.0:4317
          http:
            endpoint: 0.0.0.0:4318

      # Kubernetes ノード/Pod メトリクスの収集
      kubeletstats:
        collection_interval: 30s
        auth_type: serviceAccount
        endpoint: "https://${env:NODE_IP}:10250"
        insecure_skip_verify: true
        metric_groups:
          - node
          - pod
          - container

      # ホストメトリクス (CPU、メモリ、ディスク)
      hostmetrics:
        collection_interval: 30s
        scrapers:
          cpu:
            metrics:
              system.cpu.utilization:
                enabled: true
          memory:
            metrics:
              system.memory.utilization:
                enabled: true
          disk: {}
          network: {}

    processors:
      # メモリ使用量の制限 (Agent は軽量に)
      memory_limiter:
        check_interval: 5s
        limit_mib: 512
        spike_limit_mib: 128

      # Kubernetes メタデータの自動追加
      k8sattributes:
        auth_type: serviceAccount
        extract:
          metadata:
            - k8s.pod.name
            - k8s.pod.uid
            - k8s.namespace.name
            - k8s.node.name
            - k8s.deployment.name
          labels:
            - tag_name: app
              key: app.kubernetes.io/name
            - tag_name: version
              key: app.kubernetes.io/version

      # リソース属性の追加 (すべてのシグナルに共通適用)
      resource:
        attributes:
          - key: deployment.environment
            value: "${env:DEPLOY_ENV}"
            action: upsert
          - key: cloud.region
            value: "${env:CLOUD_REGION}"
            action: upsert

      batch:
        send_batch_size: 1024
        timeout: 5s

    exporters:
      otlp:
        endpoint: otel-collector-gateway:4317
        tls:
          insecure: false
          ca_file: /etc/ssl/certs/ca.crt

    service:
      pipelines:
        traces:
          receivers: [otlp]
          processors: [memory_limiter, k8sattributes, resource, batch]
          exporters: [otlp]
        metrics:
          receivers: [otlp, kubeletstats, hostmetrics]
          processors: [memory_limiter, k8sattributes, resource, batch]
          exporters: [otlp]
        logs:
          receivers: [otlp]
          processors: [memory_limiter, k8sattributes, resource, batch]
          exporters: [otlp]

Collector の設定: Gateway

# otel-collector-gateway.yaml
# Deployment としてデプロイ。中央処理とルーティングを担当。
apiVersion: v1
kind: ConfigMap
metadata:
  name: otel-collector-gateway
data:
  config.yaml: |
    receivers:
      otlp:
        protocols:
          grpc:
            endpoint: 0.0.0.0:4317

    processors:
      memory_limiter:
        check_interval: 5s
        limit_mib: 4096
        spike_limit_mib: 1024

      # Tail-based sampling (Gateway でのみ実施)
      # すべての trace を保存するとコストが爆発するため、賢いサンプリングが必須
      tail_sampling:
        decision_wait: 10s
        num_traces: 100000
        policies:
          # エラーを含む trace は 100% 保存
          - name: errors-policy
            type: status_code
            status_code:
              status_codes: [ERROR]
          # 遅いリクエストは 100% 保存 (P95 以上)
          - name: latency-policy
            type: latency
            latency:
              threshold_ms: 1000
          # 正常なリクエストは 10% サンプリング
          - name: probabilistic-policy
            type: probabilistic
            probabilistic:
              sampling_percentage: 10

      # 不要な属性の削除 (コスト削減)
      attributes/remove:
        actions:
          - key: http.request.header.authorization
            action: delete
          - key: http.request.header.cookie
            action: delete
          - key: db.statement
            action: hash  # SQL 文はハッシュ化して個人情報を保護

      batch:
        send_batch_size: 2048
        timeout: 10s

    exporters:
      # Traces -> Grafana Tempo
      otlp/tempo:
        endpoint: tempo:4317
        tls:
          insecure: true

      # Metrics -> Grafana Mimir (Prometheus 互換)
      prometheusremotewrite:
        endpoint: http://mimir:9009/api/v1/push
        resource_to_telemetry_conversion:
          enabled: true

      # Logs -> Grafana Loki
      loki:
        endpoint: http://loki:3100/loki/api/v1/push

    service:
      pipelines:
        traces:
          receivers: [otlp]
          processors: [memory_limiter, tail_sampling, attributes/remove, batch]
          exporters: [otlp/tempo]
        metrics:
          receivers: [otlp]
          processors: [memory_limiter, batch]
          exporters: [prometheusremotewrite]
        logs:
          receivers: [otlp]
          processors: [memory_limiter, attributes/remove, batch]
          exporters: [loki]

アプリケーションの計装: Python

Auto-instrumentation と手動 span の追加

# app/tracing.py
from opentelemetry import trace, metrics
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter
from opentelemetry.sdk.resources import Resource
from opentelemetry.semconv.resource import ResourceAttributes
import logging

def setup_observability(
    service_name: str,
    service_version: str,
    otlp_endpoint: str = "http://otel-collector:4317",
):
    """OpenTelemetry の初期化。アプリケーション起動時に一度だけ呼び出す。"""

    # リソース定義 (すべてのシグナルに共通で適用される属性)
    resource = Resource.create({
        ResourceAttributes.SERVICE_NAME: service_name,
        ResourceAttributes.SERVICE_VERSION: service_version,
        ResourceAttributes.DEPLOYMENT_ENVIRONMENT: "production",
        "team.name": "platform",
    })

    # --- Traces ---
    tracer_provider = TracerProvider(resource=resource)
    tracer_provider.add_span_processor(
        BatchSpanProcessor(
            OTLPSpanExporter(endpoint=otlp_endpoint, insecure=True),
            max_queue_size=2048,
            max_export_batch_size=512,
            schedule_delay_millis=5000,
        )
    )
    trace.set_tracer_provider(tracer_provider)

    # --- Metrics ---
    metric_reader = PeriodicExportingMetricReader(
        OTLPMetricExporter(endpoint=otlp_endpoint, insecure=True),
        export_interval_millis=30000,  # 30 秒ごとに export
    )
    meter_provider = MeterProvider(resource=resource, metric_readers=[metric_reader])
    metrics.set_meter_provider(meter_provider)

    # --- Logs (OTel Logs Bridge) ---
    # Python の logging を OTel へブリッジ
    from opentelemetry.sdk._logs import LoggerProvider
    from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
    from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter
    from opentelemetry._logs import set_logger_provider

    logger_provider = LoggerProvider(resource=resource)
    logger_provider.add_log_record_processor(
        BatchLogRecordProcessor(
            OTLPLogExporter(endpoint=otlp_endpoint, insecure=True)
        )
    )
    set_logger_provider(logger_provider)

    # Python logging handler の接続
    from opentelemetry.sdk._logs import LoggingHandler
    handler = LoggingHandler(logger_provider=logger_provider)
    logging.getLogger().addHandler(handler)

ビジネスロジックへの手動計装の追加

# app/services/order_service.py
from opentelemetry import trace, metrics
import logging

tracer = trace.get_tracer("order-service", "1.0.0")
meter = metrics.get_meter("order-service", "1.0.0")
logger = logging.getLogger(__name__)

# ビジネスメトリクスの定義
order_counter = meter.create_counter(
    name="orders.created",
    description="作成された注文数",
    unit="1",
)
order_amount_histogram = meter.create_histogram(
    name="orders.amount",
    description="注文金額の分布",
    unit="KRW",
)
payment_duration = meter.create_histogram(
    name="payment.duration",
    description="決済処理時間",
    unit="s",
)

async def create_order(user_id: str, items: list, payment_method: str):
    """注文作成 - Traces、Metrics、Logs がすべて連携する例"""

    # 1. 親 span の生成
    with tracer.start_as_current_span(
        "create_order",
        attributes={
            "user.id": user_id,
            "order.item_count": len(items),
            "payment.method": payment_method,
        },
    ) as span:
        total_amount = sum(item["price"] * item["qty"] for item in items)
        span.set_attribute("order.total_amount", total_amount)

        # 2. 在庫確認 (子 span)
        with tracer.start_as_current_span("check_inventory") as inv_span:
            for item in items:
                available = await check_stock(item["sku"], item["qty"])
                if not available:
                    inv_span.set_attribute("inventory.out_of_stock_sku", item["sku"])
                    # ログにも trace context が自動で含まれる
                    logger.warning(
                        f"在庫不足: SKU={item['sku']}, 要求={item['qty']}",
                        extra={"sku": item["sku"], "requested_qty": item["qty"]},
                    )
                    span.set_status(trace.StatusCode.ERROR, "在庫不足")
                    raise OutOfStockError(item["sku"])

        # 3. 決済処理 (子 span)
        import time
        payment_start = time.monotonic()
        with tracer.start_as_current_span(
            "process_payment",
            attributes={"payment.method": payment_method},
        ) as pay_span:
            try:
                result = await payment_gateway.charge(total_amount, payment_method)
                pay_span.set_attribute("payment.transaction_id", result.tx_id)
                logger.info(
                    f"決済成功: tx_id={result.tx_id}, amount={total_amount}",
                    extra={"tx_id": result.tx_id, "amount": total_amount},
                )
            except PaymentError as e:
                pay_span.set_status(trace.StatusCode.ERROR, str(e))
                logger.error(f"決済失敗: {e}", exc_info=True)
                raise
            finally:
                elapsed = time.monotonic() - payment_start
                payment_duration.record(elapsed, {"payment.method": payment_method})

        # 4. メトリクスの記録
        order_counter.add(1, {
            "payment.method": payment_method,
            "order.status": "created",
        })
        order_amount_histogram.record(total_amount, {
            "payment.method": payment_method,
        })

        return {"order_id": "ORD-12345", "status": "created"}

Cross-Signal Correlation: 三つのシグナルを連携させる方法

OTel の真価は三つのシグナルが連携したときに現れる。核心はすべてのシグナルに同じ trace_id を含めることだ。

Grafana での Correlation 設定

# grafana/provisioning/datasources/datasources.yaml
apiVersion: 1
datasources:
  - name: Tempo
    type: tempo
    url: http://tempo:3200
    jsonData:
      tracesToLogsV2:
        datasourceUid: loki
        filterByTraceID: true
        filterBySpanID: true
        tags:
          - key: service.name
            value: service_name
      tracesToMetrics:
        datasourceUid: mimir
        tags:
          - key: service.name
            value: service
      serviceMap:
        datasourceUid: mimir

  - name: Loki
    type: loki
    url: http://loki:3100
    jsonData:
      derivedFields:
        - name: TraceID
          datasourceUid: tempo
          matcherRegex: "trace_id=(\\w+)"
          url: '$${__value.raw}'
          matcherType: regex

  - name: Mimir
    type: prometheus
    url: http://mimir:9009/prometheus
    jsonData:
      exemplarTraceIdDestinations:
        - name: trace_id
          datasourceUid: tempo

Correlation の実践シナリオ

1. Grafana ダッシュボードでエラー率のスパイクを発見
   -> Mimir クエリ: rate(http_requests_total{status="500"}[5m])

2. 該当時間帯のエラーリクエストに含まれる exemplar をクリック
   -> Exemplar に含まれる trace_id で Tempo へ移動

3. Tempo で trace 全体を確認
   -> どのサービスのどの span でエラーが発生したかを可視化
   -> span の attributes で user.id、order.id などのビジネスコンテキストを確認

4. その span と同じ trace_id を持つログを確認
   -> Loki クエリ: {service_name="order-service"} | trace_id="abc123..."
   -> エラーのスタックトレース、入力データ、中間状態の値を確認

5. 診断全体の所要時間: 2-3  (従来: 15-30)

コスト管理:サンプリング戦略

可観測性データのコストは主にストレージとネットワークで発生する。すべてのデータを 100% 保存すると、月に数百万ウォンのコストになりうる。

シグナル別のサンプリング戦略

シグナル戦略比率備考
Traces (正常)Tail-based probabilistic10%Gateway Collector で決定
Traces (エラー)100% 保存100%エラー trace は必ず保存
Traces (遅い要求)100% 保存100%P95 以上の latency は保存
Metrics全量収集100%カーディナリティ管理でコスト統制
Logs (ERROR 以上)全量収集100%エラーログは必ず保存
Logs (INFO)Probabilistic20%正常ログはサンプリング
Logs (DEBUG)Production では無効化0%必要時に動的に有効化

メトリクスのカーディナリティ管理

# Collector で高カーディナリティの属性を削除
processors:
  # user_id、session_id のような high-cardinality label を削除
  # メトリクスには使わないこと (trace で使う)
  transform/metrics:
    metric_statements:
      - context: datapoint
        statements:
          - delete_key(attributes, "user.id")
          - delete_key(attributes, "session.id")
          - delete_key(attributes, "request.id")
          - delete_key(attributes, "http.url") # path parameter を含むとカーディナリティが爆発

コスト推定モデル

def estimate_monthly_cost(
    daily_requests: int,
    avg_spans_per_trace: int = 8,
    avg_log_lines_per_request: int = 5,
    trace_sample_rate: float = 0.10,
    log_sample_rate: float = 0.20,
) -> dict:
    """月間の可観測性データコストの推定"""
    monthly_requests = daily_requests * 30

    # Traces
    traces_per_month = monthly_requests * trace_sample_rate
    spans_per_month = traces_per_month * avg_spans_per_trace
    trace_storage_gb = spans_per_month * 0.5 / 1e6  # span あたり約 0.5KB

    # Metrics (常に 100%、カーディナリティで管理)
    # サービス 10 個、メトリクス 50 種、カーディナリティ 100 = 50,000 時系列
    metric_series = 50_000
    metric_storage_gb = metric_series * 30 * 24 * 2 * 8 / 1e9  # データポイントあたり 8B

    # Logs
    log_lines_per_month = monthly_requests * avg_log_lines_per_request * log_sample_rate
    log_storage_gb = log_lines_per_month * 0.2 / 1e6  # ログ 1 行あたり約 0.2KB

    # コスト計算 (Grafana Cloud 基準のおおよその単価)
    trace_cost = trace_storage_gb * 2.0   # $2/GB
    metric_cost = metric_series * 0.008   # $8 per 1000 series
    log_cost = log_storage_gb * 0.50      # $0.50/GB

    return {
        "traces": {
            "sampled_per_month": int(traces_per_month),
            "storage_gb": round(trace_storage_gb, 1),
            "cost_usd": round(trace_cost, 2),
        },
        "metrics": {
            "active_series": metric_series,
            "storage_gb": round(metric_storage_gb, 1),
            "cost_usd": round(metric_cost, 2),
        },
        "logs": {
            "sampled_lines_per_month": int(log_lines_per_month),
            "storage_gb": round(log_storage_gb, 1),
            "cost_usd": round(log_cost, 2),
        },
        "total_monthly_usd": round(trace_cost + metric_cost + log_cost, 2),
    }

# 日 100 万リクエストのサービスを想定
cost = estimate_monthly_cost(daily_requests=1_000_000)
print(f"月間総コスト推定: ${cost['total_monthly_usd']}")

段階的導入ロードマップ

一度にすべてを導入すると失敗する。4 段階に分けて段階的に構築する。

Phase 1 (2 週間): Traces の導入

目標: 主要サービス 3 つに distributed tracing を有効化

作業:
1. OTel Collector DaemonSet + Gateway をデプロイ
2. 主要サービスに OTel SDK を追加 (auto-instrumentation を優先)
3. Tempo バックエンドをデプロイ
4. Grafana で trace 検索を確認

成功基準:
- サービス間の trace がつながって見えるか
- P95 latency が高いリクエストのボトルネック区間を trace から特定できるか

Phase 2 (2 週間): Metrics の統合

目標: Prometheus メトリクスを OTel パイプラインへ統合

作業:
1. OTel CollectorPrometheus receiver を追加
2. 既存の Prometheus サーバーから remote_write で Mimir へ移行
3. Exemplar 連携の設定 (メトリクス -> トレース)
4. 既存 Grafana ダッシュボードの移行

成功基準:
- メトリクスダッシュボードで exemplar をクリックして trace へ移動できるか
- 既存のアラートが同じように動作するか

Phase 3 (2 週間): Logs の統合

目標: 構造化ログを OTel で収集し trace と連携させる

作業:
1. アプリケーションのロギングに OTel Logs Bridge を適用
2. ログに trace_id、span_id が自動挿入されるか確認
3. Loki バックエンドのデプロイと Collector 接続
4. Grafana で trace <-> log の双方向連携を設定

成功基準:
- trace からログへ、ログから trace へワンクリックで移動できるか
- エラーログの trace context からリクエスト全体の流れを追跡できるか

Phase 4 (2 週間): 最適化と標準化

目標: コスト最適化、サンプリングのチューニング、チームのオンボーディング

作業:
1. Tail-based sampling を有効化しコスト 30% 削減を確認
2. Semantic conventions の標準を文書化
3. チーム別ダッシュボードテンプレートの配布
4. オンコールランブックに可観測性のワークフローを追加

成功基準:
- MTTD (Mean Time To Detect) 50% 改善
- MTTR (Mean Time To Resolve) 30% 改善
- 可観測性データのコストがインフラコストの 5% 以内

トラブルシューティング

1. Trace が途中で切れる (Broken Trace)

症状: Grafana Tempo で trace を開くと一部サービスの span が欠落している

診断:

# 1. Context propagation の確認 - HTTP ヘッダーに traceparent が伝わっているか
curl -v http://service-a/api/test 2>&1 | grep -i traceparent
# traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01

# 2. サービス B がそのヘッダーを受信し伝播しているか確認
# サービス B のログで trace_id を検索

# 3. Collector で span が drop されていないか確認
curl -s http://otel-collector:8888/metrics | grep otelcol_exporter_sent_spans

原因と解決:

2. Collector の OOM (Out of Memory)

Error: memory usage exceeded limit: 512 MiB

解決:

# memory_limiter の設定確認と調整
processors:
  memory_limiter:
    check_interval: 5s
    limit_mib: 512 # Agent は 512MB 以内
    spike_limit_mib: 128
    # limit に達するとデータを drop するため
    # 十分なメモリを割り当てつつ、ノードに影響しない範囲に収める

  # batch サイズを小さくする
  batch:
    send_batch_size: 512 # 2048 から 512 へ減らす
    timeout: 5s

3. メトリクスのカーディナリティ爆発

症状: Mimir または Prometheus のメモリ使用量が急増し、クエリ速度が低下する

診断:

# カーディナリティが高いメトリクスを探す
topk(10, count by (__name__)({__name__=~".+"}))

# 特定メトリクスの label カーディナリティを確認
count(http_requests_total) by (url)
# url に path parameter が含まれ、数万個の時系列が生成されている

解決: Collector の transform processor で high-cardinality label を削除、または正規化する

4. ログに trace_id が含まれない

原因: OTel Logs Bridge は設定したが、logging ライブラリの formatter が trace context を出力していない

解決 (Python):

import logging
from opentelemetry import trace

class TraceContextFilter(logging.Filter):
    def filter(self, record):
        span = trace.get_current_span()
        ctx = span.get_span_context()
        record.trace_id = format(ctx.trace_id, '032x') if ctx.trace_id else ""
        record.span_id = format(ctx.span_id, '016x') if ctx.span_id else ""
        return True

handler = logging.StreamHandler()
handler.addFilter(TraceContextFilter())
handler.setFormatter(logging.Formatter(
    '%(asctime)s %(levelname)s [trace_id=%(trace_id)s span_id=%(span_id)s] %(message)s'
))
logging.getLogger().addHandler(handler)

クイズ

Q1. OTel Collector を Agent-Gateway の 2-tier で構成する理由は? 正答: ||Agent (DaemonSet) は各ノードで軽量に収集して k8s メタデータを付与し、 Gateway (Deployment) は中央で tail-based sampling や属性変換など重い処理を担当する。単一の tier ですべてを処理すると各ノードのリソース消費が大きくなり、サンプリング判断に必要な trace 全体の情報を ローカルで持てない。||

Q2. Tail-based sampling が Head-based より優れている点は? 正答: ||Head-based sampling は trace の開始時点でサンプリングを決めるため、エラーや遅いリクエストを 取りこぼすことがある。Tail-based は trace が完成したあとに決めるので、エラー trace は 100% 保存し、 正常な trace だけをサンプリングして、コストと品質の両方を確保できる。||

Q3. メトリクスに user_id を label として使ってはいけない理由は? 正答: ||user_id は high-cardinality な値なので、時系列の数がユーザー数だけ爆発する。100 万ユーザーの サービスでメトリクス一つに user_id label を追加すると 100 万個の時系列が生成され、ストレージコストと クエリ性能が急激に悪化する。user_id は trace の span attribute として記録すべきだ。||

Q4. Exemplar の役割は何か? 正答: ||Exemplar はメトリクスのデータポイントに紐づいた trace_id の参照だ。メトリクスダッシュボードで エラー率のスパイクを見て、その時点の exemplar をクリックすると、実際にエラーが発生した trace へ直接移動できる。 これが Metrics -> Traces correlation の中核となるメカニズムだ。||

Q5. Semantic Conventions をチーム間で統一すべき理由は? 正答: ||サービス A が service.name="payment" を、サービス B が service_name="payment-api" を使うと、 cross-signal correlation ができない。Grafana では trace -> log の連携時に同じ属性キーと値で マッチングするため、属性体系が統一されていないと、可観測性システムの中核的価値である相関分析が働かない。||

Q6. 可観測性データのコストはインフラコストの何パーセントを超えてはいけないか? 正答: ||一般的に 5-10% が適正ラインだ。これを超えるならサンプリング比率の調整、保存期間の短縮、 カーディナリティの最適化が必要になる。ただし、コスト削減のためにエラー trace やエラーログをサンプリングすると 障害対応能力が低下するので注意が必要だ。||

参考資料

コメント

まだコメントはありません。

ログインするとコメントできます