New Relicを使って外部サービスのエンドポイント別のレスポンスタイムを可視化した。
IDのようなパスパラメータを含むエンドポイントがあるときはFACET CASE句とcapture関数を使うとよい。
TL;DR
- New Relicで外部APIのレスポンスタイムを計測するときは
FROM Span WHERE category = 'http'という条件でNRQLを書く。 - 単純な
FACET句だと/users/${USER_ID}のようなパスパラメータを含むエンドポイントの計測を集約できない。 - パスパラメータを含むエンドポイントは
FACET CASE句を使って結果をまとめる - その他のエンドポイントはcapture関数を使って結果を分類する。
次のサンプルNRQLはhttps://example.com/external_apiという外部サービスのエンドポイント別レスポンスタイムの中央値を時系列データとして可視化するグラフ。
SELECT percentile(duration, 50)
FROM Span
WHERE category = 'http' AND appName = 'my-application'
AND http.url LIKE 'https://example.com/external_api%'
FACET http.method, CASES(
-- https://example.com/external_api/users/${USER_ID}/icon を想定
WHERE http.url LIKE '%icon' AS '/users/${USER_ID}/icon',
-- https://example.com/external_api/users/${USER_ID} を想定
WHERE http.url LIKE '%users/%' AS '/users/${USER_ID}'
-- その他のパスパラメータを含まないエンドポイント
) OR capture(http.url, r'https://example.com/external_api(?P<path>.*)')
SINCE 10 day ago TIMESERIES AUTO
HTTPメソッドとURLを使ってたとえば次のようなプロットで時系列データをグラフにできる。
PUT /users/${USER_ID}GET /users/${USER_ID}GET /users/${USER_ID}/iconGET /authGET /health
New Relic Oneで外部APIのレスポンスタイムを計測したい
New Relic Oneを導入し、分散トレースも確認できる状態のアプリケーションがある。
このアプリケーションが依存している外部APIのレスポンスタイムをエンドポイント別に確認したかった。
本記事では外部APIは以下のようなエンドポイントを持っている前提で記載する。
- GET
https://example.com/external_api/users/${USER_ID}
- 指定されたIDのユーザー情報を返すエンドポイント
- GET
https://example.com/external_api/users/${USER_ID}/icon
- 指定されたIDのユーザーアイコン画像を返すエンドポイント
- その他パスパラメータを含まないエンドポイント
IDによってレスポンスタイムに大きな違いはないので、「指定されたIDのユーザー情報を返すエンドポイント」としてのレスポンスタイムの集計データを確認したかった。
MetricではなくSpanを使う
New Relic One上で書くサービスのサマリーページを見ると「Web transactions time」というMetricを使ったグラフがあり「Web external」の情報も確認できる。

(参考画像はNew Relic Explorers HubのTopicから拝借)
ここを見るとこのグラフの元データとなるMetricからやりたいことができそうだが、Metricはプロパティがわかりづらく、NRQLが試行錯誤できなかった。
そこで今回はトランザクショントレースのSpanからグラフを作成する。
とうぜんアプリケーションでAPMが取得できていて外部Webサービスへのリクエストのメトリクスも取得できている必要がある。
Goアプリケーションで外部Webサービスへのリクエストのメトリクスを取得する方法
Goで該当メトリクスを取得するにはNew Relic Go Agentのnewrelic.NewRoundTripperを使う。
import (
"net/http"
"time"
"github.com/newrelic/go-agent/v3/newrelic"
)
type roundTripperFunc func(*http.Request) (*http.Response, error)
func (f roundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) {
return f(r)
}
func HttpClient() *http.Client {
cli := &http.Client{
Timeout: 5 * time.Second,
}
cli.Transport = func(rt http.RoundTripper) http.RoundTripper {
return roundTripperFunc(func(req *http.Request) (*http.Response, error) {
nrt := newrelic.NewRoundTripper(rt)
nreq := newrelic.RequestWithTransactionContext(req, newrelic.FromContext(req.Context()))
return nrt.RoundTrip(nreq)
})
}(cli.Transport)
return cli
}
このように作ったhttp.Clientから外部サービスへリクエストを送信すると、NRQLでFROM Span WHERE category = 'http'という条件で外部サービスとの通信結果のメトリクスが取得できる。
単純なFACET句だと/users/${USER_ID}のようなパスパラメータを含むエンドポイントの計測を集約できない。
このメトリクスを使ってエンドポイント別のグラフを作る。単純に考えるとNRQL上のGROUP BYであるFACET句を使うことになるだろう。
NRQLは次のようになる。
SELECT percentile(duration, 50)
FROM Span WHERE category = 'http' AND appName = 'my-application'
AND http.url LIKE 'https://example.com/external_api%'
-- HTTPメソッドとURL別にレスポンスタイムを集約する。
FACET http.method, http.url
SINCE 10 day ago TIMESERIES AUTO
しかし、これでは集計に失敗する。
FACET句だけだと、次のようにパスパラメータとしてIDなどが含まれるエンドポイントがID別に集計されてしまう。
PUT https://example.com/external_api/users/123GET https://example.com/external_api/users/123GET https://example.com/external_api/users/567GET https://example.com/external_api/users/333/iconGET https://example.com/external_api/authGET https://example.com/external_api/health
これではIDの数だけグラフにプロットされてしまうので意味があるグラフではなくなる。
パスパラメータを含むエンドポイントはFACET CASE句を使って結果をまとめる
このようなときはFACET CASE句を使うとパスパラメータを集約できる。
パスパラメータを含むエンドポイントはFACET CASE句の中でWHERE LIKEを使って集約する。
順番にパターンマッチングされているはずなので、一度WHERE条件にかかったデータは後続のWHEREにはマッチしない。
パスパラメータを含むエンドポイントはFACET CASE句にORでつなげるとよい。
SELECT percentile(duration, 50)
FROM Span
WHERE category = 'http' AND appName = 'my-application'
AND http.url LIKE 'https://example.com/external_api%'
FACET http.method, CASES(
-- https://example.com/external_api/users/${USER_ID}/icon を想定
WHERE http.url LIKE '%icon' AS '/users/${USER_ID}/icon',
-- https://example.com/external_api/users/${USER_ID} を想定
WHERE http.url LIKE '%users/%' AS '/users/${USER_ID}'
-- その他のパスパラメータを含まないエンドポイント
) OR http.url
SINCE 10 day ago TIMESERIES AUTO
こうすると次のような集約結果になる。
PUT /users/${USER_ID}GET /users/${USER_ID}GET /users/${USER_ID}/iconGET https://example.com/external_api/authGET https://example.com/external_api/health
その他のエンドポイントはcapture関数を使って表示をスッキリする。
capture関数を使うとグラフ表示時のラベルをスッキリさせることができる。
正規表現はGoなどと同じRe2形式が使える。
capture(http.url, r'https://example.com/external_api(?P<path>.*)')
capture関数も使うとNRQLは次のようになる。
SELECT percentile(duration, 50)
FROM Span
WHERE category = 'http' AND appName = 'my-application'
AND http.url LIKE 'https://example.com/external_api%'
FACET http.method, CASES(
-- https://example.com/external_api/users/${USER_ID}/icon を想定
WHERE http.url LIKE '%icon' AS '/users/${USER_ID}/icon',
-- https://example.com/external_api/users/${USER_ID} を想定
WHERE http.url LIKE '%users/%' AS '/users/${USER_ID}'
-- その他のパスパラメータを含まないエンドポイント
) OR capture(http.url, r'https://example.com/external_api(?P<path>.*)')
SINCE 10 day ago TIMESERIES AUTO
こうすると次のようなグラフのプロットが完成する。
PUT /users/${USER_ID}GET /users/${USER_ID}GET /users/${USER_ID}/iconGET /authGET /health
終わりに
New Relic実践入門という書籍も所持しているのだが、今回のやりかたは載っていないような気がしたので記事にした。
外部サービスのレスポンスタイムのレスポンスタイムを可視化することができたので、「なんとなく遅くなってません?」などと定性的な話ではなく、定量的に外部連携先と調整をしたり、
レスポンスタイムの悪化の原因部分をより詳細に把握できるようになった。
ちなみにAPMを導入しているサービス自体のエンドポイント別のデータを集計したいときはパスパラメータが入っていてもNRQLが提供するプレースホルダーのような機能で簡単に集約できる。
参考
- New Relic Go agent v3
- NRQL syntax, clauses, and functions
- Query APM metric timeslice data with NRQL