7
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Ruby on RailsAdvent Calendar 2023

Day 5

solid_cacheについて調べてみた

Posted at

はじめに

この記事はRuby on Rails Advent Calendar 2023の記事です。

solid_cacheとは

solid_cacheとは最近(具体的には2023年10月)に最初のバージョンがリリースされたgemです。目的はRailsのcache storeの一つになることで、Redisなどを使わずRDBにキャッシュを保存することが特徴です。余談ながら、initial commitは2022年11月でしたので、約1年秘密裏に開発されていたことになります。

なぜRDBにキャッシュを保存するのか?

キャッシュをRDBに保存する動機付けについてはREADMEに書かれています。

Using SQL databases backed by SSDs we can have caches that are much larger and cheaper than traditional memory only Redis or Memcached backed caches.
Testing on HEY shows that reads and writes are 25%-50% slower than with a Redis cache (1.2ms vs 0.8-1ms per single-key read), but this is not a significant percentage of the overall request time.
If cache misses are expensive (up to 50x the cost of a hit on HEY), then there are big advantages to caches that can hold months rather than days of data.

確かにキャッシュ自体の速度は低下するものの、キャッシュを安く大容量化できるのがメリットということのようです。最後の文にあるように、キャッシュを数ヶ月保持しておくことでキャッシュミス(約50倍の低速化)を回避できるということですね。

どのようにRDBにキャッシュを保存するのか

RailsとRDBといえば当然ActiveRecordが思い浮かびます。solid_cacheもARのインターフェースを使ってキャッシュを保存しています。

このクラスにメインのロジックが書かれているようです。このクラスはRecord抽象クラスを経由してActiveRecord::Baseを継承しています。

def upsert_all_no_query_cache(attributes)
  insert_all = ActiveRecord::InsertAll.new(self, attributes, unique_by: upsert_unique_by, on_duplicate: :update, update_only: [ :value ])
  sql = connection.build_insert_sql(ActiveRecord::InsertAll::Builder.new(insert_all))

  message = +"#{self} "
  message << "Bulk " if attributes.many?
  message << "Upsert"
  # exec_query_method does not clear the query cache, exec_insert_all does
  connection.send exec_query_method, sql, message
end

def select_all_no_query_cache(query, values)
  uncached do
    if connection.prepared_statements?
      result = connection.select_all(sanitize_sql(query), "#{name} Load", Array(values), preparable: true)
    else
      result = connection.select_all(sanitize_sql([ query, values ]), "#{name} Load", nil, preparable: false)
    end

    result.cast_values(SolidCache::Entry.attribute_types)
  end
end

この2つのメソッドはキャッシュストアが実装するべきインターフェースであるwritereadの実装詳細です。ARの内部クラスを利用していたりして理解が少し難しいですが、詳細には踏み込まずともINSERTSELECTを実行しているのはわかります。

まとめ

ということでsolid_cacheについて簡単に紹介しました。まだ新しいプロダクトであり、今後様々な改善が導入されることでしょう。実用例についてはすでに37Signals社のHEYがあるようですが、こちらも今後増加が見込まれます。

コードの行数は短く読みやすいので、コードリーディングの題材としても使えるかもしれません。今回この記事を書くに当たってコードを読んでいたところ、小さな改善ができそうだったのでPRを送ってみたのですが、新しいプロダクトではこういうことが出来るのも良いですね。

7
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?