この記事は、Managing high cardinality data from Prometheusの意訳です。

はじめに

Prometheusメトリクスにおける最大の課題の1つは、非常に詳細なデータの収集とそのデータの保存コストとのバランスを取ることです。詳細な属性を含むカーディナリティの高いデータは、データに対する貴重な洞察を提供し、サービス内の問題を特定するのに役立ちますが、その余分なデータには追加コストがかかります。この投稿では、Prometheusと New Relic を使用してカーディナリティの高いデータを管理し、コストを抑えながら必要な詳細なデータを取得する方法を学びます。

カーディナリティとは?

本題に入る前に少し用語を定義しておきましょう。カーディナリティという用語は、タイムスケールとデータ一意性を表現するためのメタデータがどれだけ詳細かを定義するために使用されます。カーディナリティの解像度を上げることは、より深く多角的な洞察を得ることができるため、非常に重要です。例えば、分散アプリケーションを監視する場合、ホスト名や地域などの一意の識別子を持つことが有用です。これらの属性のユニークな組み合わせは、カーディナリティの1つの単位となります。

New RelicではNRQL (New Relic Query Language) を使ってカーディナリティを確認することが可能

カーディナリティが高すぎることによる課題

しかし、カーディナリティは、特にPrometheusのkubernetes_sd_configのような、有用なカーディナリティと過剰なカーディナリティのバランスを取るために熟考された設定を必要とするソリューションを適用する場合、扱いにくいものになる可能性があります。例えば、kubernetes_sd_configの特定の構成では、ホストIPアドレスやPodのUUIDを公開することがありますが、どちらも日々のモニタリングでは役に立たないことがよくあります。利用可能なすべてのメトリクスを取り込むことで、システム内のメトリクスデータを迅速に観察できるようになったとしても、過剰なデータを取り込んで保存するために高いコストがかかる可能性があります。

クラウドネイティブ環境を監視する場合、カーディナリティの突然のスパイクに対処しなければならないことがよくあります。これは、カーディナリティが中程度以下の指標が、突然カーディナリティの高い指標に変化することで起こります。この現象は、さまざまな偶発的な理由で発生する可能性があります。例えば、チームの誰かがuser_idというラベルを追加したとします。Prometheusはラベルの値が変わるたびに新しい時系列を作成するため、膨大な量のデータが発生する可能性があります。

カーディナリティを調整する5つのアプローチ

このブログ記事では、Prometheus remote_write設定を使ってNew Relicにデータを送信しながら、Prometheusサーバーが収集するメトリクスのカーディナリティを管理する方法を紹介します。具体的には以下のステップで分析していきます。

  1. Prometheusサーバーの既存のカーディナリティを確認する
  2. スクレイプジョブが持つカーディナリティを確認する
  3. remote_write設定から特定のジョブを除外する
  4. Prometheusジョブからカーディナリティを削減する
  5. Drop Rulesを使いNew Relic側でカーディナリティを削減する

なお、この記事を理解するにあたり、まずPrometheusサーバーの設定の基本を理解する必要があります。

それでは、いよいよ本題に入ります。

1. Prometheus サーバーの既存のカーディナリティを確認する

まずはPrometheus側で現状を把握しましょう。次の PromQL クエリを使用して、さまざまな属性に基づいて情報を確認できます。

topk(10, count by (job)({job=~".+"}))

このクエリを実行すると、Prometheus はラベル ジョブによってセグメント化されたサーバーの現在のカーディナリティを表示します。ジョブが重大なカーディナリティを生成している場合は、次のクエリを使用してジョブ自体を調査できます。 

topk(10, count by (__name__)({job='my-example-job'}))

このクエリは、my-example-jobというjob内のメトリクス名 (Prometheus では__name__で表される) のカーディナリティを返します。

Prometheus remote_writeを使用して New Relic にデータを送信している場合、次の New Relic Query Language (NRQL) クエリを使用してジョブのカーディナリティを確認できます。

FROM Metric SELECT cardinality() FACET job, metricName SINCE today

NRQL は__name__の代わりにmetricNameを使用していることに注意してください。

2. スクレイプジョブが持つカーディナリティを確認する

次に、高コストになっているジョブを特定しましょう。Prometheusでは、メトリクスに付与されている数が多いラベル名や時系列データ数の多いメトリクス名などを以下のURLで確認することができます。

http://localhost:9090/tsdb-status

The most data-intensive labels and series counts in Prometheus

Prometheus上でもラベルやデータポイント数が多いメトリクスを確認可能

前の画像を例とすると、http://localhost:9090/api/v1/label/client_id/valuesにアクセスすることで、client_idというラベルにドリルダウンすることができます。

次に、以下のクエリでどのジョブがどんなラベルを持っているかを確認することができます。

topk(10, count by (job)({<label name>=~'.+'}))

過剰なカーディナリティを生成しているラベルやジョブを特定したら、そのカーディナリティの影響を低減または制限するためのアプローチを行っていきましょう。

3. remote_write設定から特定のジョブを除外する

Prometheusの特定のジョブからNew Relicにデータを送信したくない場合、scrape_configsを更新してジョブを省略することができます。これにより、カーディナリティデータが送信され、予期せぬスパイクが発生するのを防ぐことができます。

例えば、Prometheusがデータストアにデータを送信しないようにするには、以下のwrite_relabel_configsを設定に追加します。この例では、New Relicにデータを送信するのURLの例として使用しています。

...
remote_write:
- url: <https://metric-api.newrelic.com/prometheus/v1/write?prometheus_server=my-prom-server>
  bearer_token: <your NR API key>
  write_relabel_configs:
    - action: 'drop'
	  source_labels: ['job']
	  regex: (job-to-omit)
...

この例は、job-to-omitに一致するジョブを持つ全てのデータを削除する設定です。

また、keep アクションを使用することで、残すべきジョブを指定することも可能です。

  write_relabel_configs:
    - action: 'keep'
	  source_labels: ['job']
	  regex: (<other job names to keep>)
...

アクションがkeepに変更されていることに注意してください。keepアクションは、正規表現に一致する値を持つラベルのみデータを保存します。逆に言えば一致しないラベルを含まないすべてのデータを削除します。どちらのアプローチも有効ですが、どちらか一方を設定する方が簡単な場合があります。

以下のように、PrometheusサーバーとNew Relic側でカーディナリティの違いを確認することができます。

  • PromQL: topk(10, count by (job)({job=~".+"}))
  • NRQL: FROM Metric SELECT cardinality() FACET job SINCE today

2つのクエリを比較することで、scrape jobのデータがNew Relicに送信されていないことを確認することができます。New Relic での使用状況に影響を与えることなく、新しく追加された scrape job のカーディナリティを評価することができます。

4. Prometheusジョブからカーディナリティを削減する

Prometheusでは、カーディナリティの高いラベルを削除したり、不要なデータをスクレイプしないようにすることで、ジョブのカーディナリティを下げることができます。具体的な例を見てみましょう。

ラベルをDropする

スクレイプする際にlabelkeeplabeldropを使用することで特定のラベルをドロップすることができます。これらの設定により、監視するデータのカーディナリティを調整することができます。ただし、出来上がったデータ(ディメンション)が意味のある一意に識別できることを確認する必要があります。でないとデータが混在したメトリクス値になってしまう可能性があります。例えば、2つの異なるエンティティを一意に識別するラベルを削除してしまうと、それらのエンティティのメモリやCPUなどのメトリクスが同一のデータとして扱われてしまい、矛盾した情報を収集してしまうことになり、どちらのエンティティの状態も理解できなくなってしまいます。

それでも、ラベルを削除することが有効な場合があります。以下はその例です。

scrape_configs:
	- job_name: 'api-service'
	  ...
	  relabel_configs:
		  - action: labeldrop
		    regex: (ip_address)

この設定は、正規表現ip_addressにマッチするラベルを対象とします。この例では、ip_addressラベルを削除してもデータの一意性には影響しませんが、サービスのデプロイ時や再起動時にサービスのIPアドレスが再割り当てされた場合に、カーディナリティがスパイクする可能性を解消することができます。

完全にデータポイントをDropする

高いカーディナリティを管理するための1つの解決策は、そもそもscrapeジョブによって監視されるものを適切なものに調整することです。

ここでは、KubernetesクラスタのすべてのPodからメトリクスをスクレイピングしようとするPrometheusサーバーの設定例を示します。instanceというラベルにPos名が追加されているため、高いカーディナリティになります。

scrape_configs:
	- job_name: 'k8s'
	  kubernetes_sd_configs: 
		  - role: pod
	  relabel_configs:
		  - source_labels:  
		      - __meta_kubernetes_pod_name  
		    action: replace  
		    target_label: instance

ひとつのアプローチとして、特定のnamespaceのみをスクレイプ対象にすることでカーディナリティを減らすことができます。たとえば、ネームスペースmy-example-namespaceのPodのみ監視対象にする場合は、そのnamespaceの結果のみを監視するように設定を更新することができます。

以下の例のrelabel_configs に追加された keep アクションと正規表現に注目してください。

	  relabel_configs:
		  - source_labels:  
		      - __meta_kubernetes_pod_name  
		    action: replace  
		    target_label: instance
		  - action: keep
		    source_labels:
			  - __meta_kubernetes_namespace
		    regex: (my-example-namespace)

5. Drop Rulesを使いNew Relic側でカーディナリティを削減する

例えば、長期保存のコストはかけずに直近は高いカーディナリティのデータを取得したいとします。New Relicでは、カーディナリティの高いデータを管理するためのオプションがいくつか用意されています。データポイントやアトリビュートを完全に削除することも、長期アグリゲートのみから削除することも可能です。長期アグリゲートから属性をドロップすることで、直近のデータを高カーディナリティデータを参照することを実現しつつ、長期保存用データのカーディナリティを削減することができます。

長期アグリゲートでは、メトリックデータを長期間クエリすることができます。New Relic では、これをリテンションと呼んでいます。メトリックデータの正確なリテンション時間は、Retention details for assorted data typesで確認することができます。長期アグリゲートから属性を削除した場合、それらの属性は 1日のカーディナリティ制限に抵触せずに30 日間はクエリ可能です。この30 日間の保持期間は、ドキュメントではraw metric data pointsとして解説されています。

New Relic のDrop Rulesを使用するには、NerdGraph を使用したデータのドロップを参照してください。

さいごに

いかがでしたか?Prometheusは非常にたくさんのメトリクスデータを簡単に収集できる一方、データの保存コストやカーディナリティがボトルネックになるケースがあります。本ポストが皆さんのカーディナティティチューニングの参考になれば幸いです。