RailsからMemcachedを使いつつその実体を垣間見てみる

RailsからMemcachedを使いつつその実体を垣間見てみる

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}>

かなり恣意的にnamespacecompressを指定していますが後ほど触れていきます。

さて、データをキャッシュさせたり取り出したりするのはものすごくカンタンです。

[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_in6e1は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::MemCacheStorecompressはここでは関係がないようです。
compresstrueを指定した時は、データ容量が特定の閾値(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"

を自動で追加してくれるんですね。
これは便利。

TAG

  • このエントリーをはてなブックマークに追加
やまま
スペシャリスト やまま yamama

マンガとアニメとゲームから錬成された宇宙大好きエンジニア。 軌道エレベーターで行ける静止軌道上のコロニーに住まいを移し、ゲームやってマンガ読んでアニメ見て爆睡、ゲームやってマンガ読んでアニメ見て爆睡、という生活を夢見ながら今日もコードを書き続けるのだった。