New RelicでSpring Frameworkを使ったアプリケーションのキャッシュヒット率を可視化する

この記事は New Relic Advent Calendar 2024 と NewsPicks Advent Calendar 2024 の7日目の記事です。

ソーシャル経済メディア「NewsPicks」SREチームの飯野です。

NewsPicksではサービスの状態を可視化するために New Relic APM(Application Performance Monitoring)を導入しています。ことあるごとにNew Relicのダッシュボードを確認することでサービスの状態の把握や、安定性向上、パフォーマンス改善のヒントを得ています。

今回はNew RelicのAPMでは用意されていないキャッシュヒット率の可視化について話したいと思います。

キャッシュは重要

NewsPicksは参照系のアクセスが非常に多いため、キャッシュを駆使したDBへの問い合わせ削減にとても気をつかっています。特にプッシュ通知後のアクセス急増時には、キャッシュの活用状況が直接ユーザー体験に影響を与えることがあります。

キャッシュが適切に機能している場合、DBへの問い合わせが最小になり、高速にアクセスを処理できます。

キャッシュが適切に機能していない場合(これはビジネス要件の変化やABテストなどで最適化されていないAPIがアプリから広く使われるようになった時などに起こります)、アクセスの処理が追いつかず、ユーザー体験が悪化につながります。例えば、スマホの通知をタップしてもアプリがなかなか起動しない、記事が開けない、エラーが発生してしまうなどの問題が発生します。

このように重要なキャッシュですが、New Relic APMには現時点ではキャッシュヒット率のための画面は用意されていません。

代替手段として、Transactionの詳細 > Breakdown table > Avg Calls/Txn を確認し、この数値の増減で大まかな利用状況を確認していました。

Transaction Detail > Brakedown 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種類のメトリクス(hitsmissesclearevict)のうち、hitsmissesを使用してキャッシュヒット率を計算できます。

個別の <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の合計で割れば求めることができます。

\frac{count(hits)}{count(hits) + count(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

これでキャッシュ名ごとのキャッシュヒット率が可視化できました!

newspicks-apiのキャッシュヒット率から一部抜粋。
Thresholdsを定義すると「効率的!」などのコメントが表示されて賑やかになります。

まとめ

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 がわかりやすいです

Page top