redis-cluster-client 0.3.2 → 0.3.4

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: 7fa6c9386e1ffd7571fdc8a29bea08960387d958149b972e1cf27d28eac98da0
4
- data.tar.gz: e532422c94e7416df967f772112c77861f4501d5dcd73c2d9893237586077677
3
+ metadata.gz: 458c3d9cc4d1f6595510663cede0403953b2555ace0d3cae814663b23f69f1cb
4
+ data.tar.gz: 01d2a34641e298ff7e99cc5f9edbad4ed152e5ca4f18e16181b9a4b84a2492c2
5
5
  SHA512:
6
- metadata.gz: ef29a0346608779f6528559e52e348df690baf44db0cd8c9e1fe94e44bf9d282eca81101a1246cb0bff418ec398b2b207eb47563e2b976b6523e0fe6583e55fb
7
- data.tar.gz: 7e4059d96f02c078989c7bae6cc4790f1580d0fc721c545a86d694ddae7eb90541573c67bb66a93bc6732b45b1f1ac18a3370b0d3d20df41760be9eb78255f5c
6
+ metadata.gz: 3680e6b8b1594001060eb2dea29eb60f0779bba6a43350246b1d5c7e47b1bb1e04a6e1188ac87c1228adac21e86347cb3820679d6c92b693d7f7fedabba88402
7
+ data.tar.gz: 21927b8a14e9ad075e3808e736789c4339af907f5d99e16806273f1553c505e7fc86faabfa067b98f2a3dd7d45a41fb0f89e780e08825aa9df4317531871da5d
@@ -2,10 +2,13 @@
2
2
 
3
3
  require 'redis_client'
4
4
  require 'redis_client/cluster/errors'
5
+ require 'redis_client/cluster/normalized_cmd_name'
5
6
 
6
7
  class RedisClient
7
8
  class Cluster
8
9
  class Command
10
+ EMPTY_STRING = ''
11
+
9
12
  class << self
10
13
  def load(nodes) # rubocop:disable Metrics/MethodLength
11
14
  errors = []
@@ -29,7 +32,10 @@ class RedisClient
29
32
 
30
33
  def parse_command_details(rows)
31
34
  rows&.reject { |row| row[0].nil? }.to_h do |row|
32
- [row[0].downcase, { arity: row[1], flags: row[2], first: row[3], last: row[4], step: row[5] }]
35
+ [
36
+ ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_name(row[0]),
37
+ { arity: row[1], flags: row[2], first: row[3], last: row[4], step: row[5] }
38
+ ]
33
39
  end
34
40
  end
35
41
  end
@@ -40,7 +46,7 @@ class RedisClient
40
46
 
41
47
  def extract_first_key(command)
42
48
  i = determine_first_key_position(command)
43
- return '' if i == 0
49
+ return EMPTY_STRING if i == 0
44
50
 
45
51
  key = (command[i].is_a?(Array) ? command[i].flatten.first : command[i]).to_s
46
52
  hash_tag = extract_hash_tag(key)
@@ -56,7 +62,8 @@ class RedisClient
56
62
  end
57
63
 
58
64
  def exists?(name)
59
- @details.key?(name.to_s.downcase)
65
+ key = ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_name(name)
66
+ @details.key?(key)
60
67
  end
61
68
 
62
69
  private
@@ -72,14 +79,14 @@ class RedisClient
72
79
  end
73
80
 
74
81
  def dig_details(command, key)
75
- name = command&.flatten&.first.to_s.downcase
82
+ name = ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_command(command)
76
83
  return if name.empty? || !@details.key?(name)
77
84
 
78
85
  @details.fetch(name).fetch(key)
79
86
  end
80
87
 
81
88
  def determine_first_key_position(command) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
82
- case command&.flatten&.first.to_s.downcase
89
+ case ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_command(command)
83
90
  when 'eval', 'evalsha', 'zinterstore', 'zunionstore' then 3
84
91
  when 'object' then 2
85
92
  when 'memory'
@@ -104,7 +111,7 @@ class RedisClient
104
111
  s = key.index('{')
105
112
  e = key.index('}', s.to_i + 1)
106
113
 
107
- return '' if s.nil? || e.nil?
114
+ return EMPTY_STRING if s.nil? || e.nil?
108
115
 
109
116
  key[s + 1..e - 1]
110
117
  end
@@ -181,6 +181,8 @@ class RedisClient
181
181
  end
182
182
 
183
183
  def update_slot(slot, node_key)
184
+ return if @mutex.locked?
185
+
184
186
  @mutex.synchronize { @slots[slot] = node_key }
185
187
  end
186
188
 
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'singleton'
4
+
5
+ class RedisClient
6
+ class Cluster
7
+ class NormalizedCmdName
8
+ include Singleton
9
+
10
+ EMPTY_STRING = ''
11
+
12
+ def initialize
13
+ @cache = {}
14
+ @mutex = Mutex.new
15
+ end
16
+
17
+ def get_by_command(command)
18
+ get(command, index: 0)
19
+ end
20
+
21
+ def get_by_subcommand(command)
22
+ get(command, index: 1)
23
+ end
24
+
25
+ def get_by_name(name)
26
+ get(name, index: 0)
27
+ end
28
+
29
+ def clear
30
+ @mutex.synchronize { @cache.clear }
31
+ end
32
+
33
+ private
34
+
35
+ def get(command, index:)
36
+ name = extract_name(command, index: index)
37
+ return EMPTY_STRING if name.nil? || name.empty?
38
+
39
+ normalize(name)
40
+ end
41
+
42
+ def extract_name(command, index:)
43
+ case command
44
+ when String, Symbol then index.zero? ? command : nil
45
+ when Array then extract_name_from_array(command, index: index)
46
+ end
47
+ end
48
+
49
+ def extract_name_from_array(command, index:)
50
+ return if command.size - 1 < index
51
+
52
+ case e = command[index]
53
+ when String, Symbol then e
54
+ when Array then e[index]
55
+ end
56
+ end
57
+
58
+ def normalize(name)
59
+ return @cache[name] if @cache.key?(name)
60
+ return name.to_s.downcase if @mutex.locked?
61
+
62
+ @mutex.synchronize { @cache[name] = name.to_s.downcase }
63
+ @cache[name]
64
+ end
65
+ end
66
+ end
67
+ end
@@ -9,54 +9,48 @@ class RedisClient
9
9
  ReplySizeError = Class.new(::RedisClient::Error)
10
10
  MAX_THREADS = Integer(ENV.fetch('REDIS_CLIENT_MAX_THREADS', 5))
11
11
 
12
- def initialize(router, command_builder)
12
+ def initialize(router, command_builder, seed: Random.new_seed)
13
13
  @router = router
14
14
  @command_builder = command_builder
15
- @grouped = Hash.new([].freeze)
15
+ @grouped = {}
16
16
  @size = 0
17
- @seed = Random.new_seed
17
+ @seed = seed
18
18
  end
19
19
 
20
20
  def call(*args, **kwargs, &block)
21
21
  command = @command_builder.generate(args, kwargs)
22
22
  node_key = @router.find_node_key(command, seed: @seed)
23
- @grouped[node_key] += [[@size, :call_v, command, block]]
24
- @size += 1
23
+ add_line(node_key, [@size, :call_v, command, block])
25
24
  end
26
25
 
27
26
  def call_v(args, &block)
28
27
  command = @command_builder.generate(args)
29
28
  node_key = @router.find_node_key(command, seed: @seed)
30
- @grouped[node_key] += [[@size, :call_v, command, block]]
31
- @size += 1
29
+ add_line(node_key, [@size, :call_v, command, block])
32
30
  end
33
31
 
34
32
  def call_once(*args, **kwargs, &block)
35
33
  command = @command_builder.generate(args, kwargs)
36
34
  node_key = @router.find_node_key(command, seed: @seed)
37
- @grouped[node_key] += [[@size, :call_once_v, command, block]]
38
- @size += 1
35
+ add_line(node_key, [@size, :call_once_v, command, block])
39
36
  end
40
37
 
41
38
  def call_once_v(args, &block)
42
39
  command = @command_builder.generate(args)
43
40
  node_key = @router.find_node_key(command, seed: @seed)
44
- @grouped[node_key] += [[@size, :call_once_v, command, block]]
45
- @size += 1
41
+ add_line(node_key, [@size, :call_once_v, command, block])
46
42
  end
47
43
 
48
44
  def blocking_call(timeout, *args, **kwargs, &block)
49
45
  command = @command_builder.generate(args, kwargs)
50
46
  node_key = @router.find_node_key(command, seed: @seed)
51
- @grouped[node_key] += [[@size, :blocking_call_v, timeout, command, block]]
52
- @size += 1
47
+ add_line(node_key, [@size, :blocking_call_v, timeout, command, block])
53
48
  end
54
49
 
55
50
  def blocking_call_v(timeout, args, &block)
56
51
  command = @command_builder.generate(args)
57
52
  node_key = @router.find_node_key(command, seed: @seed)
58
- @grouped[node_key] += [[@size, :blocking_call_v, timeout, command, block]]
59
- @size += 1
53
+ add_line(node_key, [@size, :blocking_call_v, timeout, command, block])
60
54
  end
61
55
 
62
56
  def empty?
@@ -72,8 +66,11 @@ class RedisClient
72
66
  Thread.new(@router, k, v) do |router, node_key, rows|
73
67
  Thread.pass
74
68
  replies = router.find_node(node_key).pipelined do |pipeline|
75
- rows.each do |(_size, *row, block)|
76
- pipeline.send(*row, &block)
69
+ rows.each do |row|
70
+ case row.size
71
+ when 4 then pipeline.send(row[1], row[2], &row[3])
72
+ when 5 then pipeline.send(row[1], row[2], row[3], &row[4])
73
+ end
77
74
  end
78
75
  end
79
76
 
@@ -92,6 +89,14 @@ class RedisClient
92
89
 
93
90
  raise ::RedisClient::Cluster::ErrorCollection, errors
94
91
  end
92
+
93
+ private
94
+
95
+ def add_line(node_key, line)
96
+ @grouped[node_key] = [] unless @grouped.key?(node_key)
97
+ @grouped[node_key] << line
98
+ @size += 1
99
+ end
95
100
  end
96
101
  end
97
102
  end
@@ -6,6 +6,7 @@ require 'redis_client/cluster/errors'
6
6
  require 'redis_client/cluster/key_slot_converter'
7
7
  require 'redis_client/cluster/node'
8
8
  require 'redis_client/cluster/node_key'
9
+ require 'redis_client/cluster/normalized_cmd_name'
9
10
 
10
11
  class RedisClient
11
12
  class Cluster
@@ -25,7 +26,7 @@ class RedisClient
25
26
  end
26
27
 
27
28
  def send_command(method, command, *args, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
28
- cmd = command.first.to_s.downcase
29
+ cmd = ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_command(command)
29
30
  case cmd
30
31
  when 'acl', 'auth', 'bgrewriteaof', 'bgsave', 'quit', 'save'
31
32
  @node.call_all(method, command, args, &block).first
@@ -65,7 +66,12 @@ class RedisClient
65
66
  # @see https://redis.io/topics/cluster-spec#redirection-and-resharding
66
67
  # Redirection and resharding
67
68
  def try_send(node, method, command, args, retry_count: 3, &block) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
68
- node.send(method, *args, command, &block)
69
+ if args.empty?
70
+ # prevent memory allocation for variable-length args
71
+ node.send(method, command, &block)
72
+ else
73
+ node.send(method, *args, command, &block)
74
+ end
69
75
  rescue ::RedisClient::CommandError => e
70
76
  raise if retry_count <= 0
71
77
 
@@ -193,7 +199,7 @@ class RedisClient
193
199
  end
194
200
 
195
201
  def send_config_command(method, command, args, &block)
196
- case command[1].to_s.downcase
202
+ case ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_subcommand(command)
197
203
  when 'resetstat', 'rewrite', 'set'
198
204
  @node.call_all(method, command, args, &block).first
199
205
  else assign_node(command).send(method, *args, command, &block)
@@ -201,7 +207,7 @@ class RedisClient
201
207
  end
202
208
 
203
209
  def send_memory_command(method, command, args, &block)
204
- case command[1].to_s.downcase
210
+ case ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_subcommand(command)
205
211
  when 'stats' then @node.call_all(method, command, args, &block)
206
212
  when 'purge' then @node.call_all(method, command, args, &block).first
207
213
  else assign_node(command).send(method, *args, command, &block)
@@ -209,7 +215,7 @@ class RedisClient
209
215
  end
210
216
 
211
217
  def send_client_command(method, command, args, &block)
212
- case command[1].to_s.downcase
218
+ case ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_subcommand(command)
213
219
  when 'list' then @node.call_all(method, command, args, &block).flatten
214
220
  when 'pause', 'reply', 'setname'
215
221
  @node.call_all(method, command, args, &block).first
@@ -217,10 +223,8 @@ class RedisClient
217
223
  end
218
224
  end
219
225
 
220
- def send_cluster_command(method, command, args, &block) # rubocop:disable Metrics/MethodLength
221
- subcommand = command[1].to_s.downcase
222
-
223
- case subcommand
226
+ def send_cluster_command(method, command, args, &block)
227
+ case subcommand = ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_subcommand(command)
224
228
  when 'addslots', 'delslots', 'failover', 'forget', 'meet', 'replicate',
225
229
  'reset', 'set-config-epoch', 'setslot'
226
230
  raise ::RedisClient::Cluster::OrchestrationCommandNotSupported, ['cluster', subcommand]
@@ -234,7 +238,7 @@ class RedisClient
234
238
  end
235
239
 
236
240
  def send_script_command(method, command, args, &block)
237
- case command[1].to_s.downcase
241
+ case ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_subcommand(command)
238
242
  when 'debug', 'kill'
239
243
  @node.call_all(method, command, args, &block).first
240
244
  when 'flush', 'load'
@@ -244,7 +248,7 @@ class RedisClient
244
248
  end
245
249
 
246
250
  def send_pubsub_command(method, command, args, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
247
- case command[1].to_s.downcase
251
+ case ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_subcommand(command)
248
252
  when 'channels' then @node.call_all(method, command, args, &block).flatten.uniq.sort_by(&:to_s)
249
253
  when 'numsub'
250
254
  @node.call_all(method, command, args, &block).reject(&:empty?).map { |e| Hash[*e] }
@@ -281,6 +285,8 @@ class RedisClient
281
285
  end
282
286
 
283
287
  def update_cluster_info!
288
+ return if @mutex.locked?
289
+
284
290
  @mutex.synchronize do
285
291
  begin
286
292
  @node.each(&:close)
@@ -78,7 +78,8 @@ class RedisClient
78
78
  end
79
79
 
80
80
  def pipelined
81
- pipeline = ::RedisClient::Cluster::Pipeline.new(@router, @command_builder)
81
+ seed = @config.use_replica? && @config.replica_affinity == :random ? nil : Random.new_seed
82
+ pipeline = ::RedisClient::Cluster::Pipeline.new(@router, @command_builder, seed: seed)
82
83
  yield pipeline
83
84
  return [] if pipeline.empty? == 0
84
85
 
@@ -83,10 +83,14 @@ class RedisClient
83
83
  end
84
84
 
85
85
  def update_node(addrs)
86
+ return if @mutex.locked?
87
+
86
88
  @mutex.synchronize { @node_configs = build_node_configs(addrs) }
87
89
  end
88
90
 
89
91
  def add_node(host, port)
92
+ return if @mutex.locked?
93
+
90
94
  @mutex.synchronize { @node_configs << { host: host, port: port } }
91
95
  end
92
96
 
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.2
4
+ version: 0.3.4
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-09-07 00:00:00.000000000 Z
11
+ date: 2022-09-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis-client
@@ -42,6 +42,7 @@ files:
42
42
  - lib/redis_client/cluster/node/random_replica.rb
43
43
  - lib/redis_client/cluster/node/replica_mixin.rb
44
44
  - lib/redis_client/cluster/node_key.rb
45
+ - lib/redis_client/cluster/normalized_cmd_name.rb
45
46
  - lib/redis_client/cluster/pipeline.rb
46
47
  - lib/redis_client/cluster/pub_sub.rb
47
48
  - lib/redis_client/cluster/router.rb