- ブループリントが必要な理由
- 全体アーキテクチャ
- Collector の設定: Agent (DaemonSet)
- Collector の設定: Gateway
- アプリケーションの計装: Python
- Cross-Signal Correlation: 三つのシグナルを連携させる方法
- コスト管理:サンプリング戦略
- 段階的導入ロードマップ
- トラブルシューティング
- クイズ
- 参考資料

ブループリントが必要な理由
可観測性システムを設計するとき最もよくある失敗は、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) │
└──────────────┘
アーキテクチャの設計原則
- Agent-Gateway の 2-tier 構成: DaemonSet Collector (agent) がローカルで収集し、Gateway Collector が中央で処理とルーティングを行う。Agent は軽量に保ち、重い処理は Gateway が担当する。
- OTLP 単一プロトコル: すべてのシグナルが OTLP で送信されるため、ネットワーク設定が単純になる。
- Semantic Conventions の遵守:
service.name、service.version、deployment.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 probabilistic | 10% | Gateway Collector で決定 |
| Traces (エラー) | 100% 保存 | 100% | エラー trace は必ず保存 |
| Traces (遅い要求) | 100% 保存 | 100% | P95 以上の latency は保存 |
| Metrics | 全量収集 | 100% | カーディナリティ管理でコスト統制 |
| Logs (ERROR 以上) | 全量収集 | 100% | エラーログは必ず保存 |
| Logs (INFO) | Probabilistic | 20% | 正常ログはサンプリング |
| 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 Collector に Prometheus 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
原因と解決:
- HTTP クライアントが traceparent ヘッダーを伝えていない -> OTel の HTTP instrumentation ライブラリを確認
- メッセージキュー (Kafka、RabbitMQ) の区間で context が伝播していない -> キューの producer/consumer に OTel instrumentation を追加
- gRPC サービスで metadata の受け渡しが漏れている -> gRPC interceptor を確認
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 やエラーログをサンプリングすると
障害対応能力が低下するので注意が必要だ。||