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}/icon
GET /auth
GET /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/123
GET https://example.com/external_api/users/123
GET https://example.com/external_api/users/567
GET https://example.com/external_api/users/333/icon
GET https://example.com/external_api/auth
GET 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}/icon
GET https://example.com/external_api/auth
GET 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}/icon
GET /auth
GET /health
終わりに
New Relic実践入門という書籍も所持しているのだが、今回のやりかたは載っていないような気がしたので記事にした。
外部サービスのレスポンスタイムのレスポンスタイムを可視化することができたので、「なんとなく遅くなってません?」などと定性的な話ではなく、定量的に外部連携先と調整をしたり、
レスポンスタイムの悪化の原因部分をより詳細に把握できるようになった。
ちなみにAPMを導入しているサービス自体のエンドポイント別のデータを集計したいときはパスパラメータが入っていてもNRQLが提供するプレースホルダーのような機能で簡単に集約できる。
参考
- New Relic Go agent v3
- NRQL syntax, clauses, and functions
- Query APM metric timeslice data with NRQL