redis-cluster-client 0.3.15 → 0.4.1

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: c0492c31a84cc5a5982973891f9c305d67d6121f03fb12fef76b7045a8960421
4
- data.tar.gz: e2531e6b21591974103c794cea27fa7383fb3c7aef27ab08850d7468bc6555c0
3
+ metadata.gz: 3edb3d83fd54184b6e3069f156944fb1e51459dea9f72f6a149a2fba1fe1cfd8
4
+ data.tar.gz: 02fbd7531bcfcca5363351b3be60c832c43df320748f8641a39073ef6c08490a
5
5
  SHA512:
6
- metadata.gz: 472a05c3122f7847df1fa316ac1e20722abb5c34a299daf2dee71cc71a3cf74437100f97c9ed2105a105b517bdf01066a8702296182126a232d5b7cc463caac0
7
- data.tar.gz: 9c5bfa33629d5ec005e20c8672748967796a150a190a7aa9de46f11225731b0cec6ce02b6e00b7b94ba1703d5bd103e584d538ccf5219a10c713fbc206f07598
6
+ metadata.gz: 9a7aece18852c1b31fad6f4d3fdd43d0201496439ce2bd5a1eea96682d916cfa99217983e96067e29dfc41001c0d35690c2c8054e9dbf299520a564d80568470
7
+ data.tar.gz: 8ba145cc9c9d017c768ae15c44483512d033ac24256bdb46953ec25a9d28d404a6d9a3297cdb31445448ee0d82cda2036328d674db6dc35fab8fe14037ebced1
@@ -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
@@ -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'
@@ -13,6 +14,7 @@ class RedisClient
13
14
  class Router
14
15
  ZERO_CURSOR_FOR_SCAN = '0'
15
16
  METHODS_FOR_BLOCKING_CMD = %i[blocking_call_v blocking_call].freeze
17
+ TSF = ->(b, s) { b.nil? ? s : b.call(s) }.curry
16
18
 
17
19
  attr_reader :node
18
20
 
@@ -29,38 +31,43 @@ class RedisClient
29
31
  def send_command(method, command, *args, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
30
32
  cmd = ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_command(command)
31
33
  case cmd
32
- when 'acl', 'auth', 'bgrewriteaof', 'bgsave', 'quit', 'save'
33
- @node.call_all(method, command, args, &block).first
34
- when 'flushall', 'flushdb'
35
- @node.call_primaries(method, command, args, &block).first
36
- when 'ping' then @node.send_ping(method, command, args, &block).first
34
+ when 'ping' then @node.send_ping(method, command, args).first.then(&TSF.call(block))
37
35
  when 'wait' then send_wait_command(method, command, args, &block)
38
- when 'keys' then @node.call_replicas(method, command, args, &block).flatten.sort_by(&:to_s)
39
- when 'dbsize' then @node.call_replicas(method, command, args, &block).select { |e| e.is_a?(Integer) }.sum
36
+ when 'keys' then @node.call_replicas(method, command, args).flatten.sort_by(&:to_s).then(&TSF.call(block))
37
+ when 'dbsize' then @node.call_replicas(method, command, args).select { |e| e.is_a?(Integer) }.sum.then(&TSF.call(block))
40
38
  when 'scan' then scan(command, seed: 1)
41
- when 'lastsave' then @node.call_all(method, command, args, &block).sort_by(&:to_i)
39
+ when 'lastsave' then @node.call_all(method, command, args).sort_by(&:to_i).then(&TSF.call(block))
42
40
  when 'role' then @node.call_all(method, command, args, &block)
43
41
  when 'config' then send_config_command(method, command, args, &block)
44
42
  when 'client' then send_client_command(method, command, args, &block)
45
43
  when 'cluster' then send_cluster_command(method, command, args, &block)
46
- when 'readonly', 'readwrite', 'shutdown'
47
- raise ::RedisClient::Cluster::OrchestrationCommandNotSupported, cmd
48
44
  when 'memory' then send_memory_command(method, command, args, &block)
49
45
  when 'script' then send_script_command(method, command, args, &block)
50
46
  when 'pubsub' then send_pubsub_command(method, command, args, &block)
47
+ when 'acl', 'auth', 'bgrewriteaof', 'bgsave', 'quit', 'save'
48
+ @node.call_all(method, command, args).first.then(&TSF.call(block))
49
+ when 'flushall', 'flushdb'
50
+ @node.call_primaries(method, command, args).first.then(&TSF.call(block))
51
+ when 'readonly', 'readwrite', 'shutdown'
52
+ raise ::RedisClient::Cluster::OrchestrationCommandNotSupported, cmd
51
53
  when 'discard', 'exec', 'multi', 'unwatch'
52
54
  raise ::RedisClient::Cluster::AmbiguousNodeError, cmd
53
55
  else
54
56
  node = assign_node(command)
55
57
  try_send(node, method, command, args, &block)
56
58
  end
59
+ rescue ::RedisClient::CircuitBreaker::OpenCircuitError
60
+ raise
57
61
  rescue ::RedisClient::Cluster::Node::ReloadNeeded
58
62
  update_cluster_info!
59
63
  raise ::RedisClient::Cluster::NodeMightBeDown
60
64
  rescue ::RedisClient::Cluster::ErrorCollection => e
65
+ raise if e.errors.any?(::RedisClient::CircuitBreaker::OpenCircuitError)
66
+
61
67
  update_cluster_info! if e.errors.values.any? do |err|
62
68
  err.message.start_with?('CLUSTERDOWN Hash slot not served')
63
69
  end
70
+
64
71
  raise
65
72
  end
66
73
 
@@ -72,6 +79,8 @@ class RedisClient
72
79
  else
73
80
  node.public_send(method, *args, command, &block)
74
81
  end
82
+ rescue ::RedisClient::CircuitBreaker::OpenCircuitError
83
+ raise
75
84
  rescue ::RedisClient::CommandError => e
76
85
  raise if retry_count <= 0
77
86
 
@@ -102,6 +111,8 @@ class RedisClient
102
111
 
103
112
  def try_delegate(node, method, *args, retry_count: 3, **kwargs, &block) # rubocop:disable Metrics/AbcSize
104
113
  node.public_send(method, *args, **kwargs, &block)
114
+ rescue ::RedisClient::CircuitBreaker::OpenCircuitError
115
+ raise
105
116
  rescue ::RedisClient::CommandError => e
106
117
  raise if retry_count <= 0
107
118
 
@@ -197,9 +208,10 @@ class RedisClient
197
208
 
198
209
  private
199
210
 
200
- def send_wait_command(method, command, args, retry_count: 3, &block)
201
- @node.call_primaries(method, command, args, &block).select { |r| r.is_a?(Integer) }.sum
211
+ def send_wait_command(method, command, args, retry_count: 3, &block) # rubocop:disable Metrics/AbcSize
212
+ @node.call_primaries(method, command, args).select { |r| r.is_a?(Integer) }.sum.then(&TSF.call(block))
202
213
  rescue ::RedisClient::Cluster::ErrorCollection => e
214
+ raise if e.errors.any?(::RedisClient::CircuitBreaker::OpenCircuitError)
203
215
  raise if retry_count <= 0
204
216
  raise if e.errors.values.none? do |err|
205
217
  err.message.include?('WAIT cannot be used with replica instances')
@@ -213,7 +225,7 @@ class RedisClient
213
225
  def send_config_command(method, command, args, &block)
214
226
  case ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_subcommand(command)
215
227
  when 'resetstat', 'rewrite', 'set'
216
- @node.call_all(method, command, args, &block).first
228
+ @node.call_all(method, command, args).first.then(&TSF.call(block))
217
229
  else assign_node(command).public_send(method, *args, command, &block)
218
230
  end
219
231
  end
@@ -221,16 +233,16 @@ class RedisClient
221
233
  def send_memory_command(method, command, args, &block)
222
234
  case ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_subcommand(command)
223
235
  when 'stats' then @node.call_all(method, command, args, &block)
224
- when 'purge' then @node.call_all(method, command, args, &block).first
236
+ when 'purge' then @node.call_all(method, command, args).first.then(&TSF.call(block))
225
237
  else assign_node(command).public_send(method, *args, command, &block)
226
238
  end
227
239
  end
228
240
 
229
241
  def send_client_command(method, command, args, &block)
230
242
  case ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_subcommand(command)
231
- when 'list' then @node.call_all(method, command, args, &block).flatten
243
+ when 'list' then @node.call_all(method, command, args).flatten.then(&TSF.call(block))
232
244
  when 'pause', 'reply', 'setname'
233
- @node.call_all(method, command, args, &block).first
245
+ @node.call_all(method, command, args).first.then(&TSF.call(block))
234
246
  else assign_node(command).public_send(method, *args, command, &block)
235
247
  end
236
248
  end
@@ -240,7 +252,7 @@ class RedisClient
240
252
  when 'addslots', 'delslots', 'failover', 'forget', 'meet', 'replicate',
241
253
  'reset', 'set-config-epoch', 'setslot'
242
254
  raise ::RedisClient::Cluster::OrchestrationCommandNotSupported, ['cluster', subcommand]
243
- when 'saveconfig' then @node.call_all(method, command, args, &block).first
255
+ when 'saveconfig' then @node.call_all(method, command, args).first.then(&TSF.call(block))
244
256
  when 'getkeysinslot'
245
257
  raise ArgumentError, command.join(' ') if command.size != 4
246
258
 
@@ -249,25 +261,25 @@ class RedisClient
249
261
  end
250
262
  end
251
263
 
252
- def send_script_command(method, command, args, &block)
264
+ def send_script_command(method, command, args, &block) # rubocop:disable Metrics/AbcSize
253
265
  case ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_subcommand(command)
254
266
  when 'debug', 'kill'
255
- @node.call_all(method, command, args, &block).first
267
+ @node.call_all(method, command, args).first.then(&TSF.call(block))
256
268
  when 'flush', 'load'
257
- @node.call_primaries(method, command, args, &block).first
269
+ @node.call_primaries(method, command, args).first.then(&TSF.call(block))
258
270
  when 'exists'
259
- @node.call_all(method, command, args, &block).transpose.map { |arr| arr.any?(&:zero?) ? 0 : 1 }
271
+ @node.call_all(method, command, args).transpose.map { |arr| arr.any?(&:zero?) ? 0 : 1 }.then(&TSF.call(block))
260
272
  else assign_node(command).public_send(method, *args, command, &block)
261
273
  end
262
274
  end
263
275
 
264
276
  def send_pubsub_command(method, command, args, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
265
277
  case ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_subcommand(command)
266
- when 'channels' then @node.call_all(method, command, args, &block).flatten.uniq.sort_by(&:to_s)
278
+ when 'channels' then @node.call_all(method, command, args).flatten.uniq.sort_by(&:to_s).then(&TSF.call(block))
267
279
  when 'numsub'
268
- @node.call_all(method, command, args, &block).reject(&:empty?).map { |e| Hash[*e] }
269
- .reduce({}) { |a, e| a.merge(e) { |_, v1, v2| v1 + v2 } }
270
- when 'numpat' then @node.call_all(method, command, args, &block).select { |e| e.is_a?(Integer) }.sum
280
+ @node.call_all(method, command, args).reject(&:empty?).map { |e| Hash[*e] }
281
+ .reduce({}) { |a, e| a.merge(e) { |_, v1, v2| v1 + v2 } }.then(&TSF.call(block))
282
+ when 'numpat' then @node.call_all(method, command, args).select { |e| e.is_a?(Integer) }.sum.then(&TSF.call(block))
271
283
  else assign_node(command).public_send(method, *args, command, &block)
272
284
  end
273
285
  end
@@ -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,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.3.15
4
+ version: 0.4.1
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-12-21 00:00:00.000000000 Z
11
+ date: 2023-01-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis-client
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.11'
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.11'
26
+ version: '0.12'
27
27
  description:
28
28
  email:
29
29
  - proxy0721@gmail.com