patorashのブログ

方向性はまだない

リファクタリングデーでキャッシュ化に取り組んだ話

第三回リファクタリングデーを開催したのだけれど、これがすごくよかったのでまとめておく。

お題はキャッシュ化

後輩氏とパートナーさんは、まだキャッシュにすることの経験がなかったので、過去にそれぞれが実装したところ等でキャッシュ化できそうなところを探してみよう、という感じにした。まぁ目に付けば別に他のところでもいい。

なぜキャッシュ化するのか?

この点から説明しておいた。

Webアプリケーションの開発はDBへのアクセス回数を減らすことが大事っていうのはだいぶ浸透していて、メンバー全員が気を付けて開発できるようになってきた。しかし、そもそもアクセスしなくてもいいようにするのが理想である。つまりはコンテンツの変更がなければ、以前に作ったデータを再利用すればよい。これがキャッシュ化。

キャッシュ化のメリット

キャッシュ化が効くと何が嬉しいか?

  • アプリケーションサーバーの負荷が減る
  • データベースサーバーの負荷が減る
  • ユーザーへのレスポンスが早くなり体験がよくなる

まさにいいこと尽くめ。

キャッシュ化のデメリット

デメリットも、なくはない。

  • データが更新されているにも関わらず、変更が画面上に反映されない等の事故が起こりうる
    • キャッシュの寿命の設計が必要に
  • 本来見えてはいけないデータが見えてしまう等の事故が起こりうる
    • キャッシュの適用範囲の設計が重要に

キャッシュキーに何を持たせるか?

上記のデメリットは基本的に全て事故となりうることばかりなので、そうならないようにするには?をまず教えた。

  • ユーザー個人に紐づくデータをキャッシュする場合は、キャッシュキーに個人を特定できる情報を付加する(ユーザーIDなど)
  • ログイン状態に紐づくデータをキャッシュする場合は、キャッシュキーにログイン状態を特定できる情報を付加する(Deviseでいえば、user_signed_in?の結果等)
  • オブジェクトをキーとして指定すれば、オブジェクトに変更があれば過去のキャッシュは無視されるので有効
  • ActiveRecord::Relationの状態のオブジェクトをキーにしても意味がないので注意(クエリ発行してないから)
  • 何かしらのオブジェクト、配列、Hash等にしておくとキーにしやすい
  • データ更新系のコールバックでキャッシュを削除することを忘れないこと

何をキャッシュするか?

View

基本的にはレンダリング後のViewをキャッシュしておくといいが、会員サイト等ではページキャッシュはやりづらい。ユーザーに依存する情報をAjaxで取ってくるようにでも実装していない限りは。その実装も面倒なので、基本的にはフラグメントキャッシュを使う。

- cache(post) do
  h1 = post.title
  = simple_format post.content

計算結果など

計算結果というと仰々しいが、例えばカウント数とか。毎回カウントを取得してもいいが、何気にカウントするためのクエリは遅い。

user_count = Rails.cache.fetch(:user_count) { User.count }

更に、これをフラグメントキャッシュで使う。

- cache(:"user_count_#{user_count}") do
  p = "今までに#{number_with_delimiter(user_count)}人が活用しているノウハウ"

キャッシュの廃棄もやると…

class User < ApplicationRecord

  after_commit :delete_cache

  private

    def delete_cache
      Rails.cache.delete(:user_count)
    end
end

初期表示データ

例えば、検索などで使う場合であっても、デフォルトで設定されている検索条件の結果等はキャッシュ化しておくとよい。どうせ毎回実行されるからだ。

マスタデータ

マスタデータは、なかなか更新されにくいので、キャッシュのし甲斐がある。

ユーザーに関連するデータ

ユーザーに関連するデータは頻繁に参照するのでキャッシュ化すると速くなりやすい。その分、キャッシュの寿命と適用範囲には気を付けなければならない。

多少ずれていても問題ないデータ

常に最新のデータが表示されていなくてもいいような場合は、キャッシュの寿命を1分とかにしておくと、1分間はDBへのアクセスも減らせるし、1分後には最新のデータになるのでサイトとしても動きがあるように見せられる。ランキングとかで使えるだろうか。

実際にやってみた

以上のようなことを、ざっと説明した後に作業に取り掛かってもらった。実際にSQLの発行回数が減っていることをrack-miniprofilerなどで確認すること!と伝えた。

メンバーはあまりキャッシュを使うことを意識したことがなかったようなので、目に見えて速くなることは楽しかったようであった。

成果

リファクタリングデーの成果としては、主にマスタデータのキャッシュ化や、初期表示データのキャッシュ化をやってもらった。私自身はユーザーに関連するデータをキャッシュ化したりした(閲覧履歴などのデータ)。頻繁に参照する部分でキャッシュを活用できるようにしたのはかなり良かったと思う。

また、これ以降のバックログに取り組む際に、新たな機能においてもキャッシュ化できる箇所は対応してからPRを送ってくれたので、これが最も大きな成果であったと思う。

メンバーが順調に育ってきていると感じられた。