rubyでmemcachedのキーの一覧を表示する。また特定データを削除する等。
なんで作ろうと思ったか
C#のmemcachedセッションライブラリがバグってて無期限データ入れやがったー。
そのまま放置して130万件にもなりやがった。
くそー 消したい。flush_allすると全データ消えて既存ユーザーが強制timeout扱いなるからそれは避けたい。
なので無期限データのみ消したいんだー 絶対ー!!
アイテム一覧表示 実行結果
$ util.print_items stats {"pid"=>"16348", "uptime"=>"585047", "time"=>"1361858174", "version"=>"1.4.7", "libevent"=>"1.4.13-stable", "pointer_size"=>"64", "rusage_user"=>"1.210815", "rusage_system"=>"5.016237", "curr_connections"=>"24", "total_connections"=>"1554", "connection_structures"=>"34", "cmd_get"=>"16466", "cmd_set"=>"25559", "cmd_flush"=>"0", "get_hits"=>"16424", "get_misses"=>"42", "delete_misses"=>"0", "delete_hits"=>"4", "incr_misses"=>"0", "incr_hits"=>"0", "decr_misses"=>"0", "decr_hits"=>"0", "cas_misses"=>"0", "cas_hits"=>"0", "cas_badval"=>"0", "auth_cmds"=>"0", "auth_errors"=>"0", "bytes_read"=>"783051598", "bytes_written"=>"396680001", "limit_maxbytes"=>"67108864", "accepting_conns"=>"1", "listen_disabled_num"=>"0", "threads"=>"4", "conn_yields"=>"0", "bytes"=>"111905", "curr_items"=>"7", "total_items"=>"25559", "evictions"=>"0", "reclaimed"=>"27"} stats items {5=> {"number"=>"2", "age"=>"583060", "evicted"=>"0", "evicted_nonzero"=>"0", "evicted_time"=>"0", "outofmemory"=>"0", "tailrepairs"=>"0", "reclaimed"=>"11"}, 6=> {"number"=>"1", "age"=>"582577", "evicted"=>"0", "evicted_nonzero"=>"0", "evicted_time"=>"0", "outofmemory"=>"0", "tailrepairs"=>"0", "reclaimed"=>"14"}, 7=> {"number"=>"1", "age"=>"502636", "evicted"=>"0", "evicted_nonzero"=>"0", "evicted_time"=>"0", "outofmemory"=>"0", "tailrepairs"=>"0", "reclaimed"=>"0"}, 28=> {"number"=>"3", "age"=>"505667", "evicted"=>"0", "evicted_nonzero"=>"0", "evicted_time"=>"0", "outofmemory"=>"0", "tailrepairs"=>"0", "reclaimed"=>"1"}} server_start_time:2013-02-19 20:25:27 +0900 item_number:5 key:foo_development:_session_id:f0df468194448c505e26b99143e5d65c bytes:107 time:2013-02-26 20:55:22 +0900 item_number:5 key:foo_development:_session_id:f7450243f80ca59fbb9ffc18049fedc0 bytes:107 time:2013-02-26 20:23:07 +0900 item_number:6 key:foo_development:_session_id:e2674611e09369f8b5bc62ba95c59b92 bytes:165 time:2013-02-26 20:15:04 +0900 item_number:7 key:hoge_e15atqhotfpm4sbnbm3dwa3g bytes:269 time:2013-02-26 16:02:41 +0900 item_number:28 key:hoge_qmeysujdltvz22s2ahtpmrye bytes:36546 time:2013-02-27 14:55:44 +0900 item_number:28 key:hoge_3nsu2trne4j0vyrfqg202lqe bytes:37387 time:2013-02-26 21:04:37 +0900 item_number:28 key:hoge_lwbr2fcdzx21wgbroczjtghq bytes:36536 time:2013-02-26 16:53:29 +0900 2013-02-26 14:54:50 +0900 infinity_count:0 normal_count:7 expire_count:0
ソース
# coding: utf-8 STDOUT.sync = true require 'rubygems' require 'pp' gem 'memcache_do', '>=0.1.1' gem 'dalli', '>=2.6.2' require 'memcache_do' require 'dalli' class MemcacheStats def initialize(host='localhost', port='11211') @host = host @port = port @dc = Dalli::Client.new("#{host}:#{port}") end def list stats_items_hash.each do |no, item| stats_cachedump(no).lines do |line| next if line.chomp == 'END' hoge, key, bytes, second = line.match(/^ITEM ([^ ]*) \[(\d*) b; (\d*)/).to_a second = second.to_i time = Time.at(second) data = {:item_number => no, :key => key, :bytes => bytes, :time => time} yield(data) end end end def exec(cmd) MemcacheDo.exec(cmd, @host, @port) end def stats exec 'stats' end def stats_hash raw = stats hash = {} raw.lines do |line| # p line line.chomp! res = line.match(/^STAT (\w+) ([^ ]+)/) if res hash[res[1]] = res[2] end end hash end def stats_items exec 'stats items' end def stats_cachedump(item_number) exec "stats cachedump #{item_number} 0" end def stats_items_hash raw = stats_items hash = {} raw.lines do |line| # p line line.chomp! res = line.match(/^STAT items:(\d+):(\w+) ([^ ]+)/) if res key_num = res[1].to_i hash[key_num] = {} unless hash.has_key? key_num hash[key_num][res[2]] = res[3] end end hash end def delete(key) @dc.delete key end def dalli(key) @dc end end if $0 == __FILE__ class MemcacheUtil def initialize(host='localhost') @m = MemcacheStats.new(host) end # アイテム一覧表示 def print_items m = @m infinity_count = 0 normal_count = 0 expire_count = 0 # stats取得 # puts m.stats stats = m.stats_hash puts "stats" pp stats # stats_items取得 #puts m.stats_items stats_items = m.stats_items_hash puts "stats items" pp stats_items # サーバー開始時間を計算 server_start_time = Time.at(stats["time"].to_i - stats["uptime"].to_i) puts "server_start_time:#{server_start_time}" # データ一覧取得 m.list do |data| item_number = data[:item_number] key = data[:key] bytes = data[:bytes] time = data[:time] puts "item_number:#{item_number} key:#{key} bytes:#{bytes} time:#{time}" if time == server_start_time # 無期限キャッシュの有効期限はサーバー開始時間と一緒 infinity_count += 1 elsif time < Time.now # 期限切れデータ。どうもstats cachedumpに期限切れてるのがでてくる expire_count += 1 else normal_count += 1 end if key == "hogehoge" # 簡易データ表示(先頭にヘッダーぽいのついてるのを除去してないです) p m.exec("get #{key}") # rubyのデータなら下記のようにm.dalliでDalliClientのインスタンス返すのでご自由にどぞ。 #m.dalli.get(key) end end puts "#{Time.now} infinity_count:#{infinity_count} normal_count:#{normal_count} expire_count:#{expire_count}" end # 無期限データ削除&有効期限切れデータ削除 def delete_infinity_and_expire_data m = @m puts "#{Time.now} start process" loop do infinity_count = 0 normal_count = 0 expire_count = 0 # stats取得 # puts m.stats stats = m.stats_hash puts "stats" pp stats # stats_items取得 #puts m.stats_items stats_items = m.stats_items_hash puts "stats items" pp stats_items # サーバー開始時間を計算 server_start_time = Time.at(stats["time"].to_i - stats["uptime"].to_i) puts "server_start_time:#{server_start_time}" # データ一覧取得 m.list do |data| item_number = data[:item_number] key = data[:key] bytes = data[:bytes] time = data[:time] puts "item_number:#{item_number} key:#{key} bytes:#{bytes} time:#{time}" if (time == server_start_time) # 無期限キャッシュの有効期限はサーバー開始時間と一緒 infinity_count += 1 m.delete key elsif time < Time.now # 期限切れデータ。どうもstats cachedumpに期限切れてるのがでてくる expire_count += 1 m.delete key else normal_count += 1 end end puts "#{Time.now} infinity_count:#{infinity_count} normal_count:#{normal_count} expire_count:#{expire_count}" # 削除対象が無くなるまで繰り返す。 # stats cachedumpは1MBしか返さない制限あるのでゴミデータは削除しないと消したいデータが出てこない場合がある。なので期限切れデータを消した後もループで再実行している。 if infinity_count == 0 && expire_count == 0 break end end puts "#{Time.now} end process" end end util = MemcacheUtil.new('192.168.xxx.xxx') util.print_items # コメント外して使ってください。 # 無期限データ削除&有効期限切れデータ削除 #util.delete_infinity_and_expire_data end
注意点
stats cachedumpだとキー一覧等のデータが1MBまでしか返ってこないとか。もしアクティブなデータがいっぱい入っている場合は無理そう。
また有効期限切れデータも返ってくるので、今回は有効期限切れデータがあった場合は削除するような処理にした。
参考
http://blog.elijaa.org/index.php?post/2010/12/24/Understanding-Memcached-stats-cachedump-command
http://lzone.de/dump+memcache+keys
そのうちコマンドstats cachedumpが消えるかもとかもいってるみたい。まー微妙な仕様なので新しいのができると期待。