Skip to content

Commit 22099af

Browse files
Make SLOW_COMMAND_TIMEOUT a configuration option (#292)
1 parent dcfd72c commit 22099af

File tree

6 files changed

+32
-12
lines changed

6 files changed

+32
-12
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ gem 'redis-cluster-client'
2626
| `:replica` | Boolean | `false` | `true` if client should use scale read feature |
2727
| `:replica_affinity` | Symbol or String | `:random` | scale reading strategy, `:random`, `random_with_primary` or `:latency` are valid |
2828
| `:fixed_hostname` | String | `nil` | required if client should connect to single endpoint with SSL |
29+
| `:slow_command_timeout` | Integer | `-1` | timeout used for "slow" queries that fetch metdata e.g. CLUSTER NODES, COMMAND |
2930
| `:concurrency` | Hash | `{ model: :on_demand, size: 5}` | concurrency settings, `:on_demand`, `:pooled` and `:none` are valid models, size is a max number of workers, `:none` model is no concurrency, Please choose the one suited your environment if needed. |
3031

3132
Also, [the other generic options](https://github.com/redis-rb/redis-client#configuration) can be passed.
@@ -90,6 +91,11 @@ RedisClient.cluster(nodes: 'rediss://endpoint.example.com:6379').new_client
9091
RedisClient.cluster(nodes: 'rediss://endpoint.example.com:6379', fixed_hostname: 'endpoint.example.com').new_client
9192
```
9293

94+
```ruby
95+
# To specify a timeout for "slow" commands (CLUSTER NODES, COMMAND)
96+
RedisClient.cluster(slow_command_timeout: 4).new_client
97+
```
98+
9399
```ruby
94100
# To specify concurrency settings
95101
RedisClient.cluster(concurrency: { model: :on_demand, size: 6 }).new_client

lib/redis_client/cluster/command.rb

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
class RedisClient
88
class Cluster
99
class Command
10-
SLOW_COMMAND_TIMEOUT = Float(ENV.fetch('REDIS_CLIENT_SLOW_COMMAND_TIMEOUT', -1))
11-
1210
EMPTY_STRING = ''
1311
LEFT_BRACKET = '{'
1412
RIGHT_BRACKET = '}'
@@ -23,12 +21,12 @@ class Command
2321
)
2422

2523
class << self
26-
def load(nodes)
24+
def load(nodes, slow_command_timeout: -1)
2725
cmd = errors = nil
2826

2927
nodes&.each do |node|
3028
regular_timeout = node.read_timeout
31-
node.read_timeout = SLOW_COMMAND_TIMEOUT > 0.0 ? SLOW_COMMAND_TIMEOUT : regular_timeout
29+
node.read_timeout = slow_command_timeout > 0.0 ? slow_command_timeout : regular_timeout
3230
reply = node.call('COMMAND')
3331
node.read_timeout = regular_timeout
3432
commands = parse_command_reply(reply)

lib/redis_client/cluster/node.rb

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,6 @@ class Node
1616
# It affects to strike a balance between load and stability in initialization or changed states.
1717
MAX_STARTUP_SAMPLE = Integer(ENV.fetch('REDIS_CLIENT_MAX_STARTUP_SAMPLE', 3))
1818

19-
# It's used with slow queries of fetching meta data like CLUSTER NODES, COMMAND and so on.
20-
SLOW_COMMAND_TIMEOUT = Float(ENV.fetch('REDIS_CLIENT_SLOW_COMMAND_TIMEOUT', -1))
21-
2219
# less memory consumption, but slow
2320
USE_CHAR_ARRAY_SLOT = Integer(ENV.fetch('REDIS_CLIENT_USE_CHAR_ARRAY_SLOT', 1)) == 1
2421

@@ -96,7 +93,7 @@ def build_connection_prelude
9693
end
9794

9895
class << self
99-
def load_info(options, concurrent_worker, **kwargs) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
96+
def load_info(options, concurrent_worker, slow_command_timeout: -1, **kwargs) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
10097
raise ::RedisClient::Cluster::InitialSetupError, [] if options.nil? || options.empty?
10198

10299
startup_size = options.size > MAX_STARTUP_SAMPLE ? MAX_STARTUP_SAMPLE : options.size
@@ -107,7 +104,7 @@ def load_info(options, concurrent_worker, **kwargs) # rubocop:disable Metrics/Ab
107104
startup_nodes.each_with_index do |raw_client, i|
108105
work_group.push(i, raw_client) do |client|
109106
regular_timeout = client.read_timeout
110-
client.read_timeout = SLOW_COMMAND_TIMEOUT > 0.0 ? SLOW_COMMAND_TIMEOUT : regular_timeout
107+
client.read_timeout = slow_command_timeout > 0.0 ? slow_command_timeout : regular_timeout
111108
reply = client.call('CLUSTER', 'NODES')
112109
client.read_timeout = regular_timeout
113110
parse_cluster_node_reply(reply)

lib/redis_client/cluster/router.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def initialize(config, concurrent_worker, pool: nil, **kwargs)
2222
@pool = pool
2323
@client_kwargs = kwargs
2424
@node = fetch_cluster_info(@config, @concurrent_worker, pool: @pool, **@client_kwargs)
25-
@command = ::RedisClient::Cluster::Command.load(@node.replica_clients.shuffle)
25+
@command = ::RedisClient::Cluster::Command.load(@node.replica_clients.shuffle, slow_command_timeout: config.slow_command_timeout)
2626
@mutex = Mutex.new
2727
@command_builder = @config.command_builder
2828
end
@@ -305,7 +305,7 @@ def send_pubsub_command(method, command, args, &block) # rubocop:disable Metrics
305305
end
306306

307307
def fetch_cluster_info(config, concurrent_worker, pool: nil, **kwargs)
308-
node_info_list = ::RedisClient::Cluster::Node.load_info(config.per_node_key, concurrent_worker, **kwargs)
308+
node_info_list = ::RedisClient::Cluster::Node.load_info(config.per_node_key, concurrent_worker, slow_command_timeout: config.slow_command_timeout, **kwargs)
309309
node_addrs = node_info_list.map { |i| ::RedisClient::Cluster::NodeKey.hashify(i.node_key) }
310310
config.update_node(node_addrs)
311311
::RedisClient::Cluster::Node.new(

lib/redis_client/cluster_config.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@ class ClusterConfig
1818
MERGE_CONFIG_KEYS = %i[ssl username password].freeze
1919
IGNORE_GENERIC_CONFIG_KEYS = %i[url host port path].freeze
2020
MAX_WORKERS = Integer(ENV.fetch('REDIS_CLIENT_MAX_THREADS', 5))
21+
# It's used with slow queries of fetching meta data like CLUSTER NODES, COMMAND and so on.
22+
SLOW_COMMAND_TIMEOUT = Float(ENV.fetch('REDIS_CLIENT_SLOW_COMMAND_TIMEOUT', -1))
2123

2224
InvalidClientConfigError = Class.new(::RedisClient::Error)
2325

24-
attr_reader :command_builder, :client_config, :replica_affinity
26+
attr_reader :command_builder, :client_config, :replica_affinity, :slow_command_timeout
2527

2628
def initialize(
2729
nodes: DEFAULT_NODES,
@@ -30,6 +32,7 @@ def initialize(
3032
fixed_hostname: '',
3133
concurrency: nil,
3234
client_implementation: ::RedisClient::Cluster, # for redis gem
35+
slow_command_timeout: SLOW_COMMAND_TIMEOUT,
3336
**client_config
3437
)
3538

@@ -42,6 +45,7 @@ def initialize(
4245
@client_config = merge_generic_config(client_config, @node_configs)
4346
@concurrency = merge_concurrency_option(concurrency)
4447
@client_implementation = client_implementation
48+
@slow_command_timeout = slow_command_timeout
4549
@mutex = Mutex.new
4650
end
4751

@@ -53,6 +57,7 @@ def dup
5357
fixed_hostname: @fixed_hostname,
5458
concurrency: @concurrency,
5559
client_implementation: @client_implementation,
60+
slow_command_timeout: @slow_command_timeout,
5661
**@client_config
5762
)
5863
end

test/redis_client/cluster/test_command.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,20 @@ def test_load
3131
end
3232
end
3333

34+
def test_load_slow_timeout
35+
nodes = @raw_clients
36+
assert_equal(TEST_TIMEOUT_SEC, nodes.first.read_timeout)
37+
nodes.first.singleton_class.prepend(Module.new do
38+
def call(...)
39+
@slow_timeout = read_timeout
40+
super
41+
end
42+
end)
43+
::RedisClient::Cluster::Command.load(nodes, slow_command_timeout: 9)
44+
assert_equal(9, nodes.first.instance_variable_get(:@slow_timeout))
45+
assert_equal(TEST_TIMEOUT_SEC, nodes.first.read_timeout)
46+
end
47+
3448
def test_parse_command_reply
3549
[
3650
{

0 commit comments

Comments
 (0)