12
8

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 5 years have passed since last update.

PORTAdvent Calendar 2017

Day 7

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

Posted at

この記事は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を紹介します。

LruReduxて何?

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

ベンチマーク

試しに、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もくもく会

12
8
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
12
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?