この記事はこちらのブログ記事の再掲です。
TL;DR
- Railsの
ActiveSupport::Cache::MemCacheStore
からMemcachedに書き込んだデータを他から使うのはちょっと難しそう - 逆にMemcachedに入ってるRAWなデータをRails側で使うのは問題なさそう
- キーの長さ(250バイト制限)を気にしなくても大丈夫
RailsからMemcachedと言ってもconfig.cache_store = :mem_cache_store
的な話ではなく、任意のデータをMemcachedに突っ込んだら何がどうなってるのかを調べてみた感じのものです。
まずはMemcacheにアクセスするためのインスタンスを作ります。
[7] pry(main)> a = ActiveSupport::Cache::MemCacheStore.new('localhost:11211', expires_in: 60, namespace: 'NS_ABC', compress: true)
=> #<ActiveSupport::Cache::MemCacheStore:0x00007fc807dba2a0
@data=#<Dalli::Client:0x00007fc807db9e40 @options={}, @ring=nil, @servers=["localhost:11211"]>,
@options={:namespace=>"NS_ABC", :expires_in=>60, :compress=>true}>
かなり恣意的にnamespace
とcompress
を指定していますが後ほど触れていきます。
さて、データをキャッシュさせたり取り出したりするのはものすごくカンタンです。
[9] pry(main)> a.write('key', 'val')
=> 5044031582654955520
[13] pry(main)> a.read('key')
=> "val"
存在しないキーで参照した場合はnil
が返ります。
[8] pry(main)> a.read('abc')
=> nil
さて、基礎的なお話はこれくらいにして、じゃあ実際にMemcached側にどんな感じでデータが入ってるのか、Memcachedに直接繋いで確認してみることにします。
[root@localhost ~]# telnet localhost 11211
Trying ::1...
Connected to localhost.
Escape character is '^]'.
はい、これだけのためにわざわざtelnetクライアントをyumでインストールしました。
イマドキはtelnetコマンドが入っていないのがフツーかもしれません。
stats items
STAT items:1:number 1
STAT items:1:age 232
STAT items:1:evicted 0
STAT items:1:evicted_nonzero 0
:
stats cachedump 4 1
ITEM NS_ABC:key [114 b; 1545367943 s]
END
はい、やっと見つけました。
どうやら実際に指定したキーの前にnamespace
が自動的に追加されるようです。
せっかくなので値も取得してみましょうか。
get NS_ABC:key
VALUE NS_ABC:key 1 114
o: ActiveSupport::Cache::Entry :
@version0:@created_atf1545877723.9305558:@expires_in6e1
END
おっと。
良く分からないものが表示されました。
が、最初のo:
はオブジェクト、その後に型(クラス)があって、@expires_in
の6e1
は60の指数表記でしょう。
つまりActiveSupport::Cache::Entry
にデータを詰め込んでいい感じのシリアライズをすると同じようなものが作れそうです。
[29] pry(main)> b = ActiveSupport::Cache::Entry.new('val', expires_in: 60)
=> #<ActiveSupport::Cache::Entry:0x00007fc8072b3730 @created_at=1545878427.2002478, @expires_in=60.0, @value="val", @version=nil>
あ、なんかそれっぽいのができました。
シリアライズはどうやらMarshal
というクラスを使うのがスタンダードっぽいのでとりあえず試してみます。
[32] pry(main)> Marshal.dump(b)
=> "\x04\bo: ActiveSupport::Cache::Entry\t:\v@valueI\"\bval\x06:\x06ET:\r@version0:\x10@created_atf\x171545878427.2002478:\x10@expires_inf\b6e1"
あ、概ね同じに見えますね。
ちょっとそのまま表示してみましょうか。
[33] pry(main)> puts Marshal.dump(b)
o: ActiveSupport::Cache::Entry :
@version0:@created_atf1545878427.2002478:@expires_in6e1
=> nil
ビンゴですね。
ビンゴなのはいいんですが、ActiveSupport::Cache::MemCacheStore
からMemcachedに入れたデータを他で利用するのはちょっと難易度が高そうです。
ところで逆はどうでしょう?
Memcachedに直接データを入れてRails側で参照してみます。
set NS_ABC:hoge 0 60 8
hogehoge
STORED
[36] pry(main)> a.read('hoge')
=> "hogehoge"
はい、取得できました。
今度はMemcached側でデータを圧縮しつつ入れてみます。
set NS_ABC:hoge 1 60 8
hogehoge
STORED
[40] pry(main)> a.read('hoge')
=> nil
あらら、これはダメなようです。
ActiveSupport::Cache::MemCacheStore
のcompress
はここでは関係がないようです。
compress
にtrue
を指定した時は、データ容量が特定の閾値(compress_threshold
で指定も可能)を超えた際に圧縮するという設定のようです。
やってみましょう。
[68] pry(main)> a.write('key', 'val' * 1000)
=> 72057594037927936
get NS_ABC:key
VALUE NS_ABC:key 3 217
.-(�/*��rNL�H��r�+)���v(K�)MU2������fw�]Ԑr]�!һ(��y�q��)
C��+^��Ԣ<+���ĒԔCSS
cCCK=
3
3C��Ԋ�̢��TC+Q�������&��Y8<��vpQ4�F�(E�h��A�ج�\C@��5%��0
�END
あばばばばばばば。
良くは分かりませんが圧縮されてそうです。
きっとActiveSupport::Cache::Entry
をシリアライズしたものを圧縮してるんでしょう、という予測を立ててみます。
圧縮と言えばみんな大好きDeflate
でしょう。
やってみます。
[147] pry(main)> puts Zlib::Deflate.deflate( Marshal.dump( ActiveSupport::Cache::Entry.new('val' * 1000, expires_in: 60) ) )
.-(�/*��rNL�H��r�+)���v(K�)MU2������fw�]Ԑr]�!һ(��y�q��)
C��+^��Ԣ<+���ĒԔCSS
#
=CKS3��Ԋ�̢��TC+Q�������&��Y8<��vpQ4�F�(E�h��A�ج�\C@��5%A�0
�=> nil
あばばばばばばばばばばば。
制御コードまみれなので全く同じ表示にはなりませんがきっとこれで合ってると思います(ぉぃ
さて、Memcachedと言えばキーに250文字制限があるのですがその辺りもちょっと確認しておきましょうか。
namespace
の指定がある場合
[45] pry(main)> a.write('1234567890' * 30, 'val')
=> 5692549928996306944
stats cachedump 8 1
ITEM NS_ABC:12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456:md5:116b331c40362c11c387c7bf0eaf8295 [114 b; 1545369320 s]
END
namespace
の指定がない場合
[52] pry(main)> a.write('1234567890' * 30, 'val')
=> 5764607523034234880
stats cachedump 8 1
ITEM 123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123:md5:874fa981137f49f7885e1d05a93c21df [114 b; 1545369598 s]
END
なるほど、通常通り「namespace
:指定したキー」にしつつ、250文字を超えないように後ろにそれぞれのMD5値
[62] pry(main)> Digest::MD5.hexdigest('NS_ABC:' + '1234567890' * 30)
=> "116b331c40362c11c387c7bf0eaf8295"
[63] pry(main)> Digest::MD5.hexdigest('1234567890' * 30)
=> "874fa981137f49f7885e1d05a93c21df"
を自動で追加してくれるんですね。
これは便利。