【入門編】Datadog Custom Metricsのインデックス設計とタグカーディナリティ爆発を防ぐ命名規則のアンチパターン – 運用監視・オブザーバビリティ活用バイブル

こんにちは!プロダクション環境の安定稼働と、毎月送られてくるクラウドの請求書に冷や汗をかいていませんか?
オブザーバビリティの世界へようこそ。私はこれまで数々のシステムの炎上と、監視基盤のコスト暴走を救ってきたシニア・アーキテクトです。

今回は、Datadogの運用において「最も恐ろしい罠」でありながら、多くの開発者がうっかり踏み抜いてしまうカスタムメトリクスのカーディナリティ爆発(高 cardinality explosion)について、そのメカニズムと回避のための鉄則を徹底的に解説します。

これをマスターすれば、無駄な課金に怯えることなく、真に価値のあるアラートとダッシュボードを手に入れることができますよ。さあ、一緒に本質を学んでいきましょう!

—

1. なぜDatadogの「カスタムメトリクス」で破滅が起きるのか?

Datadogをはじめとするモダンな監視ツールの最大の強みは、「タグ(Tag)」を用いた多次元分析です。
「どの環境で、どのサービスで、どのエンドポイントでエラーが起きたのか」を、`env:production`, `service:payment`, `endpoint:/checkout` のようなタグを付与することで、自由自在にスライス&ダイスできます。

しかし、この強力な仕組みには「カーディナリティ(値の多様性)」という厳格な物理法則があります。

カーディナリティ爆発とは何か?

カーディナリティとは、あるタグが持つ「ユニークな値の数」のことです。
例えば、`env` タグであれば `production`, `staging`, `development` の 3つ です。これは低カーディナリティであり、Datadogにとって非常に扱いやすい状態です。

しかし、ここに ユーザーID や UUID、タイムスタンプ などの「毎回値が変わるもの」をタグとして突っ込んだ瞬間、話は変わります。
1日に100万人のユーザーがアクセスしたら、そのタグのユニークな値は 100万 になります。

Datadogは、送られてきたメトリクスとタグの組み合わせ(タイムシリーズ)ごとにインデックスを作成し、集計を行います。
つまり、安易に高カーディナリティな値をタグに混ぜると、爆発的な数の時系列データ(Custom Metrics)が生成され、あなたの会社のDatadogの請求書が跳ね上がり、最悪の場合はクエリがタイムアウトして監視画面が真っ白になります。

—

2. 【閲覧注意】やってはいけない!タグ設計のアンチパターン3選

まずは、現場で本当によく見かける「やってはいけない設計」を直視しましょう。これらはすべて、明日から即座に禁止すべきアンチパターンです。

アンチパターン①:UUIDやリクエストIDをタグにする

【最悪の例】一意なIDをそのままタグに含める
from datadog import statsd
import uuid

def handle_request(request):
req_id = str(uuid.uuid4())
# ❌ 破滅への片道切符:req_idごとに新しい時系列がDatadog上に無限生成される
statsd.increment(“api.request.count”, tags=[f”request_id:{req_id}”, “status:200”])

  • 何が起きるか: リクエストの数だけメトリクスの種類が増殖します。1日数千万リクエストがあるサービスなら、数日で見積もりが破綻します。一意なIDの追跡は、メトリクスではなく「ログ(Log)」や「分散トレーシング(APM)」の仕事です。

アンチパターン②:タイムスタンプやミリ秒単位の数値をタグにする

import time

【最悪の例】現在のタイムスタンプをタグにする
current_time = int(time.time())
❌ 毎秒(あるいは毎ミリ秒)値が変わるため、無限に時系列が増え続ける
statsd.gauge(“system.active_jobs”, 42, tags=[f”timestamp:{current_time}”])

  • 何が起きるか: タイムスタンプは無限のカーディナリティを持ちます。これを入れた瞬間にDatadogのインデックスはパンクします。時系列データ自体がタイムスタンプを持っているのだから、タグに含める必要は絶対にありません。

アンチパターン③:メールアドレスや顧客名をそのまま突っ込む

【最悪の例】エンドユーザーの情報をタグにする
user_email = “john.doe@example.com”
❌ アクティブユーザー数だけメトリクスが生成される
statsd.increment(“user.login.success”, tags=[f”user:{user_email}”])

  • 何が起きるか: ユーザーごとの利用状況を知りたい気持ちは分かりますが、これもメトリクスの仕事ではありません。どうしても集計したい場合は、後述する「ハッシュ化」や「グルーピング」のテクニックを使います。

—

3. 請求書爆発を防ぐ!正しい集約設計と命名規則のマスタークラス

では、どのように設計すれば安全かつリッチなオブザーバビリティを実現できるのでしょうか?
ここからは、プロのアーキテクトが実践している設計原則を伝授します。

原則1:タグに含めていいのは「有限個のカテゴリ」だけ

タグとして許可してよいのは、以下のような「有限かつ既知の集合(Low Cardinality)」だけです。

  • `env` (production, staging, development)
  • `region` (ap-northeast-1, us-east-1)
  • `tier` (web, api, worker)
  • `status_code` (200, 400, 500)

原則2:メトリクス名自体に意味を持たせすぎない

よくある失敗として、メトリクス名に細かすぎる情報を埋め込むケースがあります。

  • ❌ `api.users.12345.profile.view.count` (ユーザーIDがメトリクス名に入っている)
  • ⭕ `api.profile.view.count` (メトリクス名は汎用的にし、`tier:api` などのタグでフィルタリングする)

原則3:どうしても粒度を細かくしたいときは「丸め込み(Bucketing)」を使う

例えば、価格帯や処理時間など、連続値や高カーディナリティになりやすいデータを扱いたい場合は、範囲ごとに「丸める(バケツ詰めする)」のが定石です。

【正しい例】処理時間を適切なバケツ(範囲)に丸めてタグにする
def get_latency_bucket(duration_ms: float) -> str:
if duration_ms < 100: return "lt_100ms" elif duration_ms < 500: return "100ms_500ms" else: return "gt_500ms" duration = 350.5 bucket = get_latency_bucket(duration) ✅ これならタグのバリエーションは常に数パターンに制限される statsd.histogram("api.latency", duration, tags=[f"latency_bucket:{bucket}"]) ---

4. 【実践】安全でクリーンなDatadogカスタムメトリクス実装

それでは、実際にPython(`datadogpy`ライブラリ)を使って、安全で美しいカスタムメトリクスを送信する「HelloWorld」的なコードを見てみましょう。

1. ライブラリのインストール

まずは必要なパッケージをインストールします。

pip install datadog

2. 安全なメトリクス送信スクリプト

以下のコードは、カーディナリティ爆発を起こさないよう厳選されたタグのみを付与する模範的な実装です。環境変数から設定を読み込むことで、ステージングとプロダクションの混同も防ぎます。

import os
from datadog import initialize, statsd

1. Datadogの初期設定
APIキー等は環境変数(DATADOG_API_KEYなど)から自動読み込みされます
options = {
‘statsd_host’: ‘127.0.0.1’, # Datadog Agentが稼働しているホスト
‘statsd_port’: 8125
}
initialize(options)

def process_order(item_id: str, amount: int, user_tier: str):
“””
注文処理を行う関数(安全なメトリクス送信の例)
“””
# 【重要】item_idやuser_idはメトリクスのタグには入れない!
# 代わりに、ユーザーの「ティア(VIP, Regularなど)」という低カーディナリティな情報を使う

# 承認された安全なタグのセット
safe_tags = [
f”env:{os.getenv(‘ENVIRONMENT’, ‘development’)}”,
f”service:order-service”,
f”user_tier:{user_tier}” # 値の種類が有限(例: vip, standard, free)
]

try:
# 実際の処理(ここでは省略)
print(f”Processing order for item: {item_id}, amount: {amount}”)

# 2. カウントメトリクスの送信(成功)
statsd.increment(“order.processed.count”, tags=safe_tags)

# 3. 処理金額のヒストグラム(分布)送信
statsd.histogram(“order.amount.dollars”, amount, tags=safe_tags)

except Exception as e:
# エラー時はステータスを変更してカウント
error_tags = safe_tags + [“status:error”]
statsd.increment(“order.processed.count”, tags=error_tags)
raise e

— 動作確認用実行ブロック —
if __name__ == “__main__”:
print(“Datadog Custom Metrics – Safe Implementation Test”)

# テスト実行(数回呼んでもカーディナリティは爆発しません!)
process_order(item_id=”item_999″, amount: 1500, user_tier=”vip”)
process_order(item_id=”item_888″, amount: 300, user_tier=”standard”)

print(“Metrics sent successfully without blowing up your billing!”)

—

5. すでに爆発してしまったときの「リファクタリング手法」

「おい、記事を読むのが遅くて、すでにウチのDatadogはカーディナリティ爆発を起こしているよ!」という絶望的な状況のあなたへ。慌てず以下の手順で緊急鎮火を行ってください。

1. Datadog UIでの犯人特定

  • 「Metrics Explorer」または「Custom Metrics」の管理画面を開き、どのメトリクスが異常な数のタイムシリーズ(Active Metrics)を生成しているかを特定します。

2. コード側の改修(タグの削除)

  • 該当するコードを特定し、UUIDや動的な文字列をタグから即座に削除・または前述の「バケツ詰め」に変更します。

3. メトリクスのライフサイクル(消滅を待つ)

  • Datadogのカスタムメトリクスは、データ送信が停止してから一定期間(通常は数時間〜数日)経過すると、自動的にアクティブでなくなります(インデックスから外れ、課金対象外になります)。

4. メトリクスの除外設定(必要に応じたサポートへの連絡)

  • どうしてもすぐに止めたい古いメトリクスがある場合は、DatadogのMetric Summaryから除外設定を行うか、サポートに相談してインデックスのクリーンアップを依頼します。

—

まとめ

いかがでしたでしょうか?
Datadogのカスタムメトリクスは強力な武器ですが、一歩間違えると「会社のお金を燃やすマシーン」になってしまいます。

  • UUIDやタイムスタンプは絶対にタグに入れない(それはログやAPMの仕事)。
  • タグに使うのは「有限個のカテゴリ(環境、ステータス、ティアなど)」に絞る。
  • 詳細なデータが必要なときは、バケツ(丸め込み)を活用する。

この鉄則を守るだけで、あなたのシステムのオブザーバビリティは劇的に美しく、そして健全になります。
明日からの設計、ぜひ見直してみてくださいね。あなたの運用の平穏を、心から応援しています!

タイトルとURLをコピーしました