この記事は New Relic Advent Calendar 2024 と NewsPicks Advent Calendar 2024 の7日目の記事です。
ソーシャル経済メディア「NewsPicks」SREチームの飯野です。
NewsPicksではサービスの状態を可視化するために New Relic APM(Application Performance Monitoring)を導入しています。ことあるごとにNew Relicのダッシュボードを確認することでサービスの状態の把握や、安定性向上、パフォーマンス改善のヒントを得ています。
今回はNew RelicのAPMでは用意されていないキャッシュヒット率の可視化について話したいと思います。
- キャッシュは重要
- Spring Cache instrumentation
- メトリクスの内容を確認する
- 個別の <cache-name> のキャッシュヒット率を求める
- <cache-name> のキャッシュヒット率を求める
- まとめ
キャッシュは重要
NewsPicksは参照系のアクセスが非常に多いため、キャッシュを駆使したDBへの問い合わせ削減にとても気をつかっています。特にプッシュ通知後のアクセス急増時には、キャッシュの活用状況が直接ユーザー体験に影響を与えることがあります。
キャッシュが適切に機能している場合、DBへの問い合わせが最小になり、高速にアクセスを処理できます。
キャッシュが適切に機能していない場合(これはビジネス要件の変化やABテストなどで最適化されていないAPIがアプリから広く使われるようになった時などに起こります)、アクセスの処理が追いつかず、ユーザー体験が悪化につながります。例えば、スマホの通知をタップしてもアプリがなかなか起動しない、記事が開けない、エラーが発生してしまうなどの問題が発生します。
このように重要なキャッシュですが、New Relic APMには現時点ではキャッシュヒット率のための画面は用意されていません。
代替手段として、Transactionの詳細 > Breakdown table > Avg Calls/Txn を確認し、この数値の増減で大まかな利用状況を確認していました。
Spring Cache instrumentation
定量的な指標に基づく改善ができず困っていたところ、New Relic Java Agent 8.6.0からSpring-Cache Instrumentationがサポートされ、キャッシュヒット率を計測できるメトリクスが収集可能になっていることを発見しました。
Spring Cache instrumentation #1458
This new instrumentation module allows you to see how your caches are performing. It provides hit/miss metrics as well as clear and evict.
NewsPicksではSpring Frameworkを採用しており、Spring Cacheを使用してDBへの問い合わせ結果をキャッシュしています。この新しいメトリクスを活用してキャッシュヒット率が可視化できそうです。
メトリクスの内容を確認する
New Relic Java Agent 8.6.0のリリースノートによると、Spring Cache instrumentationでは以下の形式でメトリクスが送信されるようです。
Cache/Spring/<cache-provider>/<cache-name>/hits Cache/Spring/<cache-provider>/<cache-name>/misses Cache/Spring/<cache-provider>/<cache-name>/clear Cache/Spring/<cache-provider>/<cache-name>/evict
送信される4種類のメトリクス(hits
、misses
、clear
、evict
)のうち、hits
とmisses
を使用してキャッシュヒット率を計算できます。
個別の <cache-name>
のキャッシュヒット率を求める
次の二つのメトリクス*1を例に個別の <cache-name>
のキャッシュヒット率を求めてみます。
Cache/Spring/java.util.ConcurrentHashMap/my_cache/hits Cache/Spring/java.util.ConcurrentHashMap/my_cache/misses
キャッシュヒット率を求める準備として、まずは単位時間あたりの合計を求めるNRQLを作成します。次のように段階的に絞り込んだ結果をcountします。
- where句でMetricをmetricNameとmetricTimesliceNameを絞り込む
- filter関数でmetricTimesliceNameを絞り込む
NQRLにすると次のようになります。
SELECT filter( count(newrelic.timeslice.value), where metricTimesliceName like 'Cache/Spring/%/hits' ) as 'hits count', filter( count(newrelic.timeslice.value), where metricTimesliceName like 'Cache/Spring/%/misses' ) as 'misses count' FROM Metric WHERE appName in ('your-app-name') -- appNameは利用しているAPM名に合わせて変更する AND metricName = 'newrelic.timeslice.value' -- Cache/Spring/<cache-provider>/<cache-name>/ で絞り込む AND metricTimesliceName like 'Cache/Spring/java.util.ConcurrentHashMap/my_cache/%' LIMIT MAX TIMESERIES
準備ができたのでキャッシュヒット率を計算してみましょう。キャッシュヒット率は単位期間のhitsの個数をhitsとmissesの合計で割れば求めることができます。
先ほどのNRQLをベースに考えてみると、filterで絞り込んだ結果同士の割り算で求めることができそうです。簡単ですね!
SELECT -- filterの結果同士を割り算 filter( count(newrelic.timeslice.value), where metricTimesliceName like 'Cache/Spring/%/hits' ) / filter( count(newrelic.timeslice.value), where metricTimesliceName like 'Cache/Spring/%/hits' or metricTimesliceName like 'Cache/Spring/%/misses' ) FROM Metric WHERE appName in ('your-app-name') AND metricName = 'newrelic.timeslice.value' AND metricTimesliceName like 'Cache/Spring/java.util.ConcurrentHashMap/my_cache/%' LIMIT MAX TIMESERIES
発展: FACET host
このクエリにFACET hostを追加すると、アプリケーションが動いているhostごとのキャッシュヒット率のグラフを作ることができます。
SELECT filter( count(newrelic.timeslice.value), where metricTimesliceName like 'Cache/Spring/%/hits' ) / filter( count(newrelic.timeslice.value), where metricTimesliceName like 'Cache/Spring/%/hits' or metricTimesliceName like 'Cache/Spring/%/misses' ) FROM Metric WHERE appName in ('your-app-name') AND metricName = 'newrelic.timeslice.value' AND metricTimesliceName like 'Cache/Spring/java.util.ConcurrentHashMap/my_cache/%' -- hostごとにまとめる FACET `host` -- -- appNameが複数ある場合はhost, appNameの二つにすると便利 -- FACET `host`, appName LIMIT MAX TIMESERIES
キャッシュヒット率の可視化にかかわらず、hostごとのグラフを表示できると異常が発生しているhostが一目で確認できとても便利です。
- Metricは
FACET host
できる!
これはぜひ覚えておいてください。
<cache-name>
のキャッシュヒット率を求める
すべてのキャッシュのキャッシュヒット率を求めてみます。改めてメトリクスの形を確認します。
Cache/Spring/<cache-provider>/<cache-name>/hits
capture関数 *2を利用して<cache-provider>/<cache-name>
を抽出し、その結果をFACETに渡せば良さそうです。
-- captureでCache/Spring/<cache-provider>/<cache-name>/hitsの -- <cache-provider>/<cache-name>部分を抽出する。 FACET capture( metricTimesliceName, r'Cache/Spring/(?P<cacheName>.*)/.*' )
個別のキャッシュヒット率を求めるNRQLと組み合わせてみます。
SELECT filter( count(newrelic.timeslice.value), where metricTimesliceName like 'Cache/Spring/%/hits' ) / filter( count(newrelic.timeslice.value), where metricTimesliceName like 'Cache/Spring/%/hits' or metricTimesliceName like 'Cache/Spring/%/misses' ) FROM Metric WHERE appName in ('your-app-name') AND metricName = 'newrelic.timeslice.value' AND metricTimesliceName like 'Cache/Spring/%' -- captureでCache/Spring/<cache-provider>/<cache-name>/hitsの -- <cache-provider>/<cache-name>部分を抽出する。 FACET capture( metricTimesliceName, r'Cache/Spring/(?P<cacheName>.*)/.*' ) LIMIT MAX TIMESERIES
これでキャッシュ名ごとのキャッシュヒット率が可視化できました!
まとめ
New Relic Java Agentの組み込みのSpring Cache Instrumentationが送信するメトリクスをNRQLで集計し、キャッシュヒット率を可視化しました。これにより、キャッシュの効率を定量的に評価し、今後の改善につなげる基盤が整いました。
NewsPicksでは、Spring Cacheに加え、JedisやHikariCPなどの様々なミドルウェアライブラリのメトリクスを可視化し、日々の運用とアプリケーションの改善に活用しています。 アプリケーションの状態を効果的に可視化するには、プロジェクトで使用しているライブラリのInstrumentationの有無と、利用可能なメトリクスを把握することが重要です。
New Relic Java Agentに同梱されているInstrumentationはGitHubで確認できますので、使用しているライブラリのInstrumentationの有無を確認してみてください。
*1:https://github.com/newrelic/newrelic-java-agent/pull/1466 のExampleから持ってきました
*2:使い方は https://newrelic.com/jp/blog/how-to-relic/nrql-regrex-capture がわかりやすいです