memoistとRailsキャッシュのどちらがパフォーマンスが良いか調べてみた
はじめに
Railsでマスター系のDBで何度も同じ内容を叩いたり、N+1問題対策でincludesをつけていても、結局
デカイSQLを発行してしまい効率的ではない場合もある
そこでメモリに保存して再利用する方法memoistを使っては見たものの、普通のRailsについてる機能のLow-Level Cachingを使ってもそんなにパフォーマンスが変わらないんじゃないか?と言う疑問をがふつふつと湧いたので計測して、運用しているサービスでどちらを採用するか決めた
※Railsのキャッシュはmemcacheを使用
検証環境
- OS: MacOSX 10.10
- Rails version: 4.2
- memoist version: 0.12.0
サンプルコード
class SampleCode < ActiveRecord::Base
#キャッシュされるSampleCodeモデルのコード群
@@cached_codes = [AAA, BBB, CCC, DDD, EEE].map!(&:freeze).freeze
# クラスメソッドのメモ化
class << self
extend Memoist
def plan_ids
SampleCode(code: @@cached_codes).pluck(:id)
end
memoize :plan_ids
end
# クラスメソッドで、Low-Level Cachingを利用
def self.plan_ids_cache
Rails.cache.fetch("plan_ids_cache_key", expires_in: 1.day) do
SampleCode(code: @@cached_codes).pluck(:id)
end
end
end
計測
Rails consoleを起動して下記にある検証コードをコピペして実行する
> bin/rails console
5つの整数を格納した配列を取得するメソッドが1000回
memoistを使う場合とLow-Level Cachingを使う場合、最後に普通にActiveRecordでデータを取ってくる場合だ
(ベンチマークの試行回数の書き方が間違ってたので修正してあります)
# 開実行する
require "benchmark"
#試行回数
num = 1000
plan_code = [AAA, BBB, CCC, DDD, EEE].map!(&:freeze).freeze
report = Benchmark.bm do |r|
r.report "memo" do
num.times { SampleCode.plan_ids }
end
r.report "cache" do
num.times { CampaignCode.plan_ids_cache }
end
# ActiveRecordで素でとった場合
r.report "ar original" do
num.times { SampleCode.where(code: plan_code).pluck(:id) }
end
end
# ローカル環境実行結果(memcachedもローカルサーバで動いている)
=>
user system total real
memo 0.0200 0.0100 0.0300 (0.0332)
cache 0.3299 0.2200 0.5499 (1.1794)
ar original 1.6600 0.2500 1.9100 (4.5657)
=>
# Staging環境実行結果(サーバはHeroku, memcachedはアドオン動いている)
user system total real
memo 0.0099 0.0 0.0099 (0.1472)
cache 0.5000 0.1499 0.6499 (3.6802)
ar original 1.1500 0.1699 1.3200 (3.3467)
考察
memoistを使った場合圧倒的に早いLow-Level Cachingを用いた場合キャッシュもActiveRecordを直接取ってくる場合よりよりもローカル環境では5倍以上倍早く、stagingでは2倍以上早い。
Staging環境でのテストでもmemcachedが別サーバにあるでのオーバヘッドがあるにも関わらず予想外に好成績だった(むしろHerokuが予想外に早くてびっくりした)
memoistの場合はサーバのメモリに保存するがLow-Level Cachingの場合はmemcacheにキャッシュした値を委譲できるので、少メモリと言う観点ではLow-Level Cachingが優位性がある
結論
使いドコロによってmemoistとLow-Level Cachingを使い分けることにした
- memoist はバッチ処理など一気に大量のデータを処理する場合に用いる
- Low-Level Cachingはユーザがよく参照する画面で必要な情報をキャッシュする