Ruby on Rails の
キャッシュ機構について
2019/07/17
大手町.rb #19
大手町.rb #19 「Ruby on Rails の持つキャッシュ機構について」
1自己紹介
Tomoya Kawanishi a.k.a. @cuzic
エネチェンジ株式会社 チーフエンジニア
電力会社、ガス会社を切り替えるなら、エネチェンジ経由で!
一般家庭も!法人も!
エンジニア積極採用中です
Ruby関西の中の人
2019年9月15日(日) 大阪RubyKaigi 02
発表者として登壇くださる方、あとで声かけください。
大手町.rb の中の人
毎月 大手町.rb の開催を予定
第2水曜か、第3水曜あたりで定期開催
東京駅、各線大手町駅から直結!
Ruby の初級者がメインターゲット
大手町.rb #19 「Ruby on Rails の持つキャッシュ機構について」
今日のテーマ
Ruby on Rails のキャッシュ機構について
キャッシュはなぜ重要か
遅い処理はどうしてもたくさんある
入力が同じなら、出力も同じことが多い
単に、前回の値を覚えてそれを返せばいい!(=キャッシュ)
今日、話すこと
キャッシュの種類
Ruby on Rails が用意しているキャッシュストア
ENECHANGEでのキャッシュの利用について
2
大手町.rb #19 「Ruby on Rails の持つキャッシュ機構について」
キャッシュの種類
ページキャッシュ
ページ全体をキャッシュする
静的コンテンツのキャッシュ
ブラウザ・CDN・WEBサーバでキャッシュ
非常に高速に応答できる
フラグメントキャッシュ
view 部品のレンダリング結果をキャッシュ
低レベルキャッシュ
任意のクエリ結果、計算結果をキャッシュ
SQLキャッシュ
同一リクエスト内で、同一クエリを実行した
場合、キャッシュを返す
3
ブラウザ
CDN
WEBサーバ
(NGINX)
APサーバ
(puma等)
DBサーバ
大手町.rb #19 「Ruby on Rails の持つキャッシュ機構について」
ページキャッシュの利用
Ruby on Rails では静的コンテンツは下記を両立できる
リリース後すぐに反映される
キャッシュがすでにあればキャッシュを使う
Ruby on Rails のキャッシュの仕組み
アセットパイプラインで digest値つきのファイル名を生成
内容が変化すると digest(ファイル名)が変化する
Ruby on Rails で作られるコンテンツもページキャッシュ
を意識した設計にできる
4
# ページキャッシュが使うための設定
ttl = 1.hour
expires_in ttl, public: true, must_revalidate: true
request.session_options[:skip] = true # set-cookie を飛ばさない
@article = Article.last
fresh_when @article # fresh_when で ETAG をいいかんじに設定できる
must_revalidate: true
前回と同じコンテンツかの問い合わせ
を必須にする
大手町.rb #19 「Ruby on Rails の持つキャッシュ機構について」
(参考) ETAG とキャッシュの動作
ブラウザは ETAG と
組合せてページを
キャッシュする
リクエスト時に前回記憶し
た ETAG も合わせて送信
サーバは前回と同じ値なら
304 not modified を応答
ページ全体を返すよりも
ずっとコンパクトで
ネットワーク負荷が少ない
ブラウザだけでなく、
CDN やプロキシも同じよ
うに動作する
5
ブラウザ WEBサーバ
① / をリクエスト
② 200 OK をレスポンス
ETAG deadbeaf をブラウザはキャッシュ
③ / をリクエスト
前回の ETAG は deadbeaf
④ 304 not modified をレスポンス
ブラウザは前回のキャッシュを使う
⑤ / のコンテンツを更新
ETAG が beafbeaf になる
⑦ / をリクエスト
前回の ETAG は deadbeaf
⑧ 200 OK をレスポンス
ETAG beafbeaf をブラウザはキャッシュ
大手町.rb #19 「Ruby on Rails の持つキャッシュ機構について」
フラグメントキャッシュの利用 6
<% @products.each do |product| %>
<% cache product do %>
<%= render product %>
<% end %>
<% end %>
<%# ハッシュ値: views/products/1-201505056193031061005000/bea67108094918eeba42cd4a6e786901 %>
フラグメントキャッシュ
各部品ごとに個別に適切に期限切れを設定できる
下記の例では、 product の cache_key とテンプレート
ツリーダイジェストを元にキャッシュされる
cache_key : id と updated_at を元に生成したキー
テンプレートツリーダイジェスト: キャッシュされるビューフラ
グメントの内容から生成したハッシュ値
Ruby on Rails ではキーベースの有効期限を採用している
大手町.rb #19 「Ruby on Rails の持つキャッシュ機構について」
低レベルキャッシュ
低レベルキャッシュを使うと任意の処理結果をキャッシ
ュできる
Rails.cache.fetch
第1引数: キャッシュキー
expires_in: 有効期限
ブロック付きで呼び出す
キャッシュがなければ、ブロックの評価結果をキャッシュ
有効なキャッシュがあれば、キャッシュを返す
モデルで cache_key メソッドと組合わせて利用する
7
class Product < ApplicationRecord
def competing_price
Rails.cache.fetch("#{cache_key}/competing_price", expires_in: 12.hours) do
Competitor::API.find_price(id)
end
end
end
大手町.rb #19 「Ruby on Rails の持つキャッシュ機構について」
SQL キャッシュ
同一リクエスト内で同一のクエリを実行したとき、同一
の結果を返す
特別な設定なく、自動的に利用される
8
CACHE (0.0ms) SELECT "areas".* FROM "areas" WHERE "areas"."id" = 1
大手町.rb #19 「Ruby on Rails の持つキャッシュ機構について」
キャッシュストア
単一サーバなら FileStore、
複数サーバなら RedisCacheStoreがオススメ
ActiveSupport::Cache::MemoryStore
各Rubyプロセス内に持つキャッシュストア
プロセス間でキャッシュを共有できる
ActiveSupport::Cache::FileStore
ディスクシステム上のファイルにキャッシュする
プロセス間でキャッシュ共有できる
ActiveSupport::Cache::MemCacheStore
memcached を使うキャッシュ
ActiveSupport::Cache::RedisCacheStore
Redis を使うキャッシュ
9
大手町.rb #19 「Ruby on Rails の持つキャッシュ機構について」
(おまけ) RequestStore
https://github.com/steveklabnik/request_store
同一リクエスト内が存続期間
キャッシュストアというか
同一リクエスト内でだけ使えるグローバル変数
Model と Controller とかでデータ共有したいときとかに便利
もちろん、キャッシュストアとしても使える
容量・用法は適切に
10
大手町.rb #19 「Ruby on Rails の持つキャッシュ機構について」
ENECHANGE でのキャッシュ
ページキャッシュを積極的に活用
静的アセットはアセットパイプラインを使って生成
Nginx で、digest があれば永遠にキャッシュされるように設定
一部の静的なページはページキャッシュを利用
低レベルキャッシュ(Rails.cache.fetch)を Model で利用
キャンペーン情報等は日単位で切り替わることが多い
ENECHANGE ではキャッシュキーに年月日を含めている
日付が変わると、自動的に取得しなおす
キャッシュが切り替わる条件はすべてキャッシュキーに含めるのが
ベストプラクティス
RedisCacheStore と RequestStore を組み合わせて利用
Redis サーバはネットワーク的に別のサーバにあり、ちょっと遠い
一部のキャッシュしている値がとても大きい(数MBある)
Redis への取得結果を RequestStore にキャッシュ
同一リクエスト内で同じ値を Redisサーバまで取りに行かせない
11
大手町.rb #19 「Ruby on Rails の持つキャッシュ機構について」
まとめ
いろんなタイミングでキャッシュできる
ページキャッシュ、低レベルキャッシュ
キャッシュ機構もいろいろある
ファイルキャッシュ、MemCached、Redis ・・・
ENECHANGE では低レベルキャッシュを多用
DB からの取得結果をキャッシュ
キャッシュヒット率を高める
RequestStore も一部利用している
12
ご清聴ありがとう
ございました

Ruby on Rails のキャッシュ機構について

  • 1.
    Ruby on Railsの キャッシュ機構について 2019/07/17 大手町.rb #19
  • 2.
    大手町.rb #19 「Rubyon Rails の持つキャッシュ機構について」 1自己紹介 Tomoya Kawanishi a.k.a. @cuzic エネチェンジ株式会社 チーフエンジニア 電力会社、ガス会社を切り替えるなら、エネチェンジ経由で! 一般家庭も!法人も! エンジニア積極採用中です Ruby関西の中の人 2019年9月15日(日) 大阪RubyKaigi 02 発表者として登壇くださる方、あとで声かけください。 大手町.rb の中の人 毎月 大手町.rb の開催を予定 第2水曜か、第3水曜あたりで定期開催 東京駅、各線大手町駅から直結! Ruby の初級者がメインターゲット
  • 3.
    大手町.rb #19 「Rubyon Rails の持つキャッシュ機構について」 今日のテーマ Ruby on Rails のキャッシュ機構について キャッシュはなぜ重要か 遅い処理はどうしてもたくさんある 入力が同じなら、出力も同じことが多い 単に、前回の値を覚えてそれを返せばいい!(=キャッシュ) 今日、話すこと キャッシュの種類 Ruby on Rails が用意しているキャッシュストア ENECHANGEでのキャッシュの利用について 2
  • 4.
    大手町.rb #19 「Rubyon Rails の持つキャッシュ機構について」 キャッシュの種類 ページキャッシュ ページ全体をキャッシュする 静的コンテンツのキャッシュ ブラウザ・CDN・WEBサーバでキャッシュ 非常に高速に応答できる フラグメントキャッシュ view 部品のレンダリング結果をキャッシュ 低レベルキャッシュ 任意のクエリ結果、計算結果をキャッシュ SQLキャッシュ 同一リクエスト内で、同一クエリを実行した 場合、キャッシュを返す 3 ブラウザ CDN WEBサーバ (NGINX) APサーバ (puma等) DBサーバ
  • 5.
    大手町.rb #19 「Rubyon Rails の持つキャッシュ機構について」 ページキャッシュの利用 Ruby on Rails では静的コンテンツは下記を両立できる リリース後すぐに反映される キャッシュがすでにあればキャッシュを使う Ruby on Rails のキャッシュの仕組み アセットパイプラインで digest値つきのファイル名を生成 内容が変化すると digest(ファイル名)が変化する Ruby on Rails で作られるコンテンツもページキャッシュ を意識した設計にできる 4 # ページキャッシュが使うための設定 ttl = 1.hour expires_in ttl, public: true, must_revalidate: true request.session_options[:skip] = true # set-cookie を飛ばさない @article = Article.last fresh_when @article # fresh_when で ETAG をいいかんじに設定できる must_revalidate: true 前回と同じコンテンツかの問い合わせ を必須にする
  • 6.
    大手町.rb #19 「Rubyon Rails の持つキャッシュ機構について」 (参考) ETAG とキャッシュの動作 ブラウザは ETAG と 組合せてページを キャッシュする リクエスト時に前回記憶し た ETAG も合わせて送信 サーバは前回と同じ値なら 304 not modified を応答 ページ全体を返すよりも ずっとコンパクトで ネットワーク負荷が少ない ブラウザだけでなく、 CDN やプロキシも同じよ うに動作する 5 ブラウザ WEBサーバ ① / をリクエスト ② 200 OK をレスポンス ETAG deadbeaf をブラウザはキャッシュ ③ / をリクエスト 前回の ETAG は deadbeaf ④ 304 not modified をレスポンス ブラウザは前回のキャッシュを使う ⑤ / のコンテンツを更新 ETAG が beafbeaf になる ⑦ / をリクエスト 前回の ETAG は deadbeaf ⑧ 200 OK をレスポンス ETAG beafbeaf をブラウザはキャッシュ
  • 7.
    大手町.rb #19 「Rubyon Rails の持つキャッシュ機構について」 フラグメントキャッシュの利用 6 <% @products.each do |product| %> <% cache product do %> <%= render product %> <% end %> <% end %> <%# ハッシュ値: views/products/1-201505056193031061005000/bea67108094918eeba42cd4a6e786901 %> フラグメントキャッシュ 各部品ごとに個別に適切に期限切れを設定できる 下記の例では、 product の cache_key とテンプレート ツリーダイジェストを元にキャッシュされる cache_key : id と updated_at を元に生成したキー テンプレートツリーダイジェスト: キャッシュされるビューフラ グメントの内容から生成したハッシュ値 Ruby on Rails ではキーベースの有効期限を採用している
  • 8.
    大手町.rb #19 「Rubyon Rails の持つキャッシュ機構について」 低レベルキャッシュ 低レベルキャッシュを使うと任意の処理結果をキャッシ ュできる Rails.cache.fetch 第1引数: キャッシュキー expires_in: 有効期限 ブロック付きで呼び出す キャッシュがなければ、ブロックの評価結果をキャッシュ 有効なキャッシュがあれば、キャッシュを返す モデルで cache_key メソッドと組合わせて利用する 7 class Product < ApplicationRecord def competing_price Rails.cache.fetch("#{cache_key}/competing_price", expires_in: 12.hours) do Competitor::API.find_price(id) end end end
  • 9.
    大手町.rb #19 「Rubyon Rails の持つキャッシュ機構について」 SQL キャッシュ 同一リクエスト内で同一のクエリを実行したとき、同一 の結果を返す 特別な設定なく、自動的に利用される 8 CACHE (0.0ms) SELECT "areas".* FROM "areas" WHERE "areas"."id" = 1
  • 10.
    大手町.rb #19 「Rubyon Rails の持つキャッシュ機構について」 キャッシュストア 単一サーバなら FileStore、 複数サーバなら RedisCacheStoreがオススメ ActiveSupport::Cache::MemoryStore 各Rubyプロセス内に持つキャッシュストア プロセス間でキャッシュを共有できる ActiveSupport::Cache::FileStore ディスクシステム上のファイルにキャッシュする プロセス間でキャッシュ共有できる ActiveSupport::Cache::MemCacheStore memcached を使うキャッシュ ActiveSupport::Cache::RedisCacheStore Redis を使うキャッシュ 9
  • 11.
    大手町.rb #19 「Rubyon Rails の持つキャッシュ機構について」 (おまけ) RequestStore https://github.com/steveklabnik/request_store 同一リクエスト内が存続期間 キャッシュストアというか 同一リクエスト内でだけ使えるグローバル変数 Model と Controller とかでデータ共有したいときとかに便利 もちろん、キャッシュストアとしても使える 容量・用法は適切に 10
  • 12.
    大手町.rb #19 「Rubyon Rails の持つキャッシュ機構について」 ENECHANGE でのキャッシュ ページキャッシュを積極的に活用 静的アセットはアセットパイプラインを使って生成 Nginx で、digest があれば永遠にキャッシュされるように設定 一部の静的なページはページキャッシュを利用 低レベルキャッシュ(Rails.cache.fetch)を Model で利用 キャンペーン情報等は日単位で切り替わることが多い ENECHANGE ではキャッシュキーに年月日を含めている 日付が変わると、自動的に取得しなおす キャッシュが切り替わる条件はすべてキャッシュキーに含めるのが ベストプラクティス RedisCacheStore と RequestStore を組み合わせて利用 Redis サーバはネットワーク的に別のサーバにあり、ちょっと遠い 一部のキャッシュしている値がとても大きい(数MBある) Redis への取得結果を RequestStore にキャッシュ 同一リクエスト内で同じ値を Redisサーバまで取りに行かせない 11
  • 13.
    大手町.rb #19 「Rubyon Rails の持つキャッシュ機構について」 まとめ いろんなタイミングでキャッシュできる ページキャッシュ、低レベルキャッシュ キャッシュ機構もいろいろある ファイルキャッシュ、MemCached、Redis ・・・ ENECHANGE では低レベルキャッシュを多用 DB からの取得結果をキャッシュ キャッシュヒット率を高める RequestStore も一部利用している 12
  • 14.