redis-cluster-client 0.3.15 → 0.4.1

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