CodeGym /コース /C# SELF /パフォーマンス監視とメトリクス収集

パフォーマンス監視とメトリクス収集

C# SELF
レベル 64 , レッスン 3
使用可能

1. はじめに

あなたがサイトの管理者で、毎日1000人のユーザーが来ると想像してみて。ログを見ると動いてて、ほとんどエラーはない(たまに誰かがパスワードを忘れたり、キャプチャでミスするくらい)。「問題ないな!」って思うよね。

ところがサポートに「サイトがめっちゃ遅い、ページが10秒かかる」と書かれている。ログを見るとエラーはない! 良かった? いいえ。ログは何が起きたか(または起きなかったか)を教えてくれるけど、どれくらい速く/遅く起きたか、そのためにどれだけリソースが必要だったか、負荷が増えたときにどう変化したかは教えてくれない。

そこで登場するのがメトリクス — アプリの動作を測る指標だ。エラーの数だけじゃなく、平均応答時間、使われているメモリ量、1秒あたりのリクエスト数など、システムの健全性を判断するための値だよ。

比較:
ログ — 「何が起きたか」
メトリクス — 「システムがどれだけ良く/悪く動いているか」
トレース — 「なぜそのように動いているか(詳細)」

どんなメトリクスがあって、何を集めるべきか?

主なメトリクスの種類:

メトリクスのタイプ 用途
カウンタ(Counters) リクエスト数、エラー数、障害数 トレンド、アラート、負荷
ヒストグラム 応答時間、パケットサイズ 値の分布、パーセンタイル
ゲージ(Gauge) メモリ使用量、CPU リソースの現在の状態
サマリ(Sum) 総データ量、バイト数 期間内の総操作量

例:

  • 平均と95パーセンタイルのGET応答時間。
  • 現在オンラインのユーザー数。
  • メモリ使用量(Private Bytes, Working Set)。
  • エラー発生頻度、例:500/503
  • 1分あたりのDBクエリ数。

これらの指標は問題を見つけるだけでなく、予防にも役立つ — サーバの負荷増大や応答時間の緩やかな増加は将来の障害の前兆だからね。

2. .NET と OpenTelemetry エコシステムでのメトリクス収集の仕組み

全体アーキテクチャ

現代の .NET(.NET 6 以降、特に .NET 8/9)には、OpenTelemetry に基づく標準的なメトリクス収集の仕組みがあるよ。

仕組みはこんな感じ:

  1. アプリのコードがカウンタを増やしたり、ゲージを登録したり、ヒストグラムに記録したりする。
  2. OpenTelemetry Metrics SDKがこれらのメトリクスを(メモリ内で)収集し、定期的に送信する。
  3. エクスポーターが選んだ監視システム(Prometheus, Application Insights, Grafana Cloud, Datadog など)へ送る。
  4. 監視バックエンドが集計、保存、可視化、アラート、ダッシュボードを提供する。
概略ブロック図:

[あなたのアプリケーション] 
       ⬇ 
 [メトリクス収集 (OpenTelemetry SDK)]
       ⬇
 [メトリクスエクスポーター (Prometheus, AI, Datadog, ...)]
       ⬇
 [監視システム/ダッシュボード/アラート]

3. 実践:C#でのメトリクスの基本

シンプルな内蔵メトリクス: System.Diagnostics.Metrics

.NET は組み込みのメトリクス機構を提供している — System.Diagnostics.Metrics

主なプレイヤー: Meter, Counter<T>, Histogram<T>, ObservableGauge<T>

例: ページ訪問のカウンタ


// Meter を作る(通常アプリ全体で1つ)
using System.Diagnostics.Metrics;

static Meter meter = new Meter("MyCompany.MyApp", "1.0");

// カウンタを登録
static Counter<long> HomePageVisits = meter.CreateCounter<long>("HomePageVisits");

// コントローラやサービスのどこかで...
public void HomePageRequested()
{
    HomePageVisits.Add(1);
    // ここでページ処理の残り
}

コメント:

  • Meter はメトリクスの「ファクトリ」で、ユニークな名前(アプリ/会社の名前空間)を持つ。
  • CreateCounter<long> はカウンタを作る。増分は Add(1) で行う。

例: 応答時間の計測


static Histogram<double> PageLoadTimeMs = meter.CreateHistogram<double>("PageLoadTimeMs");

// リクエストハンドラ内で:
public void OnRequest()
{
    var stopwatch = System.Diagnostics.Stopwatch.StartNew();

    // ...ここでリクエスト処理...

    stopwatch.Stop();
    PageLoadTimeMs.Record(stopwatch.Elapsed.TotalMilliseconds);
}

動的値のためのゲージ収集

ゲージは時間とともに変化する指標:接続中のユーザー数、現在のメモリ量など。


static ObservableGauge<int> OnlineUsers = meter.CreateObservableGauge(
    "OnlineUsers", 
    () => GetOnlineUserCount());

// GetOnlineUserCount は現在値を返すメソッド
static int GetOnlineUserCount()
{
    // 実際のロジックをここに!
    return ActiveUserList.Count;
}

実世界ではアプリがメトリクスの値を提供し、エクスポーターがそれを外へ出す(例えば Prometheus が "/metrics" をスクレイプする)形で動く。

モダンな ASP.NET Core アプリへのメトリクス追加

ASP.NET Core は多くが箱から使える。パッケージ OpenTelemetry.Instrumentation.AspNetCore を追加すれば、HTTPメトリクス、応答時間、エラー数などが自動で出る。

例: Program.cs の設定:


using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenTelemetry()
    .WithMetrics(metrics =>
    {
        metrics
            .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("MyApp"))
            .AddAspNetCoreInstrumentation() // HTTPメトリクス
            .AddRuntimeInstrumentation()    // .NET CLR 実行時メトリクス
            .AddProcessInstrumentation()    // プロセスのCPU/メモリ
            .AddMeter("MyCompany.MyApp")    // 自分のメトリクス
            .AddPrometheusExporter();       // Prometheusへエクスポート
    });

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

これでアプリは /metrics でメトリクスを公開し、Prometheus などからスクレイプ可能になる。

4. メトリクスの実践的利用例

実プロジェクトでのパフォーマンス監視

メトリクスをつなげて次を把握できるようにする:

  • API が耐えられる平均・ピークの RPS(requests per second)はどれくらいか?
  • どこがボトルネックか:あるエンドポイントは 300 ms、別のは 2000 ms なのか?
  • DB 呼び出しにどれくらい時間がかかっているか(独自のヒストグラムを追加)

例: DB への応答時間を監視


static Histogram<double> DbQueryDurationMs = meter.CreateHistogram<double>("DbQueryDurationMs");

public async Task<List<Product>> GetProductsAsync()
{
    var sw = Stopwatch.StartNew();
    var result = await _db.Products.ToListAsync();
    sw.Stop();
    DbQueryDurationMs.Record(sw.Elapsed.TotalMilliseconds);
    return result;
}

例: エラーを数える


static Counter<long> ApiErrors = meter.CreateCounter<long>("ApiErrors");

public IActionResult SomeEndpoint()
{
    try
    {
        // 何らかの処理
        return Ok();
    }
    catch (Exception)
    {
        ApiErrors.Add(1);
        throw;
    }
}

メトリクスのラベル(タグ)の扱い

データを有用な特徴でグルーピングすることが重要: エンドポイント、エラー種別、ユーザー種別など。


HomePageVisits.Add(
    1, 
    KeyValuePair.Create<string, object>("UserType", "Admin"));

ヒストグラムの場合:


DbQueryDurationMs.Record(
    sw.Elapsed.TotalMilliseconds, 
    KeyValuePair.Create<string, object>("QueryType", "GetProducts"));

タグがあれば Grafana でアプリ全体だけでなく特定セグメントのグラフも作れる。

5. Prometheus, Application Insights, Datadog, Grafana との統合

エクスポーターと統合

  • Prometheus — 人気のオープンソース監視、クラウドや Kubernetes のデファクト標準。
  • Application Insights — Azure 向けのクラウド統合。
  • Datadog, Grafana Cloud — プロ向けインフラ用。

これらはすべて .NET から OpenTelemetry のエクスポーター経由でメトリクスを集められる。OTel エクスポーターのドキュメント

Prometheus(手順):

  1. NuGet パッケージを追加:OpenTelemetry.Exporter.Prometheus
  2. .AddPrometheusExporter() をメトリクス登録に追加する。
  3. Grafana でデータソースを Prometheus に設定し、ダッシュボードを作る。

参考リンク:

6. 注意点、落とし穴、よくあるミス

よくあるミスの一つは タグの過剰な粒度。タグに一意の値(例えばユーザーIDや注文ID)をたくさん入れると時系列の数が爆発して、メトリクスストレージが圧迫されコストが跳ね上がる(いわゆる cardinality explosion)。タグは粗めに保とう。

開発者はしばしば System.Diagnostics.Metrics や既存ツールを無視して、ログとタイマーで独自実装してしまう。結果として監視の統合性が悪く、保守が大変になる。標準ツールと自動計測を使おう。

もう一つのミスは メトリクスは集まっているがエクスポートされていない。エクスポーターの設定は必須:例えば .AddPrometheusExporter() を追加し、/metrics エンドポイントがスクレイプ可能になっているか確認して。

最後に、メトリクスの種類の混同:平均応答時間を Counter で取ってしまうとピークや分布が見えない。カウンタは数を数えるため、ヒストグラムは時間やサイズなど分布を取るため、ゲージは現在値のために使う、という基本を守って。

コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION