Last active
January 7, 2022 16:02
-
-
Save alexnavis/2665e05715f4355ec55bebd623d8ca8f to your computer and use it in GitHub Desktop.
Redis Big Keys, Dump Debug Info with Humanized Size Information to Export CSV
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Intention to dump from live redis. Use rdb-tools to dump from a file. | |
# To know the usage run: >> ruby redis_debug.rb | |
require 'redis' # needs redis gem to work | |
require 'csv' | |
class RedisDebug | |
DEFAULT_OPTS = { redis: {host: 'localhost', port: '6379', db: 0, timeout: 100}, | |
batch_size: 2000, | |
dry_run: false, | |
filter_top: nil, | |
report_dir: 'log', | |
file_prefix: 'redis_keys_size' | |
} | |
def self.get_info(opts = {}) | |
opts = DEFAULT_OPTS.merge(opts) | |
redis = Redis.new(opts[:redis]) | |
all_keys = redis.keys '*' | |
all_keys = opts[:dry_run] ? all_keys.take(10) : all_keys | |
puts "Total keys: #{all_keys.size}, fetching keys in batch of size '#{opts[:batch_size]}' => #{all_keys.size/opts[:batch_size]} batches" | |
results = all_keys.each_slice(opts[:batch_size]).reduce([]) do |acc, slice| | |
results = self.debug(redis, slice) | |
acc.concat(results) | |
putc ".#{acc.count}." | |
acc | |
end | |
sorted_results = results.sort_by { |debug_info| -debug_info.size }.take(opts[:filter_top] || results.count) | |
puts "\nCompleted results count: #{sorted_results.count}" | |
sorted_results | |
end | |
def self.print_info(opts = {}) | |
opts = DEFAULT_OPTS.merge(opts) | |
results = get_info opts | |
file_name = "#{opts[:report_dir]}/#{opts[:file_prefix]}#{Time.now.strftime('%Y-%m-%d_%H_%M_%S')}.csv" | |
CSV.open(file_name, 'wb') do |csv| | |
csv << ['key', 'humanized size', 'bytes', 'encoding', 'refcount', 'lru', 'lru_seconds_idle', 'address'] | |
results.each do |info| | |
csv << [info.key, info.humanize_size, info.size, info.encoding, info.refcount, info.lru, info.lru_seconds_idle, info.address] | |
end | |
end | |
puts "Regenerated report in : #{File.absolute_path(file_name)}" | |
end | |
def self.debug(redis, keys) | |
return {} if keys.nil? | |
results = redis.pipelined do | |
keys.each { |key| redis.debug(:object, key) } | |
end | |
keys.zip(results).map do |(key, value)| | |
value = value.nil? ? {} : value.split(' ').drop(1).map { |item| item.split(':') }.to_h | |
DebugInfo.new key, convert_integer_values(value) | |
end | |
end | |
def self.convert_integer_values(value) | |
value.map do |k, v| | |
v = v.to_i if v.match(/^\d+$/) | |
[k, v] | |
end.to_h | |
end | |
end | |
class DebugInfo | |
attr_reader :values, :key | |
def initialize(key, values) | |
@key = key | |
@values = values | |
end | |
def size | |
values[:serializedlength.to_s] | |
end | |
def address | |
values[:at.to_s] | |
end | |
def refcount | |
values[:refcount.to_s] | |
end | |
def encoding | |
values[:encoding.to_s] | |
end | |
def lru | |
values[:lru.to_s] | |
end | |
def lru_seconds_idle | |
values[:lru_seconds_idle.to_s] | |
end | |
def humanize_size | |
{ | |
'B' => 1024, | |
'KB' => 1024 * 1024, | |
'MB' => 1024 * 1024 * 1024, | |
'GB' => 1024 * 1024 * 1024 * 1024, | |
'TB' => 1024 * 1024 * 1024 * 1024 * 1024 | |
}.each_pair { |e, s| return "#{(size.to_f / (s / 1024)).round(2)}#{e}" if size < s } | |
end | |
def values | |
@values | |
end | |
end | |
opts = ARGV[0].nil? ? nil : eval(ARGV[0]) | |
if opts.nil? || !opts.is_a?(Hash) | |
puts 'usage >>>>>> ruby lib/redis_debug.rb <ruby hash>. e.g., ruby lib/redis_debug.rb {dry_run: true}' | |
puts "\tall options (with default) >> '#{RedisDebug::DEFAULT_OPTS}'" | |
puts "\tminimal options >> '#{{redis: {port: '1111', dry_run: true}}}'" | |
exit | |
end | |
RedisDebug.print_info(opts) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment