redis-cluster-client 0.3.14 → 0.4.0

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