- Authors
- Name
はじめに
Observabilityのコストがクラウドインフラ支出の上位項目に浮上している。2025年のグローバルObservability市場は285億ドルを突破し、2026年末には341億ドルに達する見込みだ。マイクロサービスの普及に伴い、トレースが全体コストの60〜70%を占め、ログが20〜30%を占める状況では、データ量に比例してコストが急増する。
しかし、単にデータを削減すれば良いわけではない。重要なのはシグナルとノイズを分離し、コストを抑えつつObservabilityの品質を維持することだ。
本記事では、OpenTelemetry Collectorを中心としたテレメトリパイプラインのコスト最適化戦略を、実践的な設定例とチェックリスト付きで解説する。
コスト構造の分析
シグナル別コスト比率
| シグナル | コスト比率 | 主なコスト要因 | 最適化の難易度 |
|---|---|---|---|
| トレース | 60〜70% | 高カーディナリティ、大容量ペイロード | 高 |
| ログ | 20〜30% | 非構造化データ、フルテキストインデクシング | 中 |
| メトリクス | 5〜15% | タイムシリーズのカーディナリティ爆発 | 中 |
| プロファイル | 1〜5% | CPU/メモリプロファイルのデータサイズ | 低 |
コスト発生ポイント
[生成] --> [収集/転送] --> [処理/加工] --> [インデクシング] --> [保存] --> [クエリ]
| | | | | |
SDK ネットワーク Collector バックエンド ストレージ コンピュート
オーバーヘッド 帯域幅 CPU/メモリ I/O ディスク/S3 クエリコスト
最適化の原則は「パイプラインのできるだけ上流で不要なデータを除去する」ことだ。生成段階で除去すれば後続のすべてのコストを削減できる。
サンプリング戦略
Head Sampling vs Tail Sampling
| 項目 | Head Sampling | Tail Sampling |
|---|---|---|
| 決定タイミング | トレース開始時(SDK) | トレース完了後(Collector) |
| コスト削減率 | 高い(帯域幅も節約) | 中程度 |
| データ品質 | 低い(エラー漏れの可能性) | 高い(エラー/高レイテンシ100%保持) |
| 実装の複雑さ | 低い | 高い(メモリ、ルーティング必要) |
| 適用シナリオ | 非クリティカルサービス | 本番のコアサービス |
Tail Sampling実践設定
# OpenTelemetry Collector - Tail Sampling設定
processors:
tail_sampling:
decision_wait: 30s
num_traces: 100000
expected_new_traces_per_sec: 1000
policies:
# エラーのあるトレースは100%保持
- name: error-policy
type: status_code
status_code:
status_codes:
- ERROR
# 500ms以上のレイテンシは100%保持
- name: latency-policy
type: latency
latency:
threshold_ms: 500
# 決済・認証など重要サービスは100%保持
- name: critical-service-policy
type: string_attribute
string_attribute:
key: service.name
values:
- payment-service
- auth-service
# その他の正常トレースは5%のみ
- name: normal-traffic-policy
type: probabilistic
probabilistic:
sampling_percentage: 5
2-Tier Collectorアーキテクチャ
本番環境でTail Samplingを安定的に運用するには、Agent CollectorとGateway Collectorを分離する構成が必須だ。
[サービス] --OTLP--> [Agent Collector (DaemonSet)]
|
トレースIDベースルーティング
|
[Gateway Collector] --> [Tempo/Jaeger]
(Tail Sampling実行)
ログフィルタリングパイプライン
レベルベースフィルタリング
本番環境でDEBUG/TRACEレベルのログをCollectorでドロップすると、通常30〜50%のログ量を削減できる。
processors:
# DEBUG/TRACEログのドロップ
filter/drop-debug:
error_mode: ignore
logs:
log_record:
- 'severity_number < SEVERITY_NUMBER_INFO'
# ヘルスチェックログのドロップ
filter/drop-healthcheck:
error_mode: ignore
logs:
log_record:
- 'IsMatch(body, ".*GET /health.*")'
- 'IsMatch(body, ".*GET /readyz.*")'
- 'IsMatch(body, ".*kube-probe.*")'
# 不要な属性の除去でペイロード削減
transform/reduce-attributes:
error_mode: ignore
log_statements:
- context: log
statements:
- delete_key(attributes, "log.file.path")
- delete_key(attributes, "log.iostream")
- truncate_all(attributes, 256)
- limit(attributes, 20)
フィルタリング効果の測定
# Collector内部メトリクスでフィルタリング効果を測定
RECEIVED=$(curl -s http://localhost:8888/metrics | \
grep 'otelcol_receiver_accepted_log_records' | \
awk '{sum += $2} END {print sum}')
EXPORTED=$(curl -s http://localhost:8888/metrics | \
grep 'otelcol_exporter_sent_log_records' | \
awk '{sum += $2} END {print sum}')
if [ "$RECEIVED" -gt 0 ]; then
DROP_RATE=$(echo "scale=2; (1 - $EXPORTED / $RECEIVED) * 100" | bc)
echo "受信ログ: $RECEIVED"
echo "送信ログ: $EXPORTED"
echo "ドロップ率: ${DROP_RATE}%"
fi
メトリクスカーディナリティ管理
カーディナリティ爆発はObservabilityコストを予測不能にする主要因だ。
危険なパターンと対策
| 危険パターン | 例 | 対策 |
|---|---|---|
| ユーザーIDをラベルに使用 | user_id="u12345" | ラベル除去、ログ/トレースに移動 |
| リクエストパスの原文 | path="/api/users/12345" | パス正規化 path="/api/users/:id" |
| Pod名 | pod="web-7f8c9-xk2m" | Deployment名のみ使用 |
| エラーメッセージ全文 | error="Connection refused: 10.0.1.42" | エラーコードで分類 |
Collector側でのカーディナリティ制御
processors:
# メトリクス属性の変換でカーディナリティを制御
transform/normalize-url:
error_mode: ignore
metric_statements:
- context: datapoint
statements:
- replace_pattern(attributes["url.path"], "^/api/users/[^/]+", "/api/users/:id")
- replace_pattern(attributes["url.path"], "^/api/orders/[^/]+", "/api/orders/:id")
- delete_key(attributes, "user.id")
- delete_key(attributes, "request.id")
ストレージティアリング
データの経過時間に応じてストレージ層を自動的に移行するHot/Warm/Coldアーキテクチャで、長期保存コストを大幅に削減できる。
| ティア | 保持期間 | ストレージ | クエリ性能 | コスト |
|---|---|---|---|---|
| Hot | 0〜7日 | SSD/NVMe | 最高速 | 高 |
| Warm | 7〜30日 | HDD/S3 Standard | 中速 | 中 |
| Cold | 30日〜1年 | S3 Glacier/IA | 低速 | 低 |
コスト最適化チェックリスト
Phase 1:即時適用可能(1〜2週間)
- 本番環境でDEBUG/TRACEレベルのログをCollectorでドロップしているか?
- ヘルスチェック・readiness probeのログ/トレースをフィルタリングしているか?
- メトリクスラベルにユーザーID・リクエストIDなどの高カーディナリティ値が含まれていないか?
- URLパスラベルが正規化されているか?
- Collectorに
memory_limiterプロセッサが設定されているか? - Collector内部メトリクス(ドロップ率、キューサイズ、メモリ使用量)を監視しているか?
Phase 2:中期最適化(2〜4週間)
- Tail Samplingがエラー/高レイテンシトレースを100%保持しつつ、正常トラフィックを5〜10%に制限しているか?
- 2-Tier Collectorアーキテクチャ(Agent + Gateway)が構成されているか?
- トレースIDベースルーティング(LoadBalancing Exporter)が設定されているか?
- 不要なログ属性を除去しているか?
- サービスごとのObservability Budgetが定義されているか?
- カーディナリティ監視ダッシュボードが構築されているか?
Phase 3:長期インフラ最適化(1〜3ヶ月)
- Hot/Warm/Coldストレージティアリングが構成されているか?
- S3 Intelligent-Tieringまたは同等の自動ティアリングが有効化されているか?
- メトリクスのダウンサンプリングポリシーが適用されているか?
- ILM/保持ポリシーが規制要件を満たしているか?
- CI/CDパイプラインにカーディナリティ検証ゲートが含まれているか?
トラブルシューティング
Collectorのメモリ使用量が増加し続ける場合
# 1. Collectorのメモリ使用量を確認
kubectl top pods -n observability -l app=otel-gateway
# 2. pprofでメモリプロファイリング
curl -s http://localhost:1777/debug/pprof/heap > heap.prof
go tool pprof -top heap.prof
# 3. Tail Samplingキューの状態を確認
curl -s http://localhost:8888/metrics | grep tail_sampling
カーディナリティ爆発の原因サービスを特定する方法
# Prometheusで上位メトリクスを確認
curl -s http://prometheus:9090/api/v1/status/tsdb | \
jq '.data.seriesCountByMetricName | sort_by(-.value) | .[0:10]'
# 特定メトリクスのラベルカーディナリティを分析
curl -s 'http://prometheus:9090/api/v1/query?query=count(http_server_request_duration_seconds_bucket) by (service_name)' | \
jq '.data.result | sort_by(-.value[1] | tonumber) | .[0:10]'
まとめ
Observabilityコスト最適化は、単にデータを減らすのではなく、シグナルとノイズを精密に分離してObservabilityの品質を維持しながらコスト効率を最大化するエンジニアリング活動だ。
本記事で取り上げた戦略の要約:
- サンプリング:Head Samplingでネットワークコストまで削減し、コアサービスにはTail Samplingでエラー/高レイテンシトレースを100%保持する。
- フィルタリング:DEBUG/ヘルスチェックログをCollectorでドロップし、属性整理でペイロードサイズを縮小する。
- カーディナリティ管理:高カーディナリティラベルを除去し、URLパスを正規化し、Observability Budgetでサービスごとの使用量を制御する。
- ストレージティアリング:Hot/Warm/Coldアーキテクチャでデータの経過時間に応じて低コストストレージに自動移行する。
これらの戦略を体系的に適用すれば、全体のObservabilityコストを60〜80%削減しつつ、障害対応に必要なデータを完全に保持できる。