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