mem_cache_storeはdelete_matchedをサポートしてません。なにか動的に変わるものをキーにIDなどを付与してキャッシュした時、まとめて消すことができず不便だったので、微妙ですが、モンキーパッチ書きました。
コード
module MemCacheStoreWithDeleteMatched
MEM_CACHE_STORE_WITH_DELETE_MATCHED_KEYS = 'MemCacheStoreWithDeleteMatchedKeys'
def delete_matched(matcher, options = nil)
options = merged_options(options)
options[:support_delete_matched] = true
instrument(:delete_matched, matcher.inspect) do
matcher = key_matcher(matcher, options)
key_hash = fetch(MEM_CACHE_STORE_WITH_DELETE_MATCHED_KEYS) { {} }
key_hash.keys.each do |key|
delete_entry(key, **options) if key.match(matcher)
end
end
end
def write_entry(key, entry, **options)
ret = super(key, entry, **options)
return ret unless options[:support_delete_matched]
keys = fetch(MEM_CACHE_STORE_WITH_DELETE_MATCHED_KEYS) { {} }
keys[key] = nil
write(MEM_CACHE_STORE_WITH_DELETE_MATCHED_KEYS, keys, support_delete_matched: false)
ret
end
def delete_entry(key, **options)
ret = super(key, **options)
return ret unless options[:support_delete_matched]
keys = fetch(MEM_CACHE_STORE_WITH_DELETE_MATCHED_KEYS) { {} }
keys.delete(key)
write(MEM_CACHE_STORE_WITH_DELETE_MATCHED_KEYS, keys, support_delete_matched: false)
ret
end
end
module ActiveSupport
module Cache
class MemCacheStore
prepend MemCacheStoreWithDeleteMatched
end
end
end
解説
結構強引ですが、キャッシュの保存時にキーのリストをmemcachedに保存しちゃってます。正確には出し入れ時に検索を早くするためhashでしまってます。
保存・削除時にsupport_delete_matched: true
を渡すとキーが保存されてdelete_matchedで削除できるようになります。
Rails.cache.write(key, 'value', support_delete_matched: true)
Rails.cache.delete(key, support_delete_matched: true)
多分削除はdelete_matchedを使うのでdeleteは使わないと思いますが、deleteを呼ぶなら渡さないとキーにゴミが残ります。expireしたキャッシュのキーはそのままなのでリストに残ります。まあ、expireさせるならsupport_delete_matched: true
にすることもないと思いますが。
ちなみに、envで丸っとONにすることも可能です。
config.cache_store = :mem_cache_store, 'localhost:11211', { support_delete_matched: true }
mem_cache_storeはバックエンドでdaliを使用していますが、本家ではキーリストの書き込みにかかる時間でキャッシュの利点が損なわれるという理由で却下されてます。ただ、キャッシュって読み込み早ければそれでよくない?って気もしますが・・・まあ、使用用途にもよりますか。
確かに書き込みと削除はキャッシュの量が増えるとhashのシリアライズに時間がかかるので遅くなりますね。そこは注意してください。ベンチ載せておきます。
Bench
Benchmark.bm 10 do |r|
r.report "support" do
10000.times do |num|
key = "foobar___________________________#{num}"
Rails.cache.write(key, 'value', support_delete_matched: true)
Rails.cache.delete(key, support_delete_matched: true)
end
end
r.report "unsupport" do
10000.times do |num|
key = "foobar___________________________#{num}"
Rails.cache.write(key, 'value')
Rails.cache.delete(key)
end
end
end
結果
user system total real
support 7.934330 0.920018 8.854348 ( 8.901407)
unsupport 2.024292 0.253793 2.278085 ( 2.287548)
状況によっては、削除時に、DBに接続して存在する可能性のあるキーを全部生成して削除を試みるのもアリですね。そういうこともあったので、オプションでON/OFFできるようにしてみました。