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: Ability for client to send a command to specific cluster node(s) or all cluster nodes. #986

Open
charlez opened this issue May 14, 2021 · 5 comments

Comments

@charlez
Copy link

charlez commented May 14, 2021

RE: Cluster Mode

  • Use case: App wants to know the commands and resultant load per cluster node (e.g. 'info [section]' where section might be 'stats', 'commandstats', 'memory', 'cpu', etc).
  • Request the ability for a client to send a command to a specific cluster node (e.g. supply the node id), or a means to specify a list or designate all cluster nodes (and perhaps have the response be a Hash of key-value pairs (e.g. key: node id, value: node specific response).
  • FYI, FWIW: Command 'keys' apparently applies to all cluster nodes. Thought it odd that it behaves differently than other commands. Note that I'm not complaining about such behavior.
@charlez charlez changed the title Cluster Mode: Ability for client to send a command to a specific cluster node or all cluster nodes. Cluster Mode: Ability for client to send a command to a specific cluster node(s) or all cluster nodes. May 14, 2021
@charlez charlez changed the title Cluster Mode: Ability for client to send a command to a specific cluster node(s) or all cluster nodes. Cluster Mode: Ability for client to send a command to specific cluster node(s) or all cluster nodes. May 14, 2021
@supercaracal
Copy link
Contributor

Hello,

Would you tell us what is the purpose of the use case? Is it for monitoring or load balancing?

@charlez
Copy link
Author

charlez commented May 15, 2021

@supercaracal - Yes, as you surmised, the use case is for cluster monitoring and load balancing.

  • The specific use case is for monitoring streams throughput, resource utilization/consumption, and load balancing streams/slots amongst multiple clusters' nodes.
  • FYI, FWIW: There are multiple Redis clusters, each with multiple nodes, that must work in concert to accommodate streaming millions of packets per second. Thus cluster node (and slot) monitoring and management are critical to ensure streams are properly allocated (to nodes that can accommodate streams' throughput requirements). Monitoring & Management logic is cluster aware, node aware, and stream aware - and must be able to dynamically monitor-allocate-reallocate clusters' resources.

@supercaracal
Copy link
Contributor

You can extract all node information with workaround like this:

r = Redis.new cluster: ['redis://127.0.0.1:7000']
r._client.instance_variable_get(:@node).call_all(%w[info]).map { |reply| Redis::HashifyInfo.call(reply) }

It needs more parsing steps if you specify commandstats as section of the command.

redis-rb/lib/redis.rb

Lines 311 to 317 in fa76a26

if cmd && cmd.to_s == "commandstats"
# Extract nested hashes for INFO COMMANDSTATS
reply = Hash[reply.map do |k, v|
v = v.split(",").map { |e| e.split("=") }
[k[/^cmdstat_(.*)$/, 1], Hash[v]]
end]
end

The Gem certainly sends the command to a random node when using cluster mode. That behavior might be pointless.

def send_command(command, &block)
cmd = command.first.to_s.downcase
case cmd
when 'acl', 'auth', 'bgrewriteaof', 'bgsave', 'quit', 'save'
@node.call_all(command, &block).first
when 'flushall', 'flushdb'
@node.call_master(command, &block).first
when 'wait' then @node.call_master(command, &block).reduce(:+)
when 'keys' then @node.call_slave(command, &block).flatten.sort
when 'dbsize' then @node.call_slave(command, &block).reduce(:+)
when 'lastsave' then @node.call_all(command, &block).sort
when 'role' then @node.call_all(command, &block)
when 'config' then send_config_command(command, &block)
when 'client' then send_client_command(command, &block)
when 'cluster' then send_cluster_command(command, &block)
when 'readonly', 'readwrite', 'shutdown'
raise OrchestrationCommandNotSupported, cmd
when 'memory' then send_memory_command(command, &block)
when 'script' then send_script_command(command, &block)
when 'pubsub' then send_pubsub_command(command, &block)
when 'discard', 'exec', 'multi', 'unwatch'
raise AmbiguousNodeError, cmd
else
node = assign_node(command)
try_send(node, :call, command, &block)
end
end

It seems that Redis Cluster Proxy doesn't support the command.
https://github.com/RedisLabs/redis-cluster-proxy/blob/unstable/COMMANDS.md#unsupported-commands

Basically, keys and nodes go hand in hand when servers are cluster mode and clients send commands including keys. I would say that the Gem need not dare to support of exposing interfaces each node.

@charlez
Copy link
Author

charlez commented May 17, 2021

@supercaracal - Thanks for the feedback and work-around suggestions.

Following is an approach I believe should work to send commands to individual cluster nodes (in addition to your #call_all suggestion to call all cluster nodes). Please let me know your thoughts as to the viability, thanks:

  • cluster_clients = redis_client._client.instance_variable_get(:@node).instance_variable_get(:@Clients)
  • cluster_nodes_keys = cluster_clients.keys
  • Imagine logic that takes 'cluster nodes' response 'ip_port' and 'slots' makes an association to a given cluster_nodes_keys element (e.g. '10.60.62.19:30001').
  • Then I should be able to call a specific cluster node directly, for example:
    • cluster_clients['10.60.62.19:30001'].call(['info', 'commandstats'])
  • It seems to work fine. Any caveats or problems regarding such an approach?

@supercaracal
Copy link
Contributor

It seems that there are no problems except one of the first step:

s/@Clients/@clients/

@clients = build_clients(options)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants