監視データの特性 - メトリクスの次元とカーディナリティ

## はじめに
こんにちは、Datadog Japan で Sales Engineer をしている [**AoTo**](https://qiita.com/AoTo0330) です。

この投稿は **[<font color="#6495ED">AoTo Advent Calendar 2024</font>](https://qiita.com/advent-calendar/2024/aoto) 11日目**の記事です。

オブザーバビリティで[主要なシグナル](https://github.com/cncf/tag-observability/blob/main/whitepaper.md#observability-signals)とされる**メトリクス・ログ・トレース**はそれぞれ異なる特徴を持った監視データです。それぞれの目的・データ形式・運用上の課題・データ特性を考慮して、オブザーバビリティを拡大することが重要です。

今回はその中でも「**メトリクスの次元(demension)とカーディナリティ**」に焦点を当てて解説します。

解説ではオブザーバビリティの共通認識としてまとめられている **CNCF Tag Observability** の『[**Observability Whitepaper**](https://github.com/cncf/tag-observability/blob/main/whitepaper.md)』、標準化の例として **OpenTelemetry**/**Prometheus**・製品ベンダーの例として **Datadog** の公式ドキュメントを参考に解説を行います。

### 考慮事項
本投稿では以下の用途で表現を使い分けます。

- **監視情報**: 監視データとそれから得られた分析情報などを含む、監視に関わる全てのデータ
- **監視データ**: 人間が機械の動きを推測できるシステムの出力
- **メトリクス**: 時系列の数値データ
- **メトリック**: メトリクスの単一のデータ

## メトリクスの特徴
メトリクスは最も一般的に収集・参照される監視データの一つです。
これらは単数系でメトリックとも呼称され、単数の場合は単一の種類または単一のデータであることを表します。ここでは、後者の単一のデータを指してメトリックと表現します。

メトリクスの各データは、必ずどの時点のデータであるかの**データポイント**と、その時点での何かしらを表現する**数値**が対のメトリックとして表現されます。もちろん、どのようなメトリクスであるかを示す**名称**と値の**データ型**(整数、浮動小数点など)、そして**測定単位**をメタデータ・ラベルとして付与します。

こうしたメタデータ・ラベルの組み合わせを、**次元(demension)** と呼びます。

さらに、メトリクスは不連続にデータポイント毎に数値を記録・集計するため、完全なデータではありません。このデータポイントの間隔を粒度や**解決時間(resolution)** と呼びます。

気温を例にすると、1分毎に気温計の温度を測れば1分間隔の気温の推移が分かりますが、その間にどのように気温が変化したかの詳細情報は欠落しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2363567/cffa43fd-c0e4-dfd5-f35b-2db5700e4351.png)
OpenTelemetry Docs『[Metrics Data Model](https://opentelemetry.io/docs/specs/otel/metrics/data-model/#metric-points)』より Gauge メトリクスの説明から引用

メトリクスは、大きく数値データと集約データの2つに分類できます。
さらに、メトリクスのタイプ(種類)は5つに分けられます。[^1]
[^1]: カウント(count)は合計(sum)とも表現され、ヒストグラムと分布は区別されない場合がありますが、以下は最も区別が多い Datadog の説明に合わせています。

```mermaid
graph LR
 1["メトリクス"] --> 2["数値(numeric)データ"]
 1 --> 3["集約(aggrigated)データ"]
 2 --> 4["ゲージ(gauge)"]
 2 --> 5["カウント(count)"]
 5 --[集約]--> 6["レート(rate)"]
 3 --> 7["ヒストグラム(histgram)"]
 3 --> 8["分布(distribution)"]
 3 ---> 6
```

異なるメトリクスタイプであっても、メトリクスの基本的な特性は同じです。

こうした特性によりメトリクスは効率的なシグナルであり、モニタリングにおいても重要な監視データとして広く活用されています。
 - 事前に記録・収集間隔(resolution)が決まっているため、トラフィック量に応じてボリュームが増加しない
 - メタデータ(次元)毎のグルーピング・フィルタリング・集計ができ、統計的な推測・分析ができる(長期計画の策定、インシデントの事後分析)
 - 少量のデータから状況の概要を素早く把握したり、最も迅速に障害検知を行える

このようにメトリクスはシステム全体の動作と健全性を迅速に把握するために役立ちます。

一方で、そのメトリクスの変化要因を直接的に把握したり、障害の根本原因分析を行うためには情報が不足しています。こうした、どうして・何が起きたなどの情報は、ログ・トレースなどの他の監視データに遷移し調査することが有効です。

## メトリクスの次元とカーディナリティ
ここまでの解説を踏まえると、メトリクスは軽量で収集が容易な監視データであり、様々な種類のメトリクスを様々な箇所から取得すると良いように見えるかもしれません。

しかし、ここで注意しなければならないのでメトリクスのカーディナリティです。メトリクスにおいてカーディナリティとは、一定期間に収集された一意のメトリクス名を持つ一連のメトリクスの数です。カーディナリティはメトリクスのメタデータである次元(demension)の種類×数で乗算されます。

再度、気温を例に考えてみましょう。気温は日本では少なくとも北海道・東北・関東・中部・関西・中国・四国・九州・沖縄のように複数地点でデータを収集します。これが都道府県毎になればカーディなリティは9つから47になりますし、市区町村毎になればあっという間に1000を超えます。さらにこれに、観測機器の種類やメーカー温度計の種別など細かな情報を含めると一気にそのカーディナリティは増加します。

このように、特にシステム上のメトリクスは急激に増加するリスクが高いものです。例えばメタデータとしてプロセス ID やユーザー ID などの上限のない一意な識別しを条件を付与すると、実質的に無制限にカーディナリティが増加する要因となります。

カーディナリティが増加すると、それらを保持するコストが比例して増加し、セルフマネージドなプラットフォームの場合は大量なデータによる分析パフォーマンスへの悪い影響が懸念されます。

こうした高カーディナリティの問題は、メトリクスの特性を理解しないまま利用することで頻繁に発生します。持続的に観測対象とする必要がない次元(demension)や、存続期間が短く頻繁に入れ替わる次元(demension)の付与の代わりに、集約した値を利用するかログ・トレースなど別の監視データを利用することを検討しましょう。

## プラクティス
メトリクスのカーディナリティが増加しすぎないためにも、いくつかの対処を行うことが推奨されます。メトリクスの生成時・取得時・保管時にそれぞれメトリクスのカーディナリティへの影響を考慮した対処が行えます。

![reduce_cardinarity.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2363567/4c069f22-e2a1-842d-e883-f4fcadbe8c4a.png)

### 生成時のラベル付け
メトリクスのカーディナリティが無制限に増加してしまう場合、メタデータ・ラベルとして付与された測定単位が細かすぎるか、分析に向いていないものであることがほとんどです。

さらにこうしたメタデータ・ラベルは、「トラフィック量に応じてボリュームが増加しない特性」を損ないます。トラフィック量に応じてメタデータ・ラベルが増加することで、カーディナリティが大幅に増加し場合によってはその組み合わせから天文学的な数字へと爆発してしまいます。

そのため、そもそもこうした単位で測定・記録を行わないことが最も重要で簡単なプラクティスとなります。メタデータをどういった次元(demension)・どのくらいの頻度(resolution)で取得するかで、そのカーディナリティは大幅に変化することを念頭に取得するメトリクスを考慮しましょう。

### 取得時のフィルタリング
既にこうした測定単位でメトリクスを生成している場合や、詳細な測定単位から集計値を算出したい場合は、メトリクスの取得時にメタデータ・ラベルのフィルタリングを行い、分析時に少ないカーディナリティでメトリクスを扱えます。

Promethus は pull 型のアーキテクチャで問い合わせたメトリクスのみを取得することができます。[`relabel_config`](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config) を活用すると、メトリクスの取得前にメタデータ・ラベルを集約し、カーディナリティを減らせます。

他にも、push 型で収集されたメトリクスから分析に不要なメタデータ・ラベルを破棄することでカーディナリティを下げ、コストを削減できます。Datadog の [Metrics without Limits™︎](https://docs.datadoghq.com/ja/metrics/metrics-without-limits/) は収集したメトリクスを保持する際に、選択的にラベルを指定しカーディナリティを減らした状態のメトリクスを保持できます。

### 保管時のロールアップ
それでも大量のカーディナリティのメトリクスを保管してしまった場合、保存プラットフォーム側で利用状況や保管日数でのメトリクスのロールアップを行い、コストを削減する必要があります。

ロールアップの仕組みは主にプラットフォーム側で自動的に制御されます。Datadog の場合はこうしたロールアップを行わないのが特徴ですが、プラットフォームによっては数日間の保持の後データをロールアップします。

ロールアップの手法としては時系列ロールアップが一般的です。1分粒度のメトリクスであれば5分・1時間とメトリックを集約しその平均値などにデータを集約して保持コストを最適化します。

## おわりに
メトリクスの特性である次元・カーディナリティを理解することで、メトリクスの特性から恩恵を受けて効率的・迅速にシステムの状態を把握することができます。

今回解説できなかった、時間性によるメトリクスの捉え方や思想の違いと相互互換性なども、メトリクスの特性を深く理解するには大切です。是非、参考文献を直接確認し、メトリクスを使いこなしていきましょう🐶

### 参考文献
 - **CNCF Tag Observability** 『[Observability Whitepaper](https://github.com/cncf/tag-observability/blob/main/whitepaper.md)』
 - **OpenTelemetry**『[Metrics Data Model](https://opentelemetry.io/docs/specs/otel/metrics/data-model/#metric-points)』
 - **Prometheus**『[DATA MODEL](https://prometheus.io/docs/concepts/data_model/)』
 - **Datadog**『[Metrics](https://docs.datadoghq.com/metrics/)』『[Metrics Types](https://docs.datadoghq.com/metrics/types/)』『[Distribution](https://docs.datadoghq.com/metrics/distributions/)』