redis-cluster-client 0.7.5 → 0.7.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,7 +13,6 @@ class RedisClient
13
13
  class Cluster
14
14
  class Router
15
15
  ZERO_CURSOR_FOR_SCAN = '0'
16
- METHODS_FOR_BLOCKING_CMD = %i[blocking_call_v blocking_call].freeze
17
16
  TSF = ->(f, x) { f.nil? ? x : f.call(x) }.curry
18
17
 
19
18
  def initialize(config, concurrent_worker, pool: nil, **kwargs)
@@ -23,9 +22,9 @@ class RedisClient
23
22
  @concurrent_worker = concurrent_worker
24
23
  @pool = pool
25
24
  @client_kwargs = kwargs
26
- @node = fetch_cluster_info(@config, @concurrent_worker, pool: @pool, **@client_kwargs)
25
+ @node = ::RedisClient::Cluster::Node.new(concurrent_worker, config: config, pool: pool, **kwargs)
26
+ update_cluster_info!
27
27
  @command = ::RedisClient::Cluster::Command.load(@node.replica_clients.shuffle, slow_command_timeout: config.slow_command_timeout)
28
- @mutex = Mutex.new
29
28
  @command_builder = @config.command_builder
30
29
  end
31
30
 
@@ -66,77 +65,61 @@ class RedisClient
66
65
  raise if e.errors.any?(::RedisClient::CircuitBreaker::OpenCircuitError)
67
66
 
68
67
  update_cluster_info! if e.errors.values.any? do |err|
69
- err.message.start_with?('CLUSTERDOWN Hash slot not served')
68
+ @node.owns_error?(err) && err.message.start_with?('CLUSTERDOWN Hash slot not served')
70
69
  end
71
70
 
72
71
  raise
73
72
  end
74
73
 
75
74
  # @see https://redis.io/docs/reference/cluster-spec/#redirection-and-resharding Redirection and resharding
76
- def try_send(node, method, command, args, retry_count: 3, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
77
- if args.empty?
78
- # prevent memory allocation for variable-length args
79
- node.public_send(method, command, &block)
80
- else
81
- node.public_send(method, *args, command, &block)
75
+ def try_send(node, method, command, args, retry_count: 3, &block)
76
+ handle_redirection(node, retry_count: retry_count) do |on_node|
77
+ if args.empty?
78
+ # prevent memory allocation for variable-length args
79
+ on_node.public_send(method, command, &block)
80
+ else
81
+ on_node.public_send(method, *args, command, &block)
82
+ end
82
83
  end
83
- rescue ::RedisClient::CircuitBreaker::OpenCircuitError
84
- raise
85
- rescue ::RedisClient::CommandError => e
86
- raise if retry_count <= 0
84
+ end
87
85
 
88
- if e.message.start_with?('MOVED')
89
- node = assign_redirection_node(e.message)
90
- retry_count -= 1
91
- retry
92
- elsif e.message.start_with?('ASK')
93
- node = assign_asking_node(e.message)
94
- node.call('ASKING')
95
- retry_count -= 1
96
- retry
97
- elsif e.message.start_with?('CLUSTERDOWN Hash slot not served')
98
- update_cluster_info!
99
- retry_count -= 1
100
- retry
101
- else
102
- raise
86
+ def try_delegate(node, method, *args, retry_count: 3, **kwargs, &block)
87
+ handle_redirection(node, retry_count: retry_count) do |on_node|
88
+ on_node.public_send(method, *args, **kwargs, &block)
103
89
  end
104
- rescue ::RedisClient::ConnectionError => e
105
- raise if METHODS_FOR_BLOCKING_CMD.include?(method) && e.is_a?(RedisClient::ReadTimeoutError)
106
- raise if retry_count <= 0
107
-
108
- update_cluster_info!
109
- retry_count -= 1
110
- retry
111
90
  end
112
91
 
113
- def try_delegate(node, method, *args, retry_count: 3, **kwargs, &block) # rubocop:disable Metrics/AbcSize
114
- node.public_send(method, *args, **kwargs, &block)
92
+ def handle_redirection(node, retry_count:) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
93
+ yield node
115
94
  rescue ::RedisClient::CircuitBreaker::OpenCircuitError
116
95
  raise
117
96
  rescue ::RedisClient::CommandError => e
118
- raise if retry_count <= 0
97
+ raise unless ErrorIdentification.client_owns_error?(e, node)
119
98
 
120
99
  if e.message.start_with?('MOVED')
121
100
  node = assign_redirection_node(e.message)
122
101
  retry_count -= 1
123
- retry
102
+ retry if retry_count >= 0
124
103
  elsif e.message.start_with?('ASK')
125
104
  node = assign_asking_node(e.message)
126
- node.call('ASKING')
127
105
  retry_count -= 1
128
- retry
106
+ if retry_count >= 0
107
+ node.call('ASKING')
108
+ retry
109
+ end
129
110
  elsif e.message.start_with?('CLUSTERDOWN Hash slot not served')
130
111
  update_cluster_info!
131
112
  retry_count -= 1
132
- retry
133
- else
134
- raise
113
+ retry if retry_count >= 0
135
114
  end
136
- rescue ::RedisClient::ConnectionError
137
- raise if retry_count <= 0
115
+ raise
116
+ rescue ::RedisClient::ConnectionError => e
117
+ raise unless ErrorIdentification.client_owns_error?(e, node)
138
118
 
139
119
  update_cluster_info!
120
+
121
+ raise if retry_count <= 0
122
+
140
123
  retry_count -= 1
141
124
  retry
142
125
  end
@@ -170,21 +153,30 @@ class RedisClient
170
153
  find_node(node_key)
171
154
  end
172
155
 
173
- def find_node_key(command, seed: nil)
174
- key = @command.extract_first_key(command)
175
- slot = key.empty? ? nil : ::RedisClient::Cluster::KeySlotConverter.convert(key)
176
-
177
- if @command.should_send_to_primary?(command)
178
- @node.find_node_key_of_primary(slot) || @node.any_primary_node_key(seed: seed)
156
+ def find_node_key_by_key(key, seed: nil, primary: false)
157
+ if key && !key.empty?
158
+ slot = ::RedisClient::Cluster::KeySlotConverter.convert(key)
159
+ primary ? @node.find_node_key_of_primary(slot) : @node.find_node_key_of_replica(slot)
179
160
  else
180
- @node.find_node_key_of_replica(slot, seed: seed) || @node.any_replica_node_key(seed: seed)
161
+ primary ? @node.any_primary_node_key(seed: seed) : @node.any_replica_node_key(seed: seed)
181
162
  end
182
163
  end
183
164
 
165
+ def find_primary_node_by_slot(slot)
166
+ node_key = @node.find_node_key_of_primary(slot)
167
+ find_node(node_key)
168
+ end
169
+
170
+ def find_node_key(command, seed: nil)
171
+ key = @command.extract_first_key(command)
172
+ find_node_key_by_key(key, seed: seed, primary: @command.should_send_to_primary?(command))
173
+ end
174
+
184
175
  def find_primary_node_key(command)
185
176
  key = @command.extract_first_key(command)
186
- slot = key.empty? ? nil : ::RedisClient::Cluster::KeySlotConverter.convert(key)
187
- @node.find_node_key_of_primary(slot)
177
+ return nil unless key&.size&.> 0
178
+
179
+ find_node_key_by_key(key, primary: true)
188
180
  end
189
181
 
190
182
  def find_node(node_key, retry_count: 3)
@@ -306,34 +298,8 @@ class RedisClient
306
298
  end
307
299
  end
308
300
 
309
- def fetch_cluster_info(config, concurrent_worker, pool: nil, **kwargs)
310
- node_info_list = ::RedisClient::Cluster::Node.load_info(config.per_node_key, concurrent_worker, slow_command_timeout: config.slow_command_timeout, **kwargs)
311
- node_addrs = node_info_list.map { |i| ::RedisClient::Cluster::NodeKey.hashify(i.node_key) }
312
- config.update_node(node_addrs)
313
- ::RedisClient::Cluster::Node.new(
314
- config.per_node_key,
315
- concurrent_worker,
316
- node_info_list: node_info_list,
317
- pool: pool,
318
- with_replica: config.use_replica?,
319
- replica_affinity: config.replica_affinity,
320
- **kwargs
321
- )
322
- end
323
-
324
301
  def update_cluster_info!
325
- return if @mutex.locked?
326
-
327
- @mutex.synchronize do
328
- begin
329
- @node.each(&:close)
330
- rescue ::RedisClient::Cluster::ErrorCollection
331
- # ignore
332
- end
333
-
334
- @config = @original_config.dup if @connect_with_original_config
335
- @node = fetch_cluster_info(@config, @concurrent_worker, pool: @pool, **@client_kwargs)
336
- end
302
+ @node.reload!
337
303
  end
338
304
  end
339
305
  end
@@ -1,56 +1,182 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'redis_client'
4
+ require 'redis_client/cluster/pipeline'
5
+ require 'redis_client/cluster/node_key'
4
6
 
5
7
  class RedisClient
6
8
  class Cluster
7
9
  class Transaction
8
10
  ConsistencyError = Class.new(::RedisClient::Error)
9
11
 
10
- def initialize(router, command_builder)
12
+ def initialize(router, command_builder, node = nil)
11
13
  @router = router
12
14
  @command_builder = command_builder
13
- @node_key = nil
15
+ @retryable = true
16
+ @pipeline = ::RedisClient::Pipeline.new(@command_builder)
17
+ @pending_commands = []
18
+ @node = node
19
+ prepare_tx unless @node.nil?
14
20
  end
15
21
 
16
- def call(*command, **kwargs, &_)
22
+ def call(*command, **kwargs, &block)
17
23
  command = @command_builder.generate(command, kwargs)
18
- ensure_node_key(command)
24
+ if prepare(command)
25
+ @pipeline.call_v(command, &block)
26
+ else
27
+ defer { @pipeline.call_v(command, &block) }
28
+ end
19
29
  end
20
30
 
21
- def call_v(command, &_)
31
+ def call_v(command, &block)
22
32
  command = @command_builder.generate(command)
23
- ensure_node_key(command)
33
+ if prepare(command)
34
+ @pipeline.call_v(command, &block)
35
+ else
36
+ defer { @pipeline.call_v(command, &block) }
37
+ end
24
38
  end
25
39
 
26
- def call_once(*command, **kwargs, &_)
40
+ def call_once(*command, **kwargs, &block)
41
+ @retryable = false
27
42
  command = @command_builder.generate(command, kwargs)
28
- ensure_node_key(command)
43
+ if prepare(command)
44
+ @pipeline.call_once_v(command, &block)
45
+ else
46
+ defer { @pipeline.call_once_v(command, &block) }
47
+ end
29
48
  end
30
49
 
31
- def call_once_v(command, &_)
50
+ def call_once_v(command, &block)
51
+ @retryable = false
32
52
  command = @command_builder.generate(command)
33
- ensure_node_key(command)
53
+ if prepare(command)
54
+ @pipeline.call_once_v(command, &block)
55
+ else
56
+ defer { @pipeline.call_once_v(command, &block) }
57
+ end
34
58
  end
35
59
 
36
- def execute(watch: nil, &block)
37
- yield self
38
- raise ArgumentError, 'empty transaction' if @node_key.nil?
60
+ def execute
61
+ @pending_commands.each(&:call)
39
62
 
40
- node = @router.find_node(@node_key)
41
- @router.try_delegate(node, :multi, watch: watch, &block)
63
+ raise ArgumentError, 'empty transaction' if @pipeline._empty?
64
+ raise ConsistencyError, "couldn't determine the node: #{@pipeline._commands}" if @node.nil?
65
+
66
+ settle
42
67
  end
43
68
 
44
69
  private
45
70
 
46
- def ensure_node_key(command)
71
+ def defer(&block)
72
+ @pending_commands << block
73
+ nil
74
+ end
75
+
76
+ def prepare(command)
77
+ return true unless @node.nil?
78
+
47
79
  node_key = @router.find_primary_node_key(command)
48
- raise ConsistencyError, "Client couldn't determine the node to be executed the transaction by: #{command}" if node_key.nil?
80
+ return false if node_key.nil?
49
81
 
50
- @node_key ||= node_key
51
- raise ConsistencyError, "The transaction should be done for single node: #{@node_key}, #{node_key}" if node_key != @node_key
82
+ @node = @router.find_node(node_key)
83
+ prepare_tx
84
+ true
85
+ end
52
86
 
53
- nil
87
+ def prepare_tx
88
+ @pipeline.call('MULTI')
89
+ @pending_commands.each(&:call)
90
+ @pending_commands.clear
91
+ end
92
+
93
+ def settle
94
+ @pipeline.call('EXEC')
95
+ send_transaction(@node, redirect: true)
96
+ end
97
+
98
+ def send_transaction(client, redirect:)
99
+ case client
100
+ when ::RedisClient then send_pipeline(client, redirect: redirect)
101
+ when ::RedisClient::Pooled then client.with { |c| send_pipeline(c, redirect: redirect) }
102
+ else raise NotImplementedError, "#{client.class.name}#multi for cluster client"
103
+ end
104
+ end
105
+
106
+ def send_pipeline(client, redirect:)
107
+ replies = client.ensure_connected_cluster_scoped(retryable: @retryable) do |connection|
108
+ commands = @pipeline._commands
109
+ client.middlewares.call_pipelined(commands, client.config) do
110
+ connection.call_pipelined(commands, nil)
111
+ rescue ::RedisClient::CommandError => e
112
+ return handle_command_error!(commands, e) if redirect
113
+
114
+ raise
115
+ end
116
+ end
117
+
118
+ return if replies.last.nil?
119
+
120
+ coerce_results!(replies.last)
121
+ end
122
+
123
+ def coerce_results!(results, offset: 1)
124
+ results.each_with_index do |result, index|
125
+ if result.is_a?(::RedisClient::CommandError)
126
+ result._set_command(@pipeline._commands[index + offset])
127
+ raise result
128
+ end
129
+
130
+ next if @pipeline._blocks.nil?
131
+
132
+ block = @pipeline._blocks[index + offset]
133
+ next if block.nil?
134
+
135
+ results[index] = block.call(result)
136
+ end
137
+
138
+ results
139
+ end
140
+
141
+ def handle_command_error!(commands, err)
142
+ if err.message.start_with?('CROSSSLOT')
143
+ raise ConsistencyError, "#{err.message}: #{err.command}"
144
+ elsif err.message.start_with?('MOVED', 'ASK')
145
+ ensure_the_same_node!(commands)
146
+ handle_redirection(err)
147
+ else
148
+ raise err
149
+ end
150
+ end
151
+
152
+ def ensure_the_same_node!(commands)
153
+ expected_node_key = NodeKey.build_from_client(@node)
154
+
155
+ commands.each do |command|
156
+ node_key = @router.find_primary_node_key(command)
157
+ next if node_key.nil?
158
+ next if node_key == expected_node_key
159
+
160
+ raise ConsistencyError, "the transaction should be executed to a slot in a node: #{commands}"
161
+ end
162
+ end
163
+
164
+ def handle_redirection(err)
165
+ if err.message.start_with?('MOVED')
166
+ node = @router.assign_redirection_node(err.message)
167
+ send_transaction(node, redirect: false)
168
+ elsif err.message.start_with?('ASK')
169
+ node = @router.assign_asking_node(err.message)
170
+ try_asking(node) ? send_transaction(node, redirect: false) : err
171
+ else
172
+ raise err
173
+ end
174
+ end
175
+
176
+ def try_asking(node)
177
+ node.call('ASKING') == 'OK'
178
+ rescue StandardError
179
+ false
54
180
  end
55
181
  end
56
182
  end
@@ -5,6 +5,8 @@ require 'redis_client/cluster/pipeline'
5
5
  require 'redis_client/cluster/pub_sub'
6
6
  require 'redis_client/cluster/router'
7
7
  require 'redis_client/cluster/transaction'
8
+ require 'redis_client/cluster/pinning_node'
9
+ require 'redis_client/cluster/optimistic_locking'
8
10
 
9
11
  class RedisClient
10
12
  class Cluster
@@ -89,14 +91,34 @@ class RedisClient
89
91
  pipeline.execute
90
92
  end
91
93
 
92
- def multi(watch: nil, &block)
93
- ::RedisClient::Cluster::Transaction.new(@router, @command_builder).execute(watch: watch, &block)
94
+ def multi(watch: nil)
95
+ if watch.nil? || watch.empty?
96
+ transaction = ::RedisClient::Cluster::Transaction.new(@router, @command_builder)
97
+ yield transaction
98
+ transaction.execute
99
+ else
100
+ locking = ::RedisClient::Cluster::OptimisticLocking.new(watch, @router)
101
+ locking.watch do |c|
102
+ transaction = ::RedisClient::Cluster::Transaction.new(@router, @command_builder, c)
103
+ yield transaction
104
+ transaction.execute
105
+ end
106
+ end
94
107
  end
95
108
 
96
109
  def pubsub
97
110
  ::RedisClient::Cluster::PubSub.new(@router, @command_builder)
98
111
  end
99
112
 
113
+ # TODO: This isn't an official public interface yet. Don't use in your production environment.
114
+ # @see https://github.com/redis-rb/redis-cluster-client/issues/299
115
+ def with(key: nil, hashtag: nil, write: true)
116
+ key = process_with_arguments(key, hashtag)
117
+ node_key = @router.find_node_key_by_key(key, primary: write)
118
+ node = @router.find_node(node_key)
119
+ node.with { |c| yield ::RedisClient::Cluster::PinningNode.new(c) }
120
+ end
121
+
100
122
  def close
101
123
  @concurrent_worker.close
102
124
  @router.close
@@ -105,6 +127,19 @@ class RedisClient
105
127
 
106
128
  private
107
129
 
130
+ def process_with_arguments(key, hashtag) # rubocop:disable Metrics/CyclomaticComplexity
131
+ raise ArgumentError, 'Only one of key or hashtag may be provided' if key && hashtag
132
+
133
+ if hashtag
134
+ # The documentation says not to wrap your hashtag in {}, but people will probably
135
+ # do it anyway and it's easy for us to fix here.
136
+ key = hashtag&.match?(/^{.*}$/) ? hashtag : "{#{hashtag}}"
137
+ end
138
+ raise ArgumentError, 'One of key or hashtag must be provided' if key.nil? || key.empty?
139
+
140
+ key
141
+ end
142
+
108
143
  def method_missing(name, *args, **kwargs, &block)
109
144
  if @router.command_exists?(name)
110
145
  args.unshift(name)
@@ -23,9 +23,10 @@ class RedisClient
23
23
 
24
24
  InvalidClientConfigError = Class.new(::RedisClient::Error)
25
25
 
26
- attr_reader :command_builder, :client_config, :replica_affinity, :slow_command_timeout, :connect_with_original_config
26
+ attr_reader :command_builder, :client_config, :replica_affinity, :slow_command_timeout,
27
+ :connect_with_original_config, :startup_nodes
27
28
 
28
- def initialize( # rubocop:disable Metrics/AbcSize
29
+ def initialize(
29
30
  nodes: DEFAULT_NODES,
30
31
  replica: false,
31
32
  replica_affinity: :random,
@@ -34,39 +35,26 @@ class RedisClient
34
35
  connect_with_original_config: false,
35
36
  client_implementation: ::RedisClient::Cluster, # for redis gem
36
37
  slow_command_timeout: SLOW_COMMAND_TIMEOUT,
38
+ command_builder: ::RedisClient::CommandBuilder,
37
39
  **client_config
38
40
  )
39
41
 
40
42
  @replica = true & replica
41
43
  @replica_affinity = replica_affinity.to_s.to_sym
42
44
  @fixed_hostname = fixed_hostname.to_s
43
- @node_configs = build_node_configs(nodes.dup)
44
- client_config = client_config.reject { |k, _| IGNORE_GENERIC_CONFIG_KEYS.include?(k) }
45
- @command_builder = client_config.fetch(:command_builder, ::RedisClient::CommandBuilder)
46
- @client_config = merge_generic_config(client_config, @node_configs)
45
+ @command_builder = command_builder
46
+ node_configs = build_node_configs(nodes.dup)
47
+ @client_config = merge_generic_config(client_config, node_configs)
48
+ # Keep tabs on the original startup nodes we were constructed with
49
+ @startup_nodes = build_startup_nodes(node_configs)
47
50
  @concurrency = merge_concurrency_option(concurrency)
48
51
  @connect_with_original_config = connect_with_original_config
49
52
  @client_implementation = client_implementation
50
53
  @slow_command_timeout = slow_command_timeout
51
- @mutex = Mutex.new
52
- end
53
-
54
- def dup
55
- self.class.new(
56
- nodes: @node_configs,
57
- replica: @replica,
58
- replica_affinity: @replica_affinity,
59
- fixed_hostname: @fixed_hostname,
60
- concurrency: @concurrency,
61
- connect_with_original_config: @connect_with_original_config,
62
- client_implementation: @client_implementation,
63
- slow_command_timeout: @slow_command_timeout,
64
- **@client_config
65
- )
66
54
  end
67
55
 
68
56
  def inspect
69
- "#<#{self.class.name} #{per_node_key.values}>"
57
+ "#<#{self.class.name} #{startup_nodes.values}>"
70
58
  end
71
59
 
72
60
  def read_timeout
@@ -86,29 +74,14 @@ class RedisClient
86
74
  @client_implementation.new(self, concurrency: @concurrency, **kwargs)
87
75
  end
88
76
 
89
- def per_node_key
90
- @node_configs.to_h do |config|
91
- node_key = ::RedisClient::Cluster::NodeKey.build_from_host_port(config[:host], config[:port])
92
- config = @client_config.merge(config)
93
- config = config.merge(host: @fixed_hostname) unless @fixed_hostname.empty?
94
- [node_key, config]
95
- end
96
- end
97
-
98
77
  def use_replica?
99
78
  @replica
100
79
  end
101
80
 
102
- def update_node(addrs)
103
- return if @mutex.locked?
104
-
105
- @mutex.synchronize { @node_configs = build_node_configs(addrs) }
106
- end
107
-
108
- def add_node(host, port)
109
- return if @mutex.locked?
110
-
111
- @mutex.synchronize { @node_configs << { host: host, port: port } }
81
+ def client_config_for_node(node_key)
82
+ config = ::RedisClient::Cluster::NodeKey.hashify(node_key)
83
+ config[:port] = ensure_integer(config[:port])
84
+ augment_client_config(config)
112
85
  end
113
86
 
114
87
  private
@@ -176,11 +149,23 @@ class RedisClient
176
149
  end
177
150
 
178
151
  def merge_generic_config(client_config, node_configs)
179
- return client_config if node_configs.empty?
152
+ cfg = node_configs.first || {}
153
+ client_config.reject { |k, _| IGNORE_GENERIC_CONFIG_KEYS.include?(k) }
154
+ .merge(cfg.slice(*MERGE_CONFIG_KEYS))
155
+ end
156
+
157
+ def build_startup_nodes(configs)
158
+ configs.to_h do |config|
159
+ node_key = ::RedisClient::Cluster::NodeKey.build_from_host_port(config[:host], config[:port])
160
+ config = augment_client_config(config)
161
+ [node_key, config]
162
+ end
163
+ end
180
164
 
181
- cfg = node_configs.first
182
- MERGE_CONFIG_KEYS.each { |k| client_config[k] = cfg[k] if cfg.key?(k) }
183
- client_config
165
+ def augment_client_config(config)
166
+ config = @client_config.merge(config)
167
+ config = config.merge(host: @fixed_hostname) unless @fixed_hostname.empty?
168
+ config
184
169
  end
185
170
  end
186
171
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis-cluster-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.5
4
+ version: 0.7.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Taishi Kasuga
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-10-20 00:00:00.000000000 Z
11
+ date: 2024-02-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis-client
@@ -38,16 +38,19 @@ files:
38
38
  - lib/redis_client/cluster/concurrent_worker/none.rb
39
39
  - lib/redis_client/cluster/concurrent_worker/on_demand.rb
40
40
  - lib/redis_client/cluster/concurrent_worker/pooled.rb
41
+ - lib/redis_client/cluster/error_identification.rb
41
42
  - lib/redis_client/cluster/errors.rb
42
43
  - lib/redis_client/cluster/key_slot_converter.rb
43
44
  - lib/redis_client/cluster/node.rb
45
+ - lib/redis_client/cluster/node/base_topology.rb
44
46
  - lib/redis_client/cluster/node/latency_replica.rb
45
47
  - lib/redis_client/cluster/node/primary_only.rb
46
48
  - lib/redis_client/cluster/node/random_replica.rb
47
49
  - lib/redis_client/cluster/node/random_replica_or_primary.rb
48
- - lib/redis_client/cluster/node/replica_mixin.rb
49
50
  - lib/redis_client/cluster/node_key.rb
50
51
  - lib/redis_client/cluster/normalized_cmd_name.rb
52
+ - lib/redis_client/cluster/optimistic_locking.rb
53
+ - lib/redis_client/cluster/pinning_node.rb
51
54
  - lib/redis_client/cluster/pipeline.rb
52
55
  - lib/redis_client/cluster/pub_sub.rb
53
56
  - lib/redis_client/cluster/router.rb
@@ -75,7 +78,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
75
78
  - !ruby/object:Gem::Version
76
79
  version: '0'
77
80
  requirements: []
78
- rubygems_version: 3.4.19
81
+ rubygems_version: 3.5.3
79
82
  signing_key:
80
83
  specification_version: 4
81
84
  summary: A Redis cluster client for Ruby
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class RedisClient
4
- class Cluster
5
- class Node
6
- module ReplicaMixin
7
- attr_reader :clients, :primary_clients
8
-
9
- EMPTY_ARRAY = [].freeze
10
-
11
- def initialize(replications, options, pool, _concurrent_worker, **kwargs)
12
- @replications = replications
13
- @primary_node_keys = @replications.keys.sort
14
- @replica_node_keys = @replications.values.flatten.sort
15
- @clients = build_clients(@primary_node_keys, options, pool, **kwargs)
16
- @primary_clients = @clients.select { |k, _| @primary_node_keys.include?(k) }
17
- end
18
-
19
- def any_primary_node_key(seed: nil)
20
- random = seed.nil? ? Random : Random.new(seed)
21
- @primary_node_keys.sample(random: random)
22
- end
23
-
24
- private
25
-
26
- def build_clients(primary_node_keys, options, pool, **kwargs)
27
- options.to_h do |node_key, option|
28
- option = option.merge(kwargs.reject { |k, _| ::RedisClient::Cluster::Node::IGNORE_GENERIC_CONFIG_KEYS.include?(k) })
29
- config = ::RedisClient::Cluster::Node::Config.new(scale_read: !primary_node_keys.include?(node_key), **option)
30
- client = pool.nil? ? config.new_client : config.new_pool(**pool)
31
- [node_key, client]
32
- end
33
- end
34
- end
35
- end
36
- end
37
- end