インストルメンテーション
このページは、コードのインストルメンテーションに関する意見を反映したガイドラインのセットを提供します。
インストルメンテーションの方法
短い答えは、すべてをインストルメントすることです。すべてのライブラリ、サブシステム、およびサービスには、それがどのように機能しているかの大まかなアイデアを与えるための少なくともいくつかのメトリックが必要です。
インストルメンテーションは、コードの不可欠な部分であるべきです。メトリッククラスは、それらを使用するのと同じファイルにインスタンス化します。これにより、エラーを追跡する際に、アラートからコンソール、コードへの移動が容易になります。
3種類のサービス
監視の目的上、サービスは一般的に3つのタイプに分類できます。オンラインサービング、オフライン処理、バッチジョブです。それらの間には重複がありますが、すべてのサービスはこれらのカテゴリのいずれかにうまく適合する傾向があります。
オンラインサービングシステム
オンラインサービングシステムとは、人間または他のシステムが即時の応答を期待しているシステムです。たとえば、ほとんどのデータベースとHTTPリクエストはこのカテゴリに分類されます。
そのようなシステムにおける主要なメトリックは、実行されたクエリの数、エラー、およびレイテンシです。進行中のリクエストの数も役立つ場合があります。
失敗したクエリのカウントについては、以下の「失敗」セクションを参照してください。
オンラインサービングシステムは、クライアントとサーバーの両方で監視する必要があります。両方の側で異なる動作が見られる場合、それはデバッグに非常に役立つ情報です。サービスに多くのクライアントがある場合、サービスがそれらを個別に追跡することは現実的ではないため、独自の統計に頼る必要があります。
クエリをカウントする開始時と終了時のどちらかに一貫性を持たせてください。終了時にコーディングが容易になり、エラーとレイテンシの統計と一致するため、推奨されます。
オフライン処理
オフライン処理では、応答を積極的に待っている人はいません。また、作業のバッチ処理が一般的です。処理の複数のステージがある場合もあります。
各ステージについて、受信したアイテム、進行中のアイテム数、最後に処理した時間、送信したアイテム数を確認します。バッチ処理の場合は、送受信するバッチも追跡する必要があります。
システムが最後に何かを処理した時刻を知ることは、それが停止したかどうかを検出するのに役立ちますが、それは非常に局所的な情報です。より良いアプローチは、システム全体にハートビートを送信することです。つまり、ダミーアイテムがすべてを通過し、挿入されたときのタイムスタンプを含みます。各ステージは、見た最新のハートビートタイムスタンプをエクスポートできます。これにより、システム全体でアイテムが伝播するのにかかる時間がわかります。処理が行われない静かな期間がないシステムでは、明示的なハートビートは必要ない場合があります。
バッチジョブ
オフライン処理とバッチジョブの間には曖昧な境界線があります。オフライン処理はバッチジョブで実行される場合があるからです。バッチジョブは、継続的に実行されないため、スキャンが困難になるという点で区別されます。
バッチジョブの主要なメトリックは、最後に成功した時刻です。各ジョブの主要なステージにかかった時間、全体的な実行時間、およびジョブが最後に完了した時刻(成功または失敗)を追跡することも役立ちます。これらはすべてゲージであり、PushGateway にプッシュする必要があります。通常、追跡するのに役立つ全体的なジョブ固有の統計(処理されたレコードの総数など)もあります。
数分以上かかるバッチジョブの場合、プルベースの監視を使用してスキャンすることも役立ちます。これにより、他のジョブと同様に、リソース使用量や他のシステムとの通信時のレイテンシなど、他のジョブと同様のメトリックを時間とともに追跡できます。これにより、ジョブの実行が遅くなり始めた場合のデバッグに役立ちます。
頻繁に(たとえば、15分ごとに)実行されるバッチジョブの場合は、デーモンに変換してオフライン処理ジョブとして扱うことを検討してください。
サブシステム
3つの主要なサービスタイプに加えて、システムには監視されるべきサブパートがあります。
ライブラリ
ライブラリは、ユーザーに追加の構成を必要とせずにインストルメンテーションを提供する必要があります。
プロセス外の(たとえば、ネットワーク、ディスク、またはIPC)リソースにアクセスするためのライブラリの場合は、少なくとも全体的なクエリ数、エラー(エラーが発生する可能性がある場合)、およびレイテンシを追跡してください。
ライブラリの負荷に応じて、ライブラリ自体の内部エラーとレイテンシ、および役立つと思われる一般的な統計を追跡してください。
ライブラリは、アプリケーションの複数の独立した部分によって異なるリソースに対して使用される可能性があるため、適切な場合はラベルを使用して用途を区別するように注意してください。たとえば、データベース接続プールは、通信しているデータベースを区別する必要がありますが、DNSクライアントライブラリのユーザーを区別する必要はありません。
ロギング
一般的なルールとして、ロギングコードの各行について、インクリメントされるカウンターも1つ必要です。興味深いログメッセージが見つかった場合は、それがどのくらいの頻度で発生しているか、そしてどのくらいの期間発生しているかを確認できるようにしたいはずです。
同じ関数内で複数の密接に関連するログメッセージ(たとえば、ifまたはswitchステートメントの異なるブランチ)がある場合、それらすべてに対して単一のカウンターをインクリメントすることが理にかなっている場合があります。
また、アプリケーション全体でログに記録された情報/エラー/警告行の合計数をエクスポートし、リリースプロセスの一部として顕著な違いを確認することも一般的に役立ちます。
失敗
失敗はロギングと同様に処理する必要があります。失敗が発生するたびに、カウンターをインクリメントする必要があります。ロギングとは異なり、コードの構造によっては、エラーがより一般的なエラーカウンターにバブルアップする可能性もあります。
失敗を報告する場合、通常は総試行回数を表す他のメトリックもいくつか必要です。これにより、失敗率を簡単に計算できます。
スレッドプール
あらゆる種類のスレッドプールについて、主要なメトリックは、キューに入れられたリクエスト数、使用中のスレッド数、総スレッド数、処理されたタスク数、およびそれらがどのくらいの時間かかったかです。キューで待機していた時間も追跡すると役立ちます。
キャッシュ
キャッシュの主要なメトリックは、合計クエリ数、ヒット数、全体的なレイテンシ、そしてキャッシュが前面にあるオンラインサービングシステムのクエリ数、エラー、レイテンシです。
コレクター
複雑なカスタムメトリックコレクターを実装する場合、収集にかかった時間(秒)のゲージと、発生したエラー数のゲージをエクスポートすることをお勧めします。
これは、ゲージとして期間をエクスポートするのが正しい2つのケースのうちの1つです。もう1つはバッチジョブの期間です。これらは両方とも、その特定のプッシュ/スキャンに関する情報ではなく、時間とともに複数の期間を追跡することに関連しています。
注意すべき点
監視を行う際には、一般的な注意点と、特にPrometheus固有の注意点があります。
ラベルの使用
ほとんどの監視システムにはラベルの概念とそれらを活用する式言語がないため、慣れるのに少し時間がかかります。
追加/平均/合計したい複数のメトリックがある場合は、通常、複数のメトリックではなくラベル付きの1つのメトリックにする必要があります。
たとえば、http_responses_500_total および http_responses_403_total の代わりに、code ラベルで HTTP 応答コードを示す単一のメトリック http_responses_total を作成します。これにより、ルールやグラフでメトリック全体をまとめて処理できます。
経験則として、メトリック名のどの部分も手続的に生成されるべきではありません(代わりにラベルを使用してください)。唯一の例外は、別の監視/インストルメンテーションシステムからメトリックをプロキシする場合です。
「命名」セクションも参照してください。
ラベルの乱用を避ける
各ラベルセットは、RAM、CPU、ディスク、およびネットワークのコストがかかる追加の時系列です。通常、オーバーヘッドは無視できるほど小さいですが、多数のメトリックと数百のラベルセットが数百のサーバーにわたるシナリオでは、これはすぐに積み重なる可能性があります。
一般的なガイドラインとして、メトリックのカーディナリティを10未満に保つようにしてください。それを超えるメトリックについては、システム全体で数個に限定することを目指してください。メトリックの大部分にはラベルがないはずです。
カーディナリティが100を超えるメトリック、またはそれほど大きくなる可能性がある場合は、次元の数を減らす、または分析を監視から汎用処理システムに移動するなど、代替ソリューションを調査してください。
根本的な数値をよりよく理解するために、node_exporterを見てみましょう。node_exporterは、マウントされた各ファイルシステムについてメトリックを公開します。各ノードには、たとえばnode_filesystem_availの数十の時系列があります。10,000ノードがある場合、node_filesystem_availの約100,000の時系列になります。これはPrometheusが処理するのに適しています。
ここで、ユーザーごとのクォータを追加すると、10,000ノードで10,000ユーザーの場合、すぐに数千万の2桁に達します。これは現在のPrometheusの実装では多すぎます。たとえ小さな数字であっても、このマシンで他の、より有用な可能性のあるメトリックをもう使用できなくなるため、機会費用が発生します。
不明な場合は、ラベルなしで開始し、具体的なユースケースが発生するにつれて、時間とともにラベルを追加してください。
カウンター vs ゲージ、サマリー vs ヒストグラム
与えられたメトリックに4つの主要なメトリックタイプのどれを使用すべきかを知ることは重要です。
カウンターとゲージのどちらかを選択するための簡単な経験則があります。値が下がる可能性がある場合は、ゲージです。
カウンターは上昇することしかできません(そして、プロセスが再起動したときなど、リセットされます)。イベントの数、または各イベントでの量(たとえば、HTTPリクエストの総数、またはHTTPリクエストで送信されたバイトの総数)を累計するのに役立ちます。生のカウンターはまれにしか役立ちません。rate()関数を使用して、増加率(秒あたり)を取得します。
ゲージは設定でき、上下します。進行中のリクエスト、空き/合計メモリ、または温度などの状態のスナップショットに役立ちます。ゲージのrate()を絶対に使用しないでください。
サマリーとヒストグラムは、独自のセクションで説明されているより複雑なメトリックタイプです。
タイムスタンプ、経過時間ではない
何かが起こってからの経過時間を追跡したい場合は、それが起こったUnixタイムスタンプをエクスポートしてください。経過時間ではありません。
エクスポートされたタイムスタンプがあれば、式time() - my_timestamp_metricを使用してイベントからの経過時間を計算できます。これにより、更新ロジックの必要性がなくなり、更新ロジックがスタックするのを防ぐことができます。
内部ループ
一般に、インストルメンテーションの追加リソースコストは、運用と開発にもたらすメリットによって大きく上回られます。
パフォーマンスが重要なコード、または特定のプロセス内で毎秒100,000回以上呼び出されるコードについては、更新するメトリックの数に注意を払うことを検討してください。
Javaカウンターは、競合によって12-17nsかかります。他の言語でも同様のパフォーマンスが得られます。その時間が内部ループにとって重要である場合は、内部ループで更新するメトリックの数を制限し、ラベルの使用を避ける(またはラベルルックアップの結果、たとえばGoのWith()やJavaのlabels()の戻り値をキャッシュする)ことを検討してください。
また、時間や期間を伴うメトリック更新にも注意してください。時間の取得にはシステムコールが必要になる場合があります。パフォーマンスが重要なコードに関連するすべての問題と同様に、ベンチマークは、任意の変更の影響を判断するための最良の方法です。
欠落メトリックの回避
何かが発生するまで存在しない時系列は、通常の単純な操作だけでは正しく処理できないため、扱いにくいです。これを避けるために、事前に存在する可能性のある時系列については、0のようなデフォルト値をエクスポートしてください。
ほとんどのPrometheusクライアントライブラリ(Go、Java、Pythonなど)は、ラベルのないメトリックに対して0を自動的にエクスポートします。