1. EastResident

    Posted

    EastResident
Changes in title
+Rubyで使える超高速なMemory Cache 「LruRedux」について
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,101 @@
+
+この記事は[PORT Advent Calendar](https://qiita.com/advent-calendar/2017/port)7日目の穴埋めです。
+
+Ruby on Railsの高速化戦略として最もポピュラーなのは、Fragment CacheやローレベルCacheの活用だと思います。私の所属しているチームが運用している[キャリアパーク!](https://careerpark.jp)でも、多くのページでこれらのキャッシュが使用されています。
+
+# キャッシュストアへのアクセス遅延
+
+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::MemoryStore`と`LruRedux`のパフォーマンスを比較するためにベンチマークを撮ってみました。
+
+[このページ](http://api.rubyonrails.org/classes/ActiveSupport/Cache/MemoryStore.html)のbody要素をそれぞれのキャッシュストアに1000回別のkeyで保存し、取り出すという操作を行ってみました。
+
+```rb
+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`にグローバルなインスタンスとして定義しました。
+
+```rb:config/initializers/memory_cache.rb
+MemoryStore = LruRedux::TTL::ThreadSafeCache.new(100, 24.hours)
+```
+
+説明するまでもないですが`LruRedux::TTL::ThreadSafeCache`は、時間指定ありでなおかつスレッドセーフという意味です。`LruRedux`にも様々なクラスが存在しますが、基本はこれを使えば間違いないと思われます。第一引数の`100`は最大保持数(keyの)で、`24.hour`はデフォルトの保持時間です。
+
+さらに、通常のFragmentCacheと同様の感覚で使えるように、ビューヘルパーを定義しました。
+
+```rb: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エンジニアを募集しています](https://www.wantedly.com/companies/port/projects)(Rubyエンジニア以外も)。
+もくもく会も行なっていますので、ぜひ一緒にもくもくしましょう!
+[PORTもくもく会](https://freestyle-mokumoku.connpass.com/)