redis-cluster-client 0.3.14 → 0.4.0

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: 909179c8cf02cee3894f4cb11c7d51e8de13d667345daceb3c0f13306b661613
4
- data.tar.gz: 959e61ae91001a48177cc19e72e4e7af3f95224481080751cebf2c8d04f2665c
3
+ metadata.gz: 780e5f1653b6633dbd4bb1bdc4f4da174d70176a5287bf6156ad5784f0612cf1
4
+ data.tar.gz: ca1681357111afdc82bdf32a18985963dba85f89fd49961ac62c56075daa0cc8
5
5
  SHA512:
6
- metadata.gz: c5e4ce403a09a18bfa94d5e0bf70db3277d16bcb05a8d300b4555ecb4f3b743a50af10a19ffe5988f8f60e06813a5dd147895410fb02b1f15522907a6dfe4143
7
- data.tar.gz: 56154b82f8ced444d757df7b6438d58d2d2d187917f61aa40e78b980811e7b597d090c1a982d72b6ca100fa6e606a7719faeec9eef0688d102058d4976178e41
6
+ metadata.gz: 4111e4b1305d955d3eb56df912debd0cfbbd388bae1d841cf1b9274a21d2e121fb511f1630d85f6c503d53ebf514503303b94402d3416517886b93f615584a22
7
+ data.tar.gz: 1f6e2be52bcd834482d17bebfeead2b8d1346c954d03b61251dd973ab19f27275e53571d73ba6425dceedc34dc07cd5e6dbb98fa46de00281b6d00eeb1c87f08
@@ -43,7 +43,7 @@ class RedisClient
43
43
  clients.each_slice(::RedisClient::Cluster::Node::MAX_THREADS).each_with_object({}) do |chuncked_clients, acc|
44
44
  threads = chuncked_clients.map do |k, v|
45
45
  Thread.new(k, v) do |node_key, client|
46
- Thread.current.thread_variable_set(:node_key, node_key)
46
+ Thread.current[:node_key] = node_key
47
47
 
48
48
  min = DUMMY_LATENCY_NSEC
49
49
  MEASURE_ATTEMPT_COUNT.times do
@@ -53,15 +53,15 @@ class RedisClient
53
53
  min = duration if duration < min
54
54
  end
55
55
 
56
- Thread.current.thread_variable_set(:latency, min)
56
+ Thread.current[:latency] = min
57
57
  rescue StandardError
58
- Thread.current.thread_variable_set(:latency, DUMMY_LATENCY_NSEC)
58
+ Thread.current[:latency] = DUMMY_LATENCY_NSEC
59
59
  end
60
60
  end
61
61
 
62
62
  threads.each do |t|
63
63
  t.join
64
- acc[t.thread_variable_get(:node_key)] = t.thread_variable_get(:latency)
64
+ acc[t[:node_key]] = t[:latency]
65
65
  end
66
66
  end
67
67
  end
@@ -18,7 +18,6 @@ class RedisClient
18
18
  MAX_STARTUP_SAMPLE = 37
19
19
  MAX_THREADS = Integer(ENV.fetch('REDIS_CLIENT_MAX_THREADS', 5))
20
20
  IGNORE_GENERIC_CONFIG_KEYS = %i[url host port path].freeze
21
- SLOT_OPTIMIZATION_STRING = '0' * SLOT_SIZE
22
21
 
23
22
  ReloadNeeded = Class.new(::RedisClient::Error)
24
23
 
@@ -37,27 +36,36 @@ class RedisClient
37
36
  end
38
37
  end
39
38
 
40
- Slot = Struct.new('StringArray', :string, :elements, keyword_init: true) do
39
+ class CharArray
40
+ BASE = ''
41
+ PADDING = '0'
42
+
43
+ def initialize(size, elements)
44
+ @elements = elements
45
+ @string = String.new(BASE, encoding: Encoding::BINARY, capacity: size)
46
+ size.times { @string << PADDING }
47
+ end
48
+
41
49
  def [](index)
42
50
  raise IndexError if index < 0
43
- return if index >= string.bytesize
51
+ return if index >= @string.bytesize
44
52
 
45
- elements[string.getbyte(index)]
53
+ @elements[@string.getbyte(index)]
46
54
  end
47
55
 
48
56
  def []=(index, element)
49
57
  raise IndexError if index < 0
50
- return if index >= string.bytesize
58
+ return if index >= @string.bytesize
51
59
 
52
- pos = elements.find_index(element) # O(N)
60
+ pos = @elements.find_index(element) # O(N)
53
61
  if pos.nil?
54
- raise(RangeError, 'full of elements') if elements.size >= 256
62
+ raise(RangeError, 'full of elements') if @elements.size >= 256
55
63
 
56
- pos = elements.size
57
- elements << element
64
+ pos = @elements.size
65
+ @elements << element
58
66
  end
59
67
 
60
- string.setbyte(index, pos)
68
+ @string.setbyte(index, pos)
61
69
  end
62
70
  end
63
71
 
@@ -85,11 +93,11 @@ class RedisClient
85
93
  startup_nodes.each_slice(MAX_THREADS).with_index do |chuncked_startup_nodes, chuncked_idx|
86
94
  threads = chuncked_startup_nodes.each_with_index.map do |raw_client, idx|
87
95
  Thread.new(raw_client, (MAX_THREADS * chuncked_idx) + idx) do |cli, i|
88
- Thread.current.thread_variable_set(:index, i)
96
+ Thread.current[:index] = i
89
97
  reply = cli.call('CLUSTER', 'NODES')
90
- Thread.current.thread_variable_set(:info, parse_cluster_node_reply(reply))
98
+ Thread.current[:info] = parse_cluster_node_reply(reply)
91
99
  rescue StandardError => e
92
- Thread.current.thread_variable_set(:error, e)
100
+ Thread.current[:error] = e
93
101
  ensure
94
102
  cli&.close
95
103
  end
@@ -97,12 +105,12 @@ class RedisClient
97
105
 
98
106
  threads.each do |t|
99
107
  t.join
100
- if t.thread_variable?(:info)
108
+ if t.key?(:info)
101
109
  node_info_list ||= Array.new(startup_size)
102
- node_info_list[t.thread_variable_get(:index)] = t.thread_variable_get(:info)
103
- elsif t.thread_variable?(:error)
110
+ node_info_list[t[:index]] = t[:info]
111
+ elsif t.key?(:error)
104
112
  errors ||= Array.new(startup_size)
105
- errors[t.thread_variable_get(:index)] = t.thread_variable_get(:error)
113
+ errors[t[:index]] = t[:error]
106
114
  end
107
115
  end
108
116
  end
@@ -268,10 +276,8 @@ class RedisClient
268
276
  def make_array_for_slot_node_mappings(node_info_list)
269
277
  return Array.new(SLOT_SIZE) if node_info_list.count(&:primary?) > 256
270
278
 
271
- ::RedisClient::Cluster::Node::Slot.new(
272
- string: String.new(SLOT_OPTIMIZATION_STRING, encoding: Encoding::BINARY, capacity: SLOT_SIZE),
273
- elements: node_info_list.select(&:primary?).map(&:node_key)
274
- )
279
+ primary_node_keys = node_info_list.select(&:primary?).map(&:node_key)
280
+ ::RedisClient::Cluster::Node::CharArray.new(SLOT_SIZE, primary_node_keys)
275
281
  end
276
282
 
277
283
  def build_replication_mappings(node_info_list) # rubocop:disable Metrics/AbcSize
@@ -285,7 +291,7 @@ class RedisClient
285
291
 
286
292
  def call_multiple_nodes(clients, method, command, args, &block)
287
293
  results, errors = try_map(clients) do |_, client|
288
- client.send(method, *args, command, &block)
294
+ client.public_send(method, *args, command, &block)
289
295
  end
290
296
 
291
297
  [results&.values, errors]
@@ -303,22 +309,22 @@ class RedisClient
303
309
  clients.each_slice(MAX_THREADS) do |chuncked_clients|
304
310
  threads = chuncked_clients.map do |k, v|
305
311
  Thread.new(k, v) do |node_key, client|
306
- Thread.current.thread_variable_set(:node_key, node_key)
312
+ Thread.current[:node_key] = node_key
307
313
  reply = yield(node_key, client)
308
- Thread.current.thread_variable_set(:result, reply)
314
+ Thread.current[:result] = reply
309
315
  rescue StandardError => e
310
- Thread.current.thread_variable_set(:error, e)
316
+ Thread.current[:error] = e
311
317
  end
312
318
  end
313
319
 
314
320
  threads.each do |t|
315
321
  t.join
316
- if t.thread_variable?(:result)
322
+ if t.key?(:result)
317
323
  results ||= {}
318
- results[t.thread_variable_get(:node_key)] = t.thread_variable_get(:result)
319
- elsif t.thread_variable?(:error)
324
+ results[t[:node_key]] = t[:result]
325
+ elsif t.key?(:error)
320
326
  errors ||= {}
321
- errors[t.thread_variable_get(:node_key)] = t.thread_variable_get(:error)
327
+ errors[t[:node_key]] = t[:error]
322
328
  end
323
329
  end
324
330
  end
@@ -150,34 +150,34 @@ class RedisClient
150
150
  @pipelines&.each_slice(MAX_THREADS) do |chuncked_pipelines|
151
151
  threads = chuncked_pipelines.map do |node_key, pipeline|
152
152
  Thread.new(node_key, pipeline) do |nk, pl|
153
- Thread.current.thread_variable_set(:node_key, nk)
153
+ Thread.current[:node_key] = nk
154
154
  replies = do_pipelining(@router.find_node(nk), pl)
155
155
  raise ReplySizeError, "commands: #{pl._size}, replies: #{replies.size}" if pl._size != replies.size
156
156
 
157
- Thread.current.thread_variable_set(:replies, replies)
157
+ Thread.current[:replies] = replies
158
158
  rescue ::RedisClient::Cluster::Pipeline::RedirectionNeeded => e
159
- Thread.current.thread_variable_set(:redirection_needed, e)
159
+ Thread.current[:redirection_needed] = e
160
160
  rescue StandardError => e
161
- Thread.current.thread_variable_set(:error, e)
161
+ Thread.current[:error] = e
162
162
  end
163
163
  end
164
164
 
165
165
  threads.each(&:join)
166
166
  threads.each do |t|
167
- if t.thread_variable?(:replies)
167
+ if t.key?(:replies)
168
168
  all_replies ||= Array.new(@size)
169
- @pipelines[t.thread_variable_get(:node_key)]
169
+ @pipelines[t[:node_key]]
170
170
  .outer_indices
171
- .each_with_index { |outer, inner| all_replies[outer] = t.thread_variable_get(:replies)[inner] }
172
- elsif t.thread_variable?(:redirection_needed)
171
+ .each_with_index { |outer, inner| all_replies[outer] = t[:replies][inner] }
172
+ elsif t.key?(:redirection_needed)
173
173
  all_replies ||= Array.new(@size)
174
- pipeline = @pipelines[t.thread_variable_get(:node_key)]
175
- err = t.thread_variable_get(:redirection_needed)
174
+ pipeline = @pipelines[t[:node_key]]
175
+ err = t[:redirection_needed]
176
176
  err.indices.each { |i| err.replies[i] = handle_redirection(err.replies[i], pipeline, i) }
177
177
  pipeline.outer_indices.each_with_index { |outer, inner| all_replies[outer] = err.replies[inner] }
178
- elsif t.thread_variable?(:error)
178
+ elsif t.key?(:error)
179
179
  errors ||= {}
180
- errors[t.thread_variable_get(:node_key)] = t.thread_variable_get(:error)
180
+ errors[t[:node_key]] = t[:error]
181
181
  end
182
182
  end
183
183
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'redis_client'
4
+ require 'redis_client/circuit_breaker'
4
5
  require 'redis_client/cluster/command'
5
6
  require 'redis_client/cluster/errors'
6
7
  require 'redis_client/cluster/key_slot_converter'
@@ -54,13 +55,18 @@ class RedisClient
54
55
  node = assign_node(command)
55
56
  try_send(node, method, command, args, &block)
56
57
  end
58
+ rescue ::RedisClient::CircuitBreaker::OpenCircuitError
59
+ raise
57
60
  rescue ::RedisClient::Cluster::Node::ReloadNeeded
58
61
  update_cluster_info!
59
62
  raise ::RedisClient::Cluster::NodeMightBeDown
60
63
  rescue ::RedisClient::Cluster::ErrorCollection => e
64
+ raise if e.errors.any?(::RedisClient::CircuitBreaker::OpenCircuitError)
65
+
61
66
  update_cluster_info! if e.errors.values.any? do |err|
62
67
  err.message.start_with?('CLUSTERDOWN Hash slot not served')
63
68
  end
69
+
64
70
  raise
65
71
  end
66
72
 
@@ -68,10 +74,12 @@ class RedisClient
68
74
  def try_send(node, method, command, args, retry_count: 3, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
69
75
  if args.empty?
70
76
  # prevent memory allocation for variable-length args
71
- node.send(method, command, &block)
77
+ node.public_send(method, command, &block)
72
78
  else
73
- node.send(method, *args, command, &block)
79
+ node.public_send(method, *args, command, &block)
74
80
  end
81
+ rescue ::RedisClient::CircuitBreaker::OpenCircuitError
82
+ raise
75
83
  rescue ::RedisClient::CommandError => e
76
84
  raise if retry_count <= 0
77
85
 
@@ -101,7 +109,9 @@ class RedisClient
101
109
  end
102
110
 
103
111
  def try_delegate(node, method, *args, retry_count: 3, **kwargs, &block) # rubocop:disable Metrics/AbcSize
104
- node.send(method, *args, **kwargs, &block)
112
+ node.public_send(method, *args, **kwargs, &block)
113
+ rescue ::RedisClient::CircuitBreaker::OpenCircuitError
114
+ raise
105
115
  rescue ::RedisClient::CommandError => e
106
116
  raise if retry_count <= 0
107
117
 
@@ -197,9 +207,10 @@ class RedisClient
197
207
 
198
208
  private
199
209
 
200
- def send_wait_command(method, command, args, retry_count: 3, &block)
210
+ def send_wait_command(method, command, args, retry_count: 3, &block) # rubocop:disable Metrics/AbcSize
201
211
  @node.call_primaries(method, command, args, &block).select { |r| r.is_a?(Integer) }.sum
202
212
  rescue ::RedisClient::Cluster::ErrorCollection => e
213
+ raise if e.errors.any?(::RedisClient::CircuitBreaker::OpenCircuitError)
203
214
  raise if retry_count <= 0
204
215
  raise if e.errors.values.none? do |err|
205
216
  err.message.include?('WAIT cannot be used with replica instances')
@@ -214,7 +225,7 @@ class RedisClient
214
225
  case ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_subcommand(command)
215
226
  when 'resetstat', 'rewrite', 'set'
216
227
  @node.call_all(method, command, args, &block).first
217
- else assign_node(command).send(method, *args, command, &block)
228
+ else assign_node(command).public_send(method, *args, command, &block)
218
229
  end
219
230
  end
220
231
 
@@ -222,7 +233,7 @@ class RedisClient
222
233
  case ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_subcommand(command)
223
234
  when 'stats' then @node.call_all(method, command, args, &block)
224
235
  when 'purge' then @node.call_all(method, command, args, &block).first
225
- else assign_node(command).send(method, *args, command, &block)
236
+ else assign_node(command).public_send(method, *args, command, &block)
226
237
  end
227
238
  end
228
239
 
@@ -231,7 +242,7 @@ class RedisClient
231
242
  when 'list' then @node.call_all(method, command, args, &block).flatten
232
243
  when 'pause', 'reply', 'setname'
233
244
  @node.call_all(method, command, args, &block).first
234
- else assign_node(command).send(method, *args, command, &block)
245
+ else assign_node(command).public_send(method, *args, command, &block)
235
246
  end
236
247
  end
237
248
 
@@ -244,8 +255,8 @@ class RedisClient
244
255
  when 'getkeysinslot'
245
256
  raise ArgumentError, command.join(' ') if command.size != 4
246
257
 
247
- find_node(@node.find_node_key_of_replica(command[2])).send(method, *args, command, &block)
248
- else assign_node(command).send(method, *args, command, &block)
258
+ find_node(@node.find_node_key_of_replica(command[2])).public_send(method, *args, command, &block)
259
+ else assign_node(command).public_send(method, *args, command, &block)
249
260
  end
250
261
  end
251
262
 
@@ -255,7 +266,9 @@ class RedisClient
255
266
  @node.call_all(method, command, args, &block).first
256
267
  when 'flush', 'load'
257
268
  @node.call_primaries(method, command, args, &block).first
258
- else assign_node(command).send(method, *args, command, &block)
269
+ when 'exists'
270
+ @node.call_all(method, command, args, &block).transpose.map { |arr| arr.any?(&:zero?) ? 0 : 1 }
271
+ else assign_node(command).public_send(method, *args, command, &block)
259
272
  end
260
273
  end
261
274
 
@@ -266,7 +279,7 @@ class RedisClient
266
279
  @node.call_all(method, command, args, &block).reject(&:empty?).map { |e| Hash[*e] }
267
280
  .reduce({}) { |a, e| a.merge(e) { |_, v1, v2| v1 + v2 } }
268
281
  when 'numpat' then @node.call_all(method, command, args, &block).select { |e| e.is_a?(Integer) }.sum
269
- else assign_node(command).send(method, *args, command, &block)
282
+ else assign_node(command).public_send(method, *args, command, &block)
270
283
  end
271
284
  end
272
285
 
@@ -98,7 +98,7 @@ class RedisClient
98
98
 
99
99
  def build_node_configs(addrs)
100
100
  configs = Array[addrs].flatten.filter_map { |addr| parse_node_addr(addr) }
101
- raise InvalidClientConfigError, '`nodes` option is empty' if configs.size.zero?
101
+ raise InvalidClientConfigError, '`nodes` option is empty' if configs.empty?
102
102
 
103
103
  configs
104
104
  end
@@ -150,7 +150,7 @@ class RedisClient
150
150
  end
151
151
 
152
152
  def merge_generic_config(client_config, node_configs)
153
- return client_config if node_configs.size.zero?
153
+ return client_config if node_configs.empty?
154
154
 
155
155
  cfg = node_configs.first
156
156
  MERGE_CONFIG_KEYS.each { |k| client_config[k] = cfg[k] if cfg.key?(k) }
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis-cluster-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.14
4
+ version: 0.4.0
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-10-13 00:00:00.000000000 Z
11
+ date: 2023-01-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis-client
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.10'
19
+ version: '0.12'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0.10'
26
+ version: '0.12'
27
27
  description:
28
28
  email:
29
29
  - proxy0721@gmail.com
@@ -69,7 +69,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
69
69
  - !ruby/object:Gem::Version
70
70
  version: '0'
71
71
  requirements: []
72
- rubygems_version: 3.3.22
72
+ rubygems_version: 3.3.23
73
73
  signing_key:
74
74
  specification_version: 4
75
75
  summary: A Redis cluster client for Ruby