Java

GraphQLとPrometheusの違いとメトリクス監視の実装|Micrometer・Grafana連携

「GraphQL vs Prometheus」で検索する人が最初に知りたいのは、たいてい「この2つはどちらを選ぶ技術なのか」でしょう。結論から言うと、両者は競合しません。GraphQLはクライアントが必要なデータを1リクエストで取得するためのAPIクエリ言語で、Prometheusはサーバーの稼働状況を数値の時系列として集めて監視する仕組みです。層が違うので比較して選ぶものではなく、実運用では「GraphQLサーバーのメトリクスをPrometheusで監視する」という形で組み合わせて使います。この記事では、両者の違いを整理したうえで、Spring for GraphQL・Netflix DGS・GraphQL JavaのそれぞれでオペレーションメトリクスをMicrometerで収集し、Prometheusに送ってGrafanaで可視化・アラートするまでを、コードと実装上の落とし穴つきで解説します。

まとめ:GraphQL監視は「オペレーション単位のメトリクスをMicrometer経由でPrometheusに出す」

  • GraphQLとPrometheusは競合技術ではない。GraphQL=API層のクエリ言語、Prometheus=監視層の時系列DB。組み合わせて使う。
  • 収集の基盤はMicrometer。Spring Boot Actuatorが /actuator/prometheus を公開し、Prometheusが定期的にpullして保存、Grafanaが可視化する。この流れは1本で、フレームワークが変わっても変わらない。
  • GraphQL固有の計測はフレームワークで方法が分かれる。Spring for GraphQLは graphql.requestgraphql.datafetcher を自動生成、Netflix DGSは gql.querygql.error を提供、graphql-javaは自前のInstrumentationで実装する。
  • Spring Boot 3.3以降は micrometer-registry-prometheus がPrometheus Javaクライアント1.xに切り替わり、エクスポートされるメトリクス名が変わる。ダッシュボード移行時の第一の落とし穴。
  • 設計で効くのは2点だけ。クエリIDのような高カーディナリティのラベルを付けないこと、レイテンシは平均でなくヒストグラムのp95/p99で見ること。

GraphQLとPrometheusは競合しない:役割とレイヤーの違い

この2語が並んで検索される背景には、どちらも「クエリ言語を持つ」という共通点による混同があります。GraphQLのクエリ言語はアプリケーションのデータを取りに行くためのもの、PrometheusのPromQLは集めた監視データを集計するためのものです。名前は似ていても対象がまったく違います。

観点 GraphQL Prometheus
役割 APIのデータ取得 メトリクス収集・監視
レイヤー アプリケーション層 可観測性(監視)層
クエリ言語 GraphQL(スキーマ駆動) PromQL(時系列集計)
扱うデータ 要求した任意構造のレスポンス カウンタ・ゲージ・ヒストグラム
使う場面 画面に必要なデータを1回で取得 稼働状況の定期収集とアラート

したがって現場での問いは「どちらを使うか」ではなく「GraphQLサーバーの状態をどう監視するか」になります。GraphQL自体の設計思想を先に押さえたい場合はREST APIとGraphQLの違いと使い分け|選定基準と運用コストを参照してください。

メトリクス収集パイプラインの全体像(Micrometer→Actuator→Prometheus→Grafana)

JVM上のGraphQLサーバーでは、収集の中心にMicrometerを置きます。Micrometerはメトリクスのファサードで、アプリのコードは特定の監視製品に依存せずカウンタやタイマーを記録できます。記録されたメトリクスはSpring Boot Actuatorが /actuator/prometheus エンドポイントにPrometheus形式で出力し、Prometheusサーバーが設定した間隔でそのエンドポイントをpull(スクレイプ)して時系列データベースに保存します。Grafanaはそのデータをクエリしてダッシュボードやアラートに使います。旧来この説明はフレームワークごとに繰り返されがちですが、パイプライン自体はどのフレームワークでも同一です。

依存ライブラリとActuatorの設定

Spring Bootプロジェクトなら、GraphQLスターター・Actuator・Prometheusレジストリの3つを入れます。

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-graphql'
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    runtimeOnly 'io.micrometer:micrometer-registry-prometheus'
}

次に、Prometheusエンドポイントを公開します。Actuatorは既定で大半のエンドポイントを閉じているため、明示的に露出する必要があります。

management.endpoints.web.exposure.include=health,prometheus
management.metrics.tags.application=graphql-api
management.metrics.distribution.percentiles-histogram.graphql.request=true

3行目はレイテンシをヒストグラムとして出す指定です。これを入れておくと、後述するp95/p99の算出がPrometheus側でできるようになります。

Spring Boot 3.3以降のmicrometer-registry-prometheus移行に注意

ここが更新時にはまりやすい点です。Spring Boot 3.3(Micrometer 1.13)で、micrometer-registry-prometheus の中身がPrometheus Javaクライアント1.xに置き換わりました。基底パッケージが io.micrometer.prometheus から io.micrometer.prometheusmetrics へ変わり、エクスポートされるメトリクス名にも破壊的変更が入ります。既存のGrafanaクエリやアラートがそのまま動かなくなることがあるため、バージョンを上げたら出力名を確認してください。旧0.x系の挙動(PushGateway利用など)を当面維持したい場合は、micrometer-registry-prometheus を外して micrometer-registry-prometheus-simpleclient に差し替えます。ただしsimpleclientは非推奨扱いで将来削除予定のため、移行までの暫定手段と位置づけるべきです。

GraphQLオペレーション単位でメトリクスを取る3つの実装

JVMやHTTPのメトリクスはActuatorが自動で出しますが、「どのクエリが遅いか」「どのミューテーションがエラーを出したか」というGraphQL固有の粒度は、使っているフレームワークによって取り方が変わります。代表的な3つを、それぞれの計測単位ごとに示します。

Spring for GraphQL:graphql.request / graphql.datafetcher を自動計測

Spring Boot 3.0以降のSpring for GraphQLは、Micrometer Observationによる計測が自動構成されます。追加コードなしで2種類のObservationが作られます。リクエスト全体を表す graphql.request と、データ取得の graphql.datafetcher です。重要なのは、datafetcher側は「非trivialなデータ取得」だけを対象にする点です。Javaオブジェクトのプロパティを返すだけの単純なフィールド解決は除外されるため、フィールドごとにメトリクスが無制限に増える事故を最初から避ける設計になっています。内部的には GraphQlObservationInstrumentation が計測を担います。ヒストグラムを有効化しておけば、graphql.request のp95がそのままGraphQL全体の応答時間の指標になります。Spring for GraphQLそのものの基本構成をあわせて押さえたい場合はSpring for GraphQLとは?基本概念と可観測性の重要性を参照してください。

Netflix DGS:gql.query / gql.error を専用モジュールで取得

Netflix DGS Frameworkでは、計測はオプトインの graphql-dgs-spring-boot-micrometer モジュールで提供されます。これを追加すると、クエリ実行時間の gql.query、リゾルバ単位の gql.resolver、エラー件数の gql.error といったDGS固有のメトリクスが記録されます。バージョンはDGSのBOM(platform)で解決する前提のため、依存には版番号を明示していません。

dependencies {
    implementation 'com.netflix.graphql.dgs:graphql-dgs-spring-boot-micrometer'
}
management.metrics.dgs-graphql.enabled=true
management.metrics.dgs-graphql.query-signature.enabled=true
management.metrics.dgs-graphql.tags.limiter.limit=100

タグには gql.operation.name(オペレーション名)や、クエリ本文を正規化してSHA-256でハッシュ化した gql.query.sig.hash が付きます。後者はクエリごとの傾向を追える一方でカーディナリティが上がりやすいため、tags.limiter.limit(既定100)でタグ値の上限を絞れるようになっています。この上限設定はDGSがカーディナリティ問題を前提に用意している機能で、無効にして運用するのは避けたほうが安全です。

graphql-java:Instrumentationで自前計測する

Spring for GraphQLでもDGSでもなく素のgraphql-javaを使う場合は、Instrumentation を実装して計測点を差し込みます。実行の開始でタイマーを開始し、完了時に成功/失敗のタグ付きで停止する、という最小構成です。

public class MetricsInstrumentation extends SimplePerformantInstrumentation {
    private final MeterRegistry registry;

    public MetricsInstrumentation(MeterRegistry registry) {
        this.registry = registry;
    }

    @Override
    public InstrumentationContext<ExecutionResult> beginExecution(
            InstrumentationExecutionParameters params, InstrumentationState state) {
        Timer.Sample sample = Timer.start(registry);
        return SimpleInstrumentationContext.whenCompleted((result, throwable) ->
            sample.stop(registry.timer("graphql.operation",
                "operation", params.getOperation() == null ? "unknown" : params.getOperation(),
                "outcome", throwable == null && result.getErrors().isEmpty() ? "success" : "error")));
    }
}

タグの operation にはオペレーション名を使い、クエリ文字列そのものやユーザーIDのような一意の値は入れません。ここを誤ると、次章のカーディナリティ問題を自分で作り込むことになります。

PrometheusとGrafanaでの可視化とアラート

Prometheus側は、スクレイプ対象にActuatorのエンドポイントを登録するだけです。

scrape_configs:
  - job_name: 'graphql-api'
    metrics_path: '/actuator/prometheus'
    scrape_interval: 15s
    static_configs:
      - targets: ['localhost:8080']

収集後はPromQLで指標を作ります。ヒストグラムを有効にしていれば、GraphQLリクエストのp95レイテンシは histogram_quantile で算出できます。DGSならエラー率はエラー件数をクエリ件数で割って求めます。なお以下の名称はMicrometer 1.13/Prometheusクライアント1.x系での例で、旧系ではメトリクス名の末尾などが異なる場合があります。

# Spring for GraphQL のp95レイテンシ(秒)
histogram_quantile(0.95, sum by (le) (rate(graphql_request_seconds_bucket[5m])))

# DGS のエラー率
sum(rate(gql_error_total[5m])) / sum(rate(gql_query_seconds_count[5m]))

GrafanaではデータソースとしてこのPrometheusを追加し、上記のクエリでパネルを組みます。しきい値超過の通知は、Grafanaのアラートか、Prometheus側のAlertmanagerで設定します。メトリクスに加えてログも同じ画面で相関させたい場合はGrafana Lokiとは?Prometheus連携・LogQL・導入方法をわかりやすく解説が参考になります。別フレームワークでの同じパイプラインの具体例としてはLaravelでPrometheusとGrafana監視を実装:メトリクス収集・可視化・アラート設定も合わせて確認できます。

メトリクス設計の落とし穴と対策

GraphQL監視で失敗するパターンはほぼ2つに集約されます。どちらも「多く取れば良い」という発想が原因です。

クエリ単位のIDをラベルに入れると破綻する。 Prometheusはラベルの組み合わせごとに別の時系列を作ります。GraphQLはリクエストごとにクエリ本文が変わるため、クエリ文字列やリクエストIDをそのままラベルにすると時系列が爆発し、Prometheusのメモリとストレージを食い潰します。ラベルはオペレーション名やエンドポイントなど、値の種類が有限のものに限定します。Spring for GraphQLがtrivialなdatafetcherを計測対象から外し、DGSがタグ上限を用意しているのは、いずれもこの問題への標準的な防御です。可変値をラベルにするくらいなら、そのメトリクスは取らないほうがよいと考えて設計します。

レイテンシを平均値で見ると遅いリクエストが隠れる。 平均応答時間は、一部の極端に遅いリクエストの影響を薄めてしまいます。監視で見るべきは分布の裾なので、ヒストグラムを有効化してp95やp99を指標にします。前掲の percentiles-histogram 設定を入れておけば、Prometheus側で histogram_quantile によりパーセンタイルを後から自由に算出できます。パフォーマンスチューニングの起点も、平均ではなくp99が悪化しているオペレーションを特定することから始めます。

なお、DGSではバリデーションエラーなど一部のエラーが gql.error のタグ上「unknown」に丸められる既知の挙動があります。エラー内訳を厳密に追う場合は、タグ値の中身をダッシュボードで確認してから運用に乗せてください。メトリクスだけでなくトレースやログも含めた設計を検討する段階ではOpenTelemetry(OTel)とは|3つのシグナル・OTLP・Collector・Prometheusとの違いを解説が判断材料になります。

よくある質問

GraphQLとPrometheusはどちらを使うべきですか?

どちらか一方を選ぶ関係ではありません。GraphQLはAPIのデータ取得、Prometheusはその稼働状況の監視を担い、レイヤーが異なります。実際にはGraphQLサーバーのメトリクスをPrometheusで監視する、という形で併用します。

Spring for GraphQLで自動的に取れるメトリクスは何ですか?

Spring Boot 3.0以降なら、リクエスト全体の graphql.request と、非trivialなデータ取得の graphql.datafetcher の2種類がObservationとして自動生成されます。単純なプロパティ取得は計測対象外です。

/actuator/prometheus にアクセスしてもメトリクスが出ません。

Actuatorが既定でエンドポイントを閉じているためです。management.endpoints.web.exposure.includeprometheus を追加し、micrometer-registry-prometheus が依存関係に入っているかを確認してください。

Spring Bootを3.3に上げたらGrafanaのメトリクス名が変わりました。

Micrometer 1.13でPrometheus Javaクライアントが1.xに更新され、エクスポート名に破壊的変更が入ったためです。ダッシュボードのクエリを新しい名前に合わせるか、暫定的に micrometer-registry-prometheus-simpleclient へ差し替えて旧挙動を維持します。

GraphQLメトリクスでカーディナリティの爆発を防ぐには?

クエリ本文やリクエストID、ユーザーIDのような一意の値をラベルにしないことです。ラベルはオペレーション名やエンドポイント単位に集約します。DGSなら management.metrics.dgs-graphql.tags.limiter.limit でタグ値の上限を設定できます。

関連記事

資料請求

RELATED POSTS 関連記事