redis-cluster-client 0.0.3 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
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