Hard work by INTERNET

ベンチャーで働くひとりぼっちWEB開発者が頑張るブログ

ActiveSupport::Cacheの豆知識

はじめに

ActiveSupport::Cache は、Rails.cache.read, Rails.cache.writeと書くことでフラグメントキャッシュを扱うクラスです。 さきほど、このクラスのソースコードを読んだので知見を紹介します。(rails4.2時点)

Rails.cache.fetchにブロックを渡すとミスキャッシュした時に書き込んでくれる

これは公式ドキュメントに書いている通り有名だと思います。

      #   cache = ActiveSupport::Cache::MemCacheStore.new
      #   cache.fetch("foo", force: true, raw: true) do
      #     :bar
      #   end

エントリがなかったら save_block_result_to_cache というメソッドが呼ばれていました。

複数エントリを一括に読み書きするメソッドがある

3.times do |i|
  Rails.cache.write("a#{i}", "はい#{i}")
end

という3回ストアへ書き込むコードがありますが、これを1度のアクセスだけで行うメソッドが存在しています。

Rails.cache.write_multi, Rails.cache.read_multiです。 Rails.cache.write_multi(3.times.map {|i| { "a#{i}" => "はい#{i}" } })な感じです。

このインターフェースはAPIドキュメントにも記載されている通り、スーパークラスで定義していますが、本当に1度だけ書き込むのかはキャッシュストアに依存した実装になっています。 RedisCacheStoreでは、ちゃんと1度だけ書き込むようになっていました。

# Writes multiple entries to the cache implementation. Subclasses MAY
# implement this method.
def write_multi_entries
...

キャッシュストアのクラス構成は継承を使っている。

ActiveSupport::Cacheをスーパークラスとして、各キャッシュストアはサブクラスとして定義されています。 これは、共通のIFを持たせたかったためでしょう。 raise NotImplementedErrorというメソッドがスーパークラスの随所で散見されます。

キャッシュストア内からストアの操作には instrumentというラッパーメソッドを使わなければならない

      def write(name, value, options = nil)
        options = merged_options(options)

        instrument(:write, name, options) do
          entry = Entry.new(value, **options.merge(version: normalize_version(name, options)))
          write_entry(normalize_key(name, options), entry, **options)
        end
      end

instrumentというラッパーの中には ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload) { yield(payload) } がコールされていました。 なるほど〜これは、Notificationのsubscriberに通知をするためだったんですね。

instrumentを使わずに操作をしている場合は、subscriberへの通知もれが起きるでしょう。

むすび

そこまで突っ込んで読んではいませんが、全体の構成がわかってきたように思います。

おわり