redis-cluster-client 0.0.4 → 0.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0561fdd5885f8c6c5c5c100af032b867224eff3ed91b546b6885f8bf3bd76663
4
- data.tar.gz: 1ebe59416fb043af9da7a926cd338ac260ce1a46e5220b46f7a6744c8f1b8c2b
3
+ metadata.gz: aff938c0517542c32f98206c28f1f605a52751e2e2e9a1bd10f5576285f568ee
4
+ data.tar.gz: 8a3d45fba93334959b1ee67504d2703bb116a8133ef8dac2aa8f314fdb74b268
5
5
  SHA512:
6
- metadata.gz: 86661400532550e005beb98e740e4939ffcf93c1b66fb47930bc2a5819f5dbba60e940c8e0183d312a6ce7cb55d1cc0634081029eb8e5c1b8f68ea2862d9e0df
7
- data.tar.gz: 3629f63ed379946bc8951c57ada535573130e8db504a2f5b7644ef9091408ac3223c5b0a260b13d6b84ab889b3e47c4571bbc10f5b21ceaf6758dc60c28a00b3
6
+ metadata.gz: 63dec0155a50a0dbb7a17763c43291d371abe4e3df407340ba8edf125cf2c2bb338f607026a1d004dca489761cb2e6b5450e68a468f18799e5c3443b52a21be7
7
+ data.tar.gz: 4de6c4688ca120d7f4b636bd884195b3cddc3bf8d217ea662a1f382ced46de2c9e24b0e967a801d175f0572a083b9e5ea835d54797422cf60ce799cda3002f74
@@ -29,7 +29,7 @@ class RedisClient
29
29
  end
30
30
 
31
31
  # Raised when error occurs on any node of cluster.
32
- class CommandErrorCollection < ::RedisClient::Error
32
+ class ErrorCollection < ::RedisClient::Error
33
33
  attr_reader :errors
34
34
 
35
35
  def initialize(errors)
@@ -118,7 +118,7 @@ class RedisClient
118
118
  try_map { |_, client| client.send(method, *command, **kwargs, &block) }.values
119
119
  end
120
120
 
121
- def call_primary(method, *command, **kwargs, &block)
121
+ def call_primaries(method, *command, **kwargs, &block)
122
122
  try_map do |node_key, client|
123
123
  next if replica?(node_key)
124
124
 
@@ -126,22 +126,20 @@ class RedisClient
126
126
  end.values
127
127
  end
128
128
 
129
- def call_replica(method, *command, **kwargs, &block)
130
- return call_primary(method, *command, **kwargs, &block) if replica_disabled?
129
+ def call_replicas(method, *command, **kwargs, &block)
130
+ return call_primaries(method, *command, **kwargs, &block) if replica_disabled?
131
131
 
132
+ replica_node_keys = @replications.values.map(&:sample)
132
133
  try_map do |node_key, client|
133
- next if primary?(node_key)
134
+ next if primary?(node_key) || !replica_node_keys.include?(node_key)
134
135
 
135
136
  client.send(method, *command, **kwargs, &block)
136
137
  end.values
137
138
  end
138
139
 
139
140
  def scale_reading_clients
140
- clients = @clients.select do |node_key, _|
141
- replica_disabled? ? primary?(node_key) : replica?(node_key)
142
- end
143
-
144
- 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|
145
143
  ::RedisClient::Cluster::NodeKey.build_from_host_port(client.config.host, client.config.port)
146
144
  end
147
145
  end
@@ -173,6 +171,12 @@ class RedisClient
173
171
  @mutex.synchronize { @slots[slot] = node_key }
174
172
  end
175
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)
178
+ end
179
+
176
180
  private
177
181
 
178
182
  def replica_disabled?
@@ -184,7 +188,9 @@ class RedisClient
184
188
  end
185
189
 
186
190
  def replica?(node_key)
187
- !(@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)
188
194
  end
189
195
 
190
196
  def build_slot_node_mappings(node_info)
@@ -198,12 +204,12 @@ class RedisClient
198
204
  slots
199
205
  end
200
206
 
201
- def build_replication_mappings(node_info)
207
+ def build_replication_mappings(node_info) # rubocop:disable Metrics/AbcSize
202
208
  dict = node_info.to_h { |info| [info[:id], info] }
203
209
  node_info.each_with_object(Hash.new { |h, k| h[k] = [] }) do |info, acc|
204
210
  primary_info = dict[info[:primary_id]]
205
211
  acc[primary_info[:node_key]] << info[:node_key] unless primary_info.nil?
206
- acc[info[:node_key]]
212
+ acc[info[:node_key]] if info[:role] == 'master' # for the primary which have no replicas
207
213
  end
208
214
  end
209
215
 
@@ -222,14 +228,14 @@ class RedisClient
222
228
  end
223
229
 
224
230
  def try_map # rubocop:disable Metrics/MethodLength
225
- errors = {}
226
231
  results = {}
232
+ errors = {}
227
233
  threads = @clients.map do |k, v|
228
234
  Thread.new(k, v) do |node_key, client|
229
235
  Thread.pass
230
236
  reply = yield(node_key, client)
231
237
  results[node_key] = reply unless reply.nil?
232
- rescue ::RedisClient::CommandError => e
238
+ rescue StandardError => e
233
239
  errors[node_key] = e
234
240
  end
235
241
  end
@@ -237,7 +243,7 @@ class RedisClient
237
243
  threads.each(&:join)
238
244
  return results if errors.empty?
239
245
 
240
- raise ::RedisClient::Cluster::CommandErrorCollection, errors
246
+ raise ::RedisClient::Cluster::ErrorCollection, errors
241
247
  end
242
248
  end
243
249
  end
@@ -40,13 +40,13 @@ class RedisClient
40
40
  @size.zero?
41
41
  end
42
42
 
43
+ # TODO: https://github.com/redis-rb/redis-cluster-client/issues/37 handle redirections
43
44
  def execute # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
44
- all_replies = []
45
+ all_replies = Array.new(@size)
46
+ errors = {}
45
47
  threads = @grouped.map do |k, v|
46
48
  Thread.new(@client, k, v) do |client, node_key, rows|
47
49
  Thread.pass
48
-
49
- node_key = node_key.nil? ? client.instance_variable_get(:@node).primary_node_keys.sample : node_key
50
50
  replies = client.send(:find_node, node_key).pipelined do |pipeline|
51
51
  rows.each do |row|
52
52
  case row[1]
@@ -61,11 +61,15 @@ class RedisClient
61
61
  raise ReplySizeError, "commands: #{rows.size}, replies: #{replies.size}" if rows.size != replies.size
62
62
 
63
63
  rows.each_with_index { |row, idx| all_replies[row.first] = replies[idx] }
64
+ rescue StandardError => e
65
+ errors[node_key] = e
64
66
  end
65
67
  end
66
68
 
67
69
  threads.each(&:join)
68
- all_replies
70
+ return all_replies if errors.empty?
71
+
72
+ raise ::RedisClient::Cluster::ErrorCollection, errors
69
73
  end
70
74
  end
71
75
 
@@ -183,13 +187,13 @@ class RedisClient
183
187
  def send_command(method, *command, **kwargs, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
184
188
  cmd = command.first.to_s.downcase
185
189
  case cmd
186
- when 'acl', 'auth', 'bgrewriteaof', 'bgsave', 'quit', 'save'
190
+ when 'acl', 'auth', 'bgrewriteaof', 'bgsave', 'quit', 'save', 'ping'
187
191
  @node.call_all(method, *command, **kwargs, &block).first
188
192
  when 'flushall', 'flushdb'
189
- @node.call_primary(method, *command, **kwargs, &block).first
190
- when 'wait' then @node.call_primary(method, *command, **kwargs, &block).sum
191
- when 'keys' then @node.call_replica(method, *command, **kwargs, &block).flatten.sort
192
- when 'dbsize' then @node.call_replica(method, *command, **kwargs, &block).sum
193
+ @node.call_primaries(method, *command, **kwargs, &block).first
194
+ when 'wait' then send_wait_command(method, *command, **kwargs, &block)
195
+ when 'keys' then @node.call_replicas(method, *command, **kwargs, &block).flatten.sort
196
+ when 'dbsize' then @node.call_replicas(method, *command, **kwargs, &block).sum
193
197
  when 'scan' then _scan(*command, **kwargs)
194
198
  when 'lastsave' then @node.call_all(method, *command, **kwargs, &block).sort
195
199
  when 'role' then @node.call_all(method, *command, **kwargs, &block)
@@ -207,6 +211,19 @@ class RedisClient
207
211
  node = assign_node(*command)
208
212
  try_send(node, method, *command, **kwargs, &block)
209
213
  end
214
+ rescue RedisClient::Cluster::ErrorCollection => e
215
+ update_cluster_info! if e.errors.values.map(&:class).any?(::RedisClient::ConnectionError)
216
+ raise
217
+ end
218
+
219
+ def send_wait_command(method, *command, retry_count: 3, **kwargs, &block)
220
+ @node.call_primaries(method, *command, **kwargs, &block).sum
221
+ rescue RedisClient::Cluster::ErrorCollection => e
222
+ raise if retry_count <= 0 || e.errors.values.map(&:message).grep(/ERR WAIT cannot be used with replica instances/).empty?
223
+
224
+ update_cluster_info!
225
+ retry_count -= 1
226
+ retry
210
227
  end
211
228
 
212
229
  def send_config_command(method, *command, **kwargs, &block)
@@ -234,13 +251,17 @@ class RedisClient
234
251
  end
235
252
  end
236
253
 
237
- def send_cluster_command(method, *command, **kwargs, &block)
254
+ def send_cluster_command(method, *command, **kwargs, &block) # rubocop:disable Metrics/MethodLength
238
255
  subcommand = command[1].to_s.downcase
239
256
  case subcommand
240
257
  when 'addslots', 'delslots', 'failover', 'forget', 'meet', 'replicate',
241
258
  'reset', 'set-config-epoch', 'setslot'
242
259
  raise ::RedisClient::Cluster::OrchestrationCommandNotSupported, ['cluster', subcommand]
243
260
  when 'saveconfig' then @node.call_all(method, *command, **kwargs, &block).first
261
+ when 'getkeysinslot'
262
+ raise ArgumentError, command.join(' ') if command.size != 4
263
+
264
+ find_node(@node.find_node_key_of_replica(command[2])).send(method, *command, **kwargs, &block)
244
265
  else assign_node(*command).send(method, *command, **kwargs, &block)
245
266
  end
246
267
  end
@@ -250,7 +271,7 @@ class RedisClient
250
271
  when 'debug', 'kill'
251
272
  @node.call_all(method, *command, **kwargs, &block).first
252
273
  when 'flush', 'load'
253
- @node.call_primary(method, *command, **kwargs, &block).first
274
+ @node.call_primaries(method, *command, **kwargs, &block).first
254
275
  else assign_node(*command).send(method, *command, **kwargs, &block)
255
276
  end
256
277
  end
@@ -268,18 +289,16 @@ class RedisClient
268
289
 
269
290
  # @see https://redis.io/topics/cluster-spec#redirection-and-resharding
270
291
  # Redirection and resharding
271
- def try_send(node, method, *args, retry_count: 3, **kwargs, &block) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
292
+ def try_send(node, method, *args, retry_count: 3, **kwargs, &block) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
272
293
  node.send(method, *args, **kwargs, &block)
273
294
  rescue ::RedisClient::CommandError => e
274
- if e.message.start_with?(REPLY_MOVED)
275
- raise if retry_count <= 0
295
+ raise if retry_count <= 0
276
296
 
297
+ if e.message.start_with?(REPLY_MOVED)
277
298
  node = assign_redirection_node(e.message)
278
299
  retry_count -= 1
279
300
  retry
280
301
  elsif e.message.start_with?(REPLY_ASK)
281
- raise if retry_count <= 0
282
-
283
302
  node = assign_asking_node(e.message)
284
303
  node.call(CMD_ASKING)
285
304
  retry_count -= 1
@@ -288,8 +307,11 @@ class RedisClient
288
307
  raise
289
308
  end
290
309
  rescue ::RedisClient::ConnectionError
310
+ raise if retry_count <= 0
311
+
291
312
  update_cluster_info!
292
- raise
313
+ retry_count -= 1
314
+ retry
293
315
  end
294
316
 
295
317
  def _scan(*command, **kwargs) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
@@ -349,13 +371,16 @@ class RedisClient
349
371
  end
350
372
  end
351
373
 
352
- def find_node(node_key)
374
+ def find_node(node_key, retry_count: 3)
353
375
  return @node.sample if node_key.nil?
354
376
 
355
377
  @node.find_by(node_key)
356
378
  rescue ::RedisClient::Cluster::Node::ReloadNeeded
379
+ raise(::RedisClient::ConnectionError, 'unstable cluster state') if retry_count <= 0
380
+
357
381
  update_cluster_info!(node_key)
358
- @node.find_by(node_key)
382
+ retry_count -= 1
383
+ retry
359
384
  end
360
385
 
361
386
  def update_cluster_info!(node_key = nil)
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.4
4
+ version: 0.0.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: 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