Posted at
PORTDay 7

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

More than 1 year has 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もくもく会