redis-cluster-client 0.0.4 → 0.0.7

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: 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