Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cluster mode #62

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
.bundle/
vendor/
# Remove if using rbenv
.ruby-version
5 changes: 4 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# frozen_string_literal: true

source 'https://rubygems.org'

gem 'redis'
gem 'hiredis'
gem 'redis'
gem 'redis-clustering'
10 changes: 7 additions & 3 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
GEM
remote: https://rubygems.org/
specs:
hiredis (0.6.0)
redis (3.2.2)
connection_pool (2.4.1)
hiredis (0.6.3)
redis (5.1.0)
redis-client (>= 0.17.0)
redis-client (0.21.0)
connection_pool

PLATFORMS
ruby
Expand All @@ -12,4 +16,4 @@ DEPENDENCIES
redis

BUNDLED WITH
1.11.2
2.4.19
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This script samples a number of the Redis keys in a database and then groups them with other *similar* looking keys. It then displays key
metrics around those groups of keys to help you spot where efficiencies can be made in the memory usage of your Redis database.
_Warning_: The script cannot be used with AWS Elasticache Redis instances, as the debug command is restricted.
_Warning_: The script cannot provide idle time on AWS Elasticache Redis instances, as the debug command is restricted. Use the memory flag instead.

## Installation
`bundle install` will take care of everything!
Expand All @@ -24,7 +24,7 @@ The legacy option looks like this:

You can also specify the arguments with declarations, which also adds the ability to use a Redis URL and pass in authentication credentials:

redis-audit.rb -h/--host [host] -p/--port [port] -a/--password [password] -d/--dbnum [dbnum] -s/--sample [(optional)sample_size]
redis-audit.rb -h/--host [host] -p/--port [port] -a/--password [password] -d/--dbnum [dbnum] -s/--sample [(optional)sample_size] -c/--cluster (optional) -m/--memory (optional)

or

Expand All @@ -39,6 +39,8 @@ will enable you to see that keys are being grouped properly. If you omit this pa
greater than the number of keys in the database the script will walk all the keys in the Redis database. **DO NOT** run this with a lot of keys on
a production master database. Keys * will block for a long time and cause timeouts!
- **Url**: Follows the normal syntax for Redis Urls
- **Cluster Mode**: Connect to a cluster endpoint
- **Memory Flag**: Use the memory command for key sizing. Idle time will not be available if this flag is used.

`redis-audit.rb --help` will print out the argument options as well.

Expand Down
67 changes: 54 additions & 13 deletions redis-audit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@

require 'bundler/setup'
require 'redis'
require 'redis/connection/hiredis'
require 'redis-clustering'
require 'hiredis'
require 'optparse'

# Container class for stats around a key group
Expand Down Expand Up @@ -76,11 +77,12 @@ class RedisAudit
# Configure regular expressions here if you need to guarantee that certain keys are grouped together
@@key_group_regex_list = []

def initialize(redis, sample_size)
def initialize(redis, sample_size, use_memory)
@redis = redis
@keys = Hash.new {|h,k| h[k] = KeyStats.new}
@sample_size = sample_size
@dbsize = 0
@use_memory = use_memory
end

def audit_keys
Expand Down Expand Up @@ -117,14 +119,23 @@ def audit_keys
end

def audit_key(key)
pipeline = @redis.pipelined do
@redis.debug("object", key)
@redis.type(key)
@redis.ttl(key)
pipeline = @redis.pipelined {|p|
if @use_memory
p.memory("usage", key)
else
p.debug("object", key)
end
p.type(key)
p.ttl(key)
}
if @use_memory
serialized_length = pipeline[0].nil? ? 0 : pipeline[0]
idle_time = 1
else
debug_fields = @@debug_regex.match(pipeline[0])
serialized_length = debug_fields[1].to_i
idle_time = debug_fields[2].to_i
end
debug_fields = @@debug_regex.match(pipeline[0])
serialized_length = debug_fields[1].to_i
idle_time = debug_fields[2].to_i
type = pipeline[1]
ttl = pipeline[2] == -1 ? nil : pipeline[2]
@keys[group_key(key, type)].add_stats_for_key(key, type, idle_time, serialized_length, ttl)
Expand Down Expand Up @@ -301,6 +312,15 @@ def make_proportion_percentage(value)
options[:sample_size] = sample_size.to_i
end

# Note: Cluster mode and memory flag not present in backwards compatible command line
opts.on("-c", "--cluster", "Cluster Mode") do |cluster|
options[:cluster] = cluster
end

opts.on("-m", "--memory", "Memory Flag") do |mem|
options[:memory] = mem
end

opts.on('--help', 'Displays Help') do
puts opts
exit
Expand All @@ -323,7 +343,11 @@ def make_proportion_percentage(value)

# create our connection to the redis db
if !options[:url].nil?
redis = Redis.new(:url => options[:url])
if options[:cluster].nil?
redis = Redis.new(:url => options[:url])
else
redis = Redis::Cluster.new(nodes: [url], replica: true)
end
else
# with url empty, assume that --host has been set, but since we don't enforce
# port or dbnum to be set, allow sane defaults
Expand All @@ -335,11 +359,22 @@ def make_proportion_percentage(value)
if options[:dbnum].nil?
options[:dbnum] = 0
end
# Build a url for use with connecting in cluster mode
url = "redis://#{options[:host]}:#{options[:port]}/#{options[:dbnum]}"
# don't pass the password argument unless it is set
if options[:password].nil?
redis = Redis.new(:host => options[:host], :port => options[:port], :db => options[:dbnum])
if options[:cluster].nil?
redis = Redis.new(:host => options[:host], :port => options[:port], :db => options[:dbnum])
else
redis = Redis::Cluster.new(nodes: [url], replica: true)
end
else
redis = Redis.new(:host => options[:host], :port => options[:port], :password => options[:password], :db => options[:dbnum])
if options[:cluster].nil?
redis = Redis.new(:host => options[:host], :port => options[:port], :password => options[:password], :db => options[:dbnum])
else
puts "Password not supported with cluster option"
exit 1
end
end
end

Expand All @@ -348,8 +383,14 @@ def make_proportion_percentage(value)
options[:sample_size] = 0
end

# Use the Redis debug command by default
# TODO: We could try a debug call, and if it fails fall back to memory command
if options[:memory].nil?
options[:memory] = false
end

# audit our data
auditor = RedisAudit.new(redis, options[:sample_size])
auditor = RedisAudit.new(redis, options[:sample_size], options[:memory])
if !options[:url].nil?
puts "Auditing #{options[:url]} sampling #{options[:sample_size]} keys"
else
Expand Down