Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

Rubyで使える超高速なMemory Cache 「LruRedux」について

More than 3 years have passed since last update.

この記事はPORT Advent Calendar7日目の穴埋めです。

Ruby on Railsの高速化戦略として最もポピュラーなのは、Fragment CacheやローレベルCacheの活用だと思います。私の所属しているチームが運用しているキャリアパーク!でも、多くのページでこれらのキャッシュが使用されています。

キャッシュストアへのアクセス遅延

Railsでキャッシュを活用する場合、多くはバックエンドのデータストアとしてRedisもしくはMemcacheが用いられます。
これらのデータストアはAmazon ElasticCacheなどのクラウドサービスを用いてネットワーク上に設置するのが一般的です。ローカル環境で開発しているとついつい忘れがちなのですが、ネットワーク上に存在するキャッシュストアにアクセスする際には、ネットワークを介することによる遅延が発生してしまいます(多くの場合において無視できるレベルですが・・・)。

ですが、例えば一つのwebページを表示する際にFragment Cacheを5箇所に分けて使用しているなど、リモートのキャッシュストアに複数回のアクセスを行っていたりすると、この遅延も無視できないものになってきます。

LruReduxで超高速なMemoryCacheを

上記のようなネットワーク遅延が気になる場合、MemoryStoreをキャッシュストアとして利用すれば解決できるかもしれません。Rails標準のActiveSupport::CacheでもMemoryStoreはサポートされていますが、今回はより高速なLruReduxを紹介します。

https://github.com/SamSaffron/lru_redux

LruReduxて何?

自分もあまり詳しくはありませんが、Lru(Least Recently Used)というキャッシュアルゴリズムをRubyで使用するためのライブラリだと認識しています。

https://ja.wikipedia.org/wiki/Least_Recently_Used

ベンチマーク

試しに、ActiveSupport::Cache::MemoryStoreLruReduxのパフォーマンスを比較するためにベンチマークを撮ってみました。

このページのbody要素をそれぞれのキャッシュストアに1000回別のkeyで保存し、取り出すという操作を行ってみました。

require 'net/http'

uri = 'http://api.rubyonrails.org/classes/ActiveSupport/Cache/MemoryStore.html'
body = Net::HTTP.get_response(URI.parse(uri)).body

memory_cache = ActiveSupport::Cache::MemoryStore.new

lru_redux = LruRedux::TTL::ThreadSafeCache.new(1000, 24.hours)


Benchmark.bm 12 do |r|
  r.report 'memory' do
    1000.times do |n|
      memory_cache.write("key-#{n}", body)
    end
    1000.times do |n|
      tmp = memory_cache.fetch("key-#{n}")
    end
  end
  r.report 'lru_redux' do
    1000.times do |n|
      lru_redux["key-#{n}"] = body
    end
    1000.times do |n|
      tmp = lru_redux["key-#{n}"]
    end
  end
end

結果は以下のようになりました。ついでにRedisの結果も含めてあります。

total real
MemoryStore 0.430 0.438
LruRedux 0.020 0.020
Redis 0.620 0.632

通常のMemoryStoreと比較して、LruReduxだと約20倍以上のパフォーマンスが出ていそうです。

Railsでの具体的な実装

色々と方法が考えられると思いますが、自分はconfig/initializersにグローバルなインスタンスとして定義しました。

config/initializers/memory_cache.rb
MemoryStore = LruRedux::TTL::ThreadSafeCache.new(100, 24.hours)

説明するまでもないですがLruRedux::TTL::ThreadSafeCacheは、時間指定ありでなおかつスレッドセーフという意味です。LruReduxにも様々なクラスが存在しますが、基本はこれを使えば間違いないと思われます。第一引数の100は最大保持数(keyの)で、24.hourはデフォルトの保持時間です。

さらに、通常のFragmentCacheと同様の感覚で使えるように、ビューヘルパーを定義しました。

app/helpers/application_helper.rb
def memory_cache(key, &block)
  MemoryStore[key] ||= capture(&block)
end

# ---- 使用例 ----
# = memory_cache 'key' do
#   = render 'partical_name'

まとめ

ここまでで説明したようにLruReduxは非常に高速ですが、通常ここまでのパフォーマンスは必要とされません。また、ホストマシンのメモリを消費するので、あまり多用するわけにはいきませんし、複数台で稼働している場合は扱いが難しくなりがです。本番で使う場合は注意が必要です。

最後になりますが、PORT株式会社では自社サービスを支えてくれる優秀なRubyエンジニアを募集しています(Rubyエンジニア以外も)。
もくもく会も行なっていますので、ぜひ一緒にもくもくしましょう!
PORTもくもく会

EastResident
webの会社でアドテクエンジニア?やってます。しばらくはGCP中心に書く予定。
https://eastresident.hatenablog.com
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away