redis-cluster-client 0.0.3 → 0.0.6

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7a4668f3eaba9c3053d1464bdf1dc85524e2ba43fdbb2ffbbadc46fb4759d5e6
4
- data.tar.gz: eba5280f698fe559a69cdeba28cef2c0fff87023fa78e7d098042d9cc6c036ac
3
+ metadata.gz: fde84481c2ca9680670f252a002391a34ae793148a21a8e02086d199412a53ab
4
+ data.tar.gz: 5af188432f9ae5e76c13d0dd174c25cde4b98d1de49efeb97e2a809bd111a9a6
5
5
  SHA512:
6
- metadata.gz: 989fd1b82d429ae6450a8c9e1555fc3635c1592c4d68744ad33df9cb26f5d222666fbeb077af99757c6305956936f44b8e38aef405c1a275c10e501559337934
7
- data.tar.gz: c0e88ca74a4d0ff70116969ea0722d1659858587c1cfe8a404edf41658c0b9a87e0f3651db02fe88ad6d099b2948814fcc9415aa793394fb4e75ebc5eef0acec
6
+ metadata.gz: f26dc7bb447ee70742efc90344758ad3058cd0d1506ad8576637b7e9ad296a806d7e15e3b824cead0fb2313140dea00f6f579a15803399c16257ac347c45085f
7
+ data.tar.gz: 06f43e223a53d9ada0803f7292bc1ca58505fb3c341fd7643eb6cf0eff73bebe4ecd2022b0a1b59af81dda879a9d8a9b078107181ff45037773382dada321597
@@ -79,6 +79,7 @@ class RedisClient
79
79
  @slots = build_slot_node_mappings(node_info)
80
80
  @replications = build_replication_mappings(node_info)
81
81
  @clients = build_clients(options, pool: pool, **kwargs)
82
+ @mutex = Mutex.new
82
83
  end
83
84
 
84
85
  def inspect
@@ -101,6 +102,12 @@ class RedisClient
101
102
  @clients.filter_map { |k, _| primary?(k) ? k : nil }.sort
102
103
  end
103
104
 
105
+ def replica_node_keys
106
+ return primary_node_keys if replica_disabled?
107
+
108
+ @clients.filter_map { |k, _| replica?(k) ? k : nil }.sort
109
+ end
110
+
104
111
  def find_by(node_key)
105
112
  @clients.fetch(node_key)
106
113
  rescue KeyError
@@ -122,19 +129,17 @@ class RedisClient
122
129
  def call_replica(method, *command, **kwargs, &block)
123
130
  return call_primary(method, *command, **kwargs, &block) if replica_disabled?
124
131
 
132
+ replica_node_keys = @replications.values.map(&:sample)
125
133
  try_map do |node_key, client|
126
- next if primary?(node_key)
134
+ next if primary?(node_key) || !replica_node_keys.include?(node_key)
127
135
 
128
136
  client.send(method, *command, **kwargs, &block)
129
137
  end.values
130
138
  end
131
139
 
132
140
  def scale_reading_clients
133
- clients = @clients.select do |node_key, _|
134
- replica_disabled? ? primary?(node_key) : replica?(node_key)
135
- end
136
-
137
- clients.values.sort_by do |client|
141
+ keys = replica_disabled? ? @replications.keys : @replications.values.map(&:first)
142
+ @clients.select { |k, _| keys.include?(k) }.values.sort_by do |client|
138
143
  ::RedisClient::Cluster::NodeKey.build_from_host_port(client.config.host, client.config.port)
139
144
  end
140
145
  end
@@ -163,7 +168,13 @@ class RedisClient
163
168
  end
164
169
 
165
170
  def update_slot(slot, node_key)
166
- @slots[slot] = node_key
171
+ @mutex.synchronize { @slots[slot] = node_key }
172
+ end
173
+
174
+ def replicated?(primary_node_key, replica_node_key)
175
+ return false if @replications.nil? || @replications.size.zero?
176
+
177
+ @replications.fetch(primary_node_key).include?(replica_node_key)
167
178
  end
168
179
 
169
180
  private
@@ -177,7 +188,9 @@ class RedisClient
177
188
  end
178
189
 
179
190
  def replica?(node_key)
180
- !(@replications.nil? || @replications.size.zero?) && @replications[node_key].size.zero?
191
+ return false if @replications.nil? || @replications.size.zero?
192
+
193
+ !@replications.key?(node_key)
181
194
  end
182
195
 
183
196
  def build_slot_node_mappings(node_info)
@@ -191,12 +204,12 @@ class RedisClient
191
204
  slots
192
205
  end
193
206
 
194
- def build_replication_mappings(node_info)
207
+ def build_replication_mappings(node_info) # rubocop:disable Metrics/AbcSize
195
208
  dict = node_info.to_h { |info| [info[:id], info] }
196
209
  node_info.each_with_object(Hash.new { |h, k| h[k] = [] }) do |info, acc|
197
210
  primary_info = dict[info[:primary_id]]
198
211
  acc[primary_info[:node_key]] << info[:node_key] unless primary_info.nil?
199
- acc[info[:node_key]]
212
+ acc[info[:node_key]] if info[:role] == 'master' # for the primary which have no replicas
200
213
  end
201
214
  end
202
215
 
@@ -217,15 +230,17 @@ class RedisClient
217
230
  def try_map # rubocop:disable Metrics/MethodLength
218
231
  errors = {}
219
232
  results = {}
220
-
221
- @clients.each do |node_key, client|
222
- reply = yield(node_key, client)
223
- results[node_key] = reply unless reply.nil?
224
- rescue ::RedisClient::CommandError => e
225
- errors[node_key] = e
226
- next
233
+ threads = @clients.map do |k, v|
234
+ Thread.new(k, v) do |node_key, client|
235
+ Thread.pass
236
+ reply = yield(node_key, client)
237
+ results[node_key] = reply unless reply.nil?
238
+ rescue ::RedisClient::CommandError => e
239
+ errors[node_key] = e
240
+ end
227
241
  end
228
242
 
243
+ threads.each(&:join)
229
244
  return results if errors.empty?
230
245
 
231
246
  raise ::RedisClient::Cluster::CommandErrorCollection, errors
@@ -15,7 +15,6 @@ class RedisClient
15
15
  def initialize(client)
16
16
  @client = client
17
17
  @grouped = Hash.new([].freeze)
18
- @replies = []
19
18
  @size = 0
20
19
  end
21
20
 
@@ -41,27 +40,31 @@ class RedisClient
41
40
  @size.zero?
42
41
  end
43
42
 
44
- # TODO: use concurrency
43
+ # TODO: https://github.com/redis-rb/redis-cluster-client/issues/37
45
44
  def execute # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
46
- @grouped.each do |node_key, rows|
47
- node_key = node_key.nil? ? @client.instance_variable_get(:@node).primary_node_keys.sample : node_key
48
- replies = @client.send(:find_node, node_key).pipelined do |pipeline|
49
- rows.each do |row|
50
- case row[1]
51
- when :call then pipeline.call(*row[2], **row[3])
52
- when :call_once then pipeline.call_once(*row[2], **row[3])
53
- when :blocking_call then pipeline.blocking_call(row[2], *row[3], **row[4])
54
- else raise NotImplementedError, row[1]
45
+ all_replies = Array.new(@size)
46
+ threads = @grouped.map do |k, v|
47
+ Thread.new(@client, k, v) do |client, node_key, rows|
48
+ Thread.pass
49
+ replies = client.send(:find_node, node_key).pipelined do |pipeline|
50
+ rows.each do |row|
51
+ case row[1]
52
+ when :call then pipeline.call(*row[2], **row[3])
53
+ when :call_once then pipeline.call_once(*row[2], **row[3])
54
+ when :blocking_call then pipeline.blocking_call(row[2], *row[3], **row[4])
55
+ else raise NotImplementedError, row[1]
56
+ end
55
57
  end
56
58
  end
57
- end
58
59
 
59
- raise ReplySizeError, "commands: #{rows.size}, replies: #{replies.size}" if rows.size != replies.size
60
+ raise ReplySizeError, "commands: #{rows.size}, replies: #{replies.size}" if rows.size != replies.size
60
61
 
61
- rows.each_with_index { |row, idx| @replies[row.first] = replies[idx] }
62
+ rows.each_with_index { |row, idx| all_replies[row.first] = replies[idx] }
63
+ end
62
64
  end
63
65
 
64
- @replies
66
+ threads.each(&:join)
67
+ all_replies
65
68
  end
66
69
  end
67
70
 
@@ -103,6 +106,7 @@ class RedisClient
103
106
  @client_kwargs = kwargs
104
107
  @node = fetch_cluster_info!(@config, pool: @pool, **@client_kwargs)
105
108
  @command = ::RedisClient::Cluster::Command.load(@node)
109
+ @mutex = Mutex.new
106
110
  end
107
111
 
108
112
  def inspect
@@ -178,11 +182,11 @@ class RedisClient
178
182
  def send_command(method, *command, **kwargs, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
179
183
  cmd = command.first.to_s.downcase
180
184
  case cmd
181
- when 'acl', 'auth', 'bgrewriteaof', 'bgsave', 'quit', 'save'
185
+ when 'acl', 'auth', 'bgrewriteaof', 'bgsave', 'quit', 'save', 'ping'
182
186
  @node.call_all(method, *command, **kwargs, &block).first
183
187
  when 'flushall', 'flushdb'
184
188
  @node.call_primary(method, *command, **kwargs, &block).first
185
- when 'wait' then @node.call_primary(method, *command, **kwargs, &block).sum
189
+ when 'wait' then send_wait_command(method, *command, **kwargs, &block)
186
190
  when 'keys' then @node.call_replica(method, *command, **kwargs, &block).flatten.sort
187
191
  when 'dbsize' then @node.call_replica(method, *command, **kwargs, &block).sum
188
192
  when 'scan' then _scan(*command, **kwargs)
@@ -202,6 +206,19 @@ class RedisClient
202
206
  node = assign_node(*command)
203
207
  try_send(node, method, *command, **kwargs, &block)
204
208
  end
209
+ rescue RedisClient::Cluster::CommandErrorCollection => e
210
+ update_cluster_info! if e.errors.values.map(&:class).any?(::RedisClient::ConnectionError)
211
+ raise
212
+ end
213
+
214
+ def send_wait_command(method, *command, retry_count: 3, **kwargs, &block)
215
+ @node.call_primary(method, *command, **kwargs, &block).sum
216
+ rescue RedisClient::Cluster::CommandErrorCollection => e
217
+ raise if retry_count <= 0 || e.errors.values.map(&:message).grep(/ERR WAIT cannot be used with replica instances/).empty?
218
+
219
+ update_cluster_info!
220
+ retry_count -= 1
221
+ retry
205
222
  end
206
223
 
207
224
  def send_config_command(method, *command, **kwargs, &block)
@@ -263,18 +280,16 @@ class RedisClient
263
280
 
264
281
  # @see https://redis.io/topics/cluster-spec#redirection-and-resharding
265
282
  # Redirection and resharding
266
- def try_send(node, method, *args, retry_count: 3, **kwargs, &block) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
283
+ def try_send(node, method, *args, retry_count: 3, **kwargs, &block) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
267
284
  node.send(method, *args, **kwargs, &block)
268
285
  rescue ::RedisClient::CommandError => e
269
- if e.message.start_with?(REPLY_MOVED)
270
- raise if retry_count <= 0
286
+ raise if retry_count <= 0
271
287
 
288
+ if e.message.start_with?(REPLY_MOVED)
272
289
  node = assign_redirection_node(e.message)
273
290
  retry_count -= 1
274
291
  retry
275
292
  elsif e.message.start_with?(REPLY_ASK)
276
- raise if retry_count <= 0
277
-
278
293
  node = assign_asking_node(e.message)
279
294
  node.call(CMD_ASKING)
280
295
  retry_count -= 1
@@ -283,8 +298,11 @@ class RedisClient
283
298
  raise
284
299
  end
285
300
  rescue ::RedisClient::ConnectionError
301
+ raise if retry_count <= 0
302
+
286
303
  update_cluster_info!
287
- raise
304
+ retry_count -= 1
305
+ retry
288
306
  end
289
307
 
290
308
  def _scan(*command, **kwargs) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
@@ -326,9 +344,13 @@ class RedisClient
326
344
  find_node(node_key)
327
345
  end
328
346
 
329
- def find_node_key(*command, primary_only: false)
347
+ def find_node_key(*command, primary_only: false) # rubocop:disable Metrics/MethodLength
330
348
  key = @command.extract_first_key(command)
331
- return if key.empty?
349
+ if key.empty?
350
+ return @node.primary_node_keys.sample if @command.should_send_to_primary?(command) || primary_only
351
+
352
+ return @node.replica_node_keys.sample
353
+ end
332
354
 
333
355
  slot = ::RedisClient::Cluster::KeySlotConverter.convert(key)
334
356
  return unless @node.slot_exists?(slot)
@@ -340,23 +362,28 @@ class RedisClient
340
362
  end
341
363
  end
342
364
 
343
- def find_node(node_key)
365
+ def find_node(node_key, retry_count: 3)
344
366
  return @node.sample if node_key.nil?
345
367
 
346
368
  @node.find_by(node_key)
347
369
  rescue ::RedisClient::Cluster::Node::ReloadNeeded
370
+ raise(::RedisClient::ConnectionError, 'unstable cluster state') if retry_count <= 0
371
+
348
372
  update_cluster_info!(node_key)
349
- @node.find_by(node_key)
373
+ retry_count -= 1
374
+ retry
350
375
  end
351
376
 
352
377
  def update_cluster_info!(node_key = nil)
353
- unless node_key.nil?
354
- host, port = ::RedisClient::Cluster::NodeKey.split(node_key)
355
- @config.add_node(host, port)
356
- end
378
+ @mutex.synchronize do
379
+ unless node_key.nil?
380
+ host, port = ::RedisClient::Cluster::NodeKey.split(node_key)
381
+ @config.add_node(host, port)
382
+ end
357
383
 
358
- @node.each(&:close)
359
- @node = fetch_cluster_info!(@config, pool: @pool, **@client_kwargs)
384
+ @node.each(&:close)
385
+ @node = fetch_cluster_info!(@config, pool: @pool, **@client_kwargs)
386
+ end
360
387
  end
361
388
  end
362
389
  end
@@ -15,6 +15,7 @@ class RedisClient
15
15
  VALID_SCHEMES = [DEFAULT_SCHEME, SECURE_SCHEME].freeze
16
16
  VALID_NODES_KEYS = %i[ssl username password host port db].freeze
17
17
  MERGE_CONFIG_KEYS = %i[ssl username password].freeze
18
+ IGNORE_GENERIC_CONFIG_KEYS = %i[url host port path].freeze
18
19
 
19
20
  InvalidClientConfigError = Class.new(::RedisClient::Error)
20
21
 
@@ -22,7 +23,9 @@ class RedisClient
22
23
  @replica = true & replica
23
24
  @fixed_hostname = fixed_hostname.to_s
24
25
  @node_configs = build_node_configs(nodes.dup)
26
+ client_config = client_config.reject { |k, _| IGNORE_GENERIC_CONFIG_KEYS.include?(k) }
25
27
  @client_config = merge_generic_config(client_config, @node_configs)
28
+ @mutex = Mutex.new
26
29
  end
27
30
 
28
31
  def inspect
@@ -51,11 +54,11 @@ class RedisClient
51
54
  end
52
55
 
53
56
  def update_node(addrs)
54
- @node_configs = build_node_configs(addrs)
57
+ @mutex.synchronize { @node_configs = build_node_configs(addrs) }
55
58
  end
56
59
 
57
60
  def add_node(host, port)
58
- @node_configs << { host: host, port: port }
61
+ @mutex.synchronize { @node_configs << { host: host, port: port } }
59
62
  end
60
63
 
61
64
  def dup
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.0.3
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Taishi Kasuga
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-06-17 00:00:00.000000000 Z
11
+ date: 2022-06-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis-client