PaPoo
cover
technews
Author
technews
世界の技術ニュースをリアルタイムでキャッチし、日本語でわかりやすく発信。AI・半導体・スタートアップから規制動向まで、グローバルテックシーンの「今」をお届けします。

高トラフィックREST APIを軽くする方法:Redisキャッシュ、書き込み整合性、そしてJMeter検証

記事のキーポイント

なぜREST APIは遅くなるのか

元記事の主張はかなりシンプルです。
高トラフィックなREST APIは、databaseアクセスがボトルネックになりやすい、という話です。

これは現場感としてもすごく納得感があります。
どんなにSQLを改善して、indexを貼って、ちょっと頑張っても、DBに毎回取りに行く構成だと限界が見えやすいんですよね。記事では、DBの応答が数百ミリ秒かかる一方で、Redisを使えばずっと速くなると説明しています。しかも、Redisはメモリ上で動くので、​disk-based なDBより桁違いに速い。ここはやっぱり強いです。

記事では、あるテストで「10秒以上かかっていた重いリクエストが1秒未満になった」と紹介しています。もちろんケース次第ではありますが、キャッシュの効き方って本当に極端です。ハマると気持ちいい。これは面白いところです。

まずは Redis caching の基本を入れる

記事では Spring Boot を前提に、Redis cache の土台を作っています。
必要なのは主にこの2つです。

そして @EnableCaching を付ける。
ざっくり言うと、​Springに「cacheを使います」と教えるための設定です。

さらに、application.properties などで Redis の接続先やTTL(Time To Live)を設定します。TTLは、​cacheを何分で期限切れにするかという意味です。永久に残すと古いデータが残り続けるので、期限を決めるのは大事です。

このあたりは地味ですが、実はかなり重要です。
cacheは「入れれば終わり」ではなく、​いつ消すかまで含めて設計するものなんですよね。

読み込みを速くする:Redis cache の本命

この手の記事でまず効くのは、やはり読み込みです。
よくあるパターンは「最初にcacheを見て、なければDBへ」という流れです。

イメージはこんな感じです。

  1. APIリクエストが来る
  2. まずRedisを見る
  3. データがあれば、それを返す
  4. なければDBに取りに行く
  5. 取れたデータをRedisに保存して返す

これだけで、​同じデータへのアクセスが多いAPIほど強くなる
商品詳細、プロフィール、設定値、ランキング、参照専用の一覧など、使いどころはかなり多いです。

個人的には、cacheの価値って「平均応答時間を少し縮める」よりも、​ピーク時のDB負荷を下げられることにあると思います。速くなるのももちろん嬉しいですが、DBが倒れにくくなるのが本当に大きいです。

書き込みはどうする? @CachePut が便利

cacheは読み込み専用ではありません。
データを更新したときに、cacheとの整合性をどう保つかが肝です。

記事では write-through という考え方を紹介しています。
これは、​DBに書いたら、その結果をすぐcacheにも反映する方式です。

Spring Boot では @CachePut が使えます。
たとえば商品情報を更新するメソッドで、DB保存のあとに、その戻り値をcacheへ入れるイメージです。

これが何を嬉しくするかというと、
更新直後の読み込みでも古いデータを返しにくいことです。

ここはかなり実務的です。
cacheを導入すると「速くなったけど古い値が出る」という事故が起きがちです。
なので、読み込みだけでなく、​更新時の流れをどうするかは絶対に避けて通れません。

削除については @CacheEvict を使って、DBから消したらcacheも消す。
これで「DBにはないのにcacheにだけ残る幽霊データ」を減らせます。こういう話、地味だけど本当に大事です。

write-behind は強いけど、扱いが難しい

記事では write-behind も触れています。
これは、​まずcacheに書いて、DB反映は後でまとめてやる方式です。

DBへの負担を下げられるので、理屈としては魅力的です。
ただし、障害時の扱いとかデータ消失リスクとか、考えることが増えます。なので、個人的にはかなり慎重に使うべき方式だと思います。

image_0003.svg

記事でも、write-through よりは一般的でないと読み取れます。
高性能だけを追うと危ないので、ここは「速さ」と「安全性」のバランス勝負ですね。

cache stampede を防がないと、逆にDBが死ぬ

ここがこの記事でかなり重要なポイントです。
cache stampede は、cacheが期限切れになった瞬間に大量のリクエストが一斉にDBへ殺到する現象です。
日本語だと「雪崩」「群衆雪崩」みたいなイメージです。

たとえば人気商品のcacheが切れた瞬間、100件のリクエストが全部DBを見に行ったらどうなるか。
せっかくcacheを入れたのに、DBが一気に苦しくなります。むしろ、何もしないより悪化することすらありえます。これは怖い。

記事では、この対策として locking を使います。
簡単に言うと、​​「このデータを取りに行くのは1人だけ」にするやり方です。

単一インスタンスなら Java の lock でもいいけれど、複数台構成なら distributed lock が必要になる。
そこで Redis と相性がいいのが Redisson です。

流れはこんな感じです。

この仕組み、少し重たいのは事実です。
でも、​本当に混むデータだけに使うならかなり有効だと思います。すべてのキーに入れるのはやりすぎです。ホットなデータだけを守る、というのが現実的でしょう。

記事では他にも、TTLを少しずらす、早めに再計算する、といった軽めの対策にも触れています。
このあたりは「銀の弾丸はない」という感じで、実に現実的です。

速くなったかは JMeter で確かめる

最後に大事なのが、​本当に速くなったかを load test で確認することです。
記事では Apache JMeter を使っています。

JMeterは、ざっくり言えばたくさんのユーザーが同時にアクセスした状況を再現する道具です。
APIが1人に対して速いのは当たり前で、問題は「100人、1000人が来たらどうなるか」ですよね。そこを見ます。

テストでは、平均応答時間、throughput(1秒あたりに処理できる量)、error rate などを見ます。
cacheなしとcacheありを比較すると、差がかなりはっきり出るはずです。

記事の流れを踏まえると、

個人的には、ここをちゃんと計測する姿勢がいちばん好感を持てます。
キャッシュは「速くなった気がする」で終わらせると危険なので、​数字で見るのが正解です。

この話をどう受け取るべきか

この記事は、単なる「Redis入れよう」記事ではありません。
むしろ、​高トラフィックAPIを本当に運用するなら、読み込み高速化だけでは足りないという話です。

要するに重要なのはこの3つです。

  1. Redisで読み込みを速くする
  2. 更新時の整合性を崩さない
  3. cache stampede を防ぐ

この3点が揃って、ようやく「実戦で使えるcache設計」になります。

個人的には、この記事の良さはかなり実務寄りなところだと思います。
夢のある話だけではなく、「速くなるけど、失効時は危ないよ」「lockは強いけど、使いすぎるなよ」「最後は load test しようね」と、ちゃんと現場の泥臭さを押さえています。こういう記事は信用しやすいです。

まとめ

高トラフィックなREST APIを改善したいなら、まず見るべきはDBアクセスです。
Redis caching は、そのボトルネックを外すためのかなり強力な手段です。ただし、読み込みを速くするだけでは不十分で、書き込みの整合性、cache stampede 対策、そして JMeter などによる検証まで含めて初めて完成度が上がる、というのがこの記事のメッセージだといえます。

現場目線で言うと、​cacheは魔法ではないけれど、設計できればめちゃくちゃ効く
その意味で、この記事はかなり実用的で、読んで損のない内容だと思います。


参考: Optimizing High-Volume REST APIs

同じ著者の記事