redis-cluster-client 0.16.2 → 0.16.3

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: f8d639d8939c9fdf304fa0f61886c1274f6e1c08bcd9e213dae3c7e0d844be02
4
- data.tar.gz: 6f540ce88572df689c8a3e3971e3e96054b09b9fee7ef53c9aa518566ec3dd75
3
+ metadata.gz: 5c16432e083671229128632c116401dc9b1bd56f6b3b1e3c8f107956901d29f2
4
+ data.tar.gz: b464a6f87e8c952a5e4f5b22d0609a9be09b3c6c7817eb91ed779e0b142a4290
5
5
  SHA512:
6
- metadata.gz: 7d9c164a6590fb0ab3fa43328b484f8919895f17d3a6d5a9114c0c7b1a7867864be6d8e12ae1af45cdc81d39b9aa590e8c088de878ab964c18d9ae6f3c58682d
7
- data.tar.gz: 01c0503f5f75d2441bdd2ea71537114c380db552e00ed8b130b0330626440f58a0d94bf94974dc044f4aebca9e024c931635dd8cbe5c96e225c80a7e10120840
6
+ metadata.gz: 0b94204a193f0bfe56c75da819651c8c16c72bd9620c51996f2dcfc8b2c7faefc416c342ca7631772c96d4fec7a9e7c8aede9638c28fb540855603b829a1042d
7
+ data.tar.gz: 6d873858848aa00124a043efce6ee8fdd629516e8c5447802b079636de1dd4782b0584c84d791ed0bc7c589d66c273e985878cdea238054e60f1a5a5a2ce72c9
@@ -9,18 +9,57 @@ class RedisClient
9
9
  class Command
10
10
  EMPTY_STRING = ''
11
11
  EMPTY_HASH = {}.freeze
12
- EMPTY_ARRAY = [].freeze
13
12
 
14
- private_constant :EMPTY_STRING, :EMPTY_HASH, :EMPTY_ARRAY
13
+ private_constant :EMPTY_HASH
15
14
 
16
- Detail = Struct.new(
15
+ Spec = Struct.new(
17
16
  'RedisCommand',
18
17
  :first_key_position,
19
18
  :key_step,
20
19
  :write?,
21
20
  :readonly?,
22
21
  keyword_init: true
23
- )
22
+ ) do
23
+ def extract_first_key(command)
24
+ i = first_key_position.to_i
25
+ return command[i] if i > 0
26
+
27
+ i = determine_first_key_position(command)
28
+ return ::RedisClient::Cluster::Command::EMPTY_STRING if i == 0
29
+
30
+ command[i]
31
+ end
32
+
33
+ def should_send_to_primary?
34
+ write?
35
+ end
36
+
37
+ def should_send_to_replica?
38
+ readonly?
39
+ end
40
+
41
+ private
42
+
43
+ def determine_first_key_position(command) # rubocop:disable Metrics/AbcSize
44
+ cmd_name = command.first
45
+ if cmd_name.casecmp('xread').zero?
46
+ determine_optional_key_position(command, 'streams')
47
+ elsif cmd_name.casecmp('xreadgroup').zero?
48
+ determine_optional_key_position(command, 'streams')
49
+ elsif cmd_name.casecmp('migrate').zero?
50
+ command[3].empty? ? determine_optional_key_position(command, 'keys') : 3
51
+ elsif cmd_name.casecmp('memory').zero?
52
+ command[1].to_s.casecmp('usage').zero? ? 2 : 0
53
+ else
54
+ 0
55
+ end
56
+ end
57
+
58
+ def determine_optional_key_position(command, option_name)
59
+ i = command.index { |v| v.to_s.casecmp(option_name).zero? }
60
+ i.nil? ? 0 : i + 1
61
+ end
62
+ end
24
63
 
25
64
  class << self
26
65
  def load(nodes, slow_command_timeout: -1) # rubocop:disable Metrics/AbcSize
@@ -65,7 +104,7 @@ class RedisClient
65
104
  else row[2].include?('write')
66
105
  end
67
106
 
68
- acc[row.first] = ::RedisClient::Cluster::Command::Detail.new(
107
+ acc[row.first] = ::RedisClient::Cluster::Command::Spec.new(
69
108
  first_key_position: pos,
70
109
  key_step: row[5],
71
110
  write?: writable,
@@ -79,53 +118,13 @@ class RedisClient
79
118
  @commands = commands || EMPTY_HASH
80
119
  end
81
120
 
82
- def extract_first_key(command)
83
- i = determine_first_key_position(command)
84
- return EMPTY_STRING if i == 0
85
-
86
- command[i]
87
- end
88
-
89
- def should_send_to_primary?(command)
90
- find_command_info(command.first)&.write?
91
- end
92
-
93
- def should_send_to_replica?(command)
94
- find_command_info(command.first)&.readonly?
121
+ def get_spec(name)
122
+ @commands[name] || @commands[name.to_s.downcase(:ascii)]
95
123
  end
96
124
 
97
125
  def exists?(name)
98
126
  @commands.key?(name) || @commands.key?(name.to_s.downcase(:ascii))
99
127
  end
100
-
101
- private
102
-
103
- def find_command_info(name)
104
- @commands[name] || @commands[name.to_s.downcase(:ascii)]
105
- end
106
-
107
- def determine_first_key_position(command) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/PerceivedComplexity
108
- i = find_command_info(command.first)&.first_key_position.to_i
109
- return i if i > 0
110
-
111
- cmd_name = command.first
112
- if cmd_name.casecmp('xread').zero?
113
- determine_optional_key_position(command, 'streams')
114
- elsif cmd_name.casecmp('xreadgroup').zero?
115
- determine_optional_key_position(command, 'streams')
116
- elsif cmd_name.casecmp('migrate').zero?
117
- command[3].empty? ? determine_optional_key_position(command, 'keys') : 3
118
- elsif cmd_name.casecmp('memory').zero?
119
- command[1].to_s.casecmp('usage').zero? ? 2 : 0
120
- else
121
- i
122
- end
123
- end
124
-
125
- def determine_optional_key_position(command, option_name)
126
- i = command.index { |v| v.to_s.casecmp(option_name).zero? }
127
- i.nil? ? 0 : i + 1
128
- end
129
128
  end
130
129
  end
131
130
  end
@@ -3,17 +3,57 @@
3
3
  class RedisClient
4
4
  class Cluster
5
5
  module ConcurrentWorker
6
- class None
7
- def new_group(size:)
8
- ::RedisClient::Cluster::ConcurrentWorker::Group.new(
9
- worker: self,
10
- queue: [],
11
- size: size
6
+ module None
7
+ class Group
8
+ Task = Struct.new(
9
+ 'RedisClusterClientSingleThreadTask',
10
+ :id, :result, keyword_init: true
12
11
  )
12
+
13
+ def initialize(size:)
14
+ @tasks = Array.new(size)
15
+ @idx = 0
16
+ end
17
+
18
+ def push(id, *args, **kwargs, &block)
19
+ raise InvalidNumberOfTasks, "max size reached: #{@idx}" if @idx == @tasks.size
20
+
21
+ result = exec(*args, **kwargs, &block)
22
+ @tasks[@idx] = Task.new(id: id, result: result)
23
+ @idx += 1
24
+ nil
25
+ end
26
+
27
+ def each
28
+ raise InvalidNumberOfTasks, "expected: #{@tasks.size}, actual: #{@idx}" if @idx != @tasks.size
29
+
30
+ @tasks.each { |task| yield(task.id, task.result) }
31
+ nil
32
+ end
33
+
34
+ def close
35
+ @idx = 0
36
+ @tasks.clear
37
+ nil
38
+ end
39
+
40
+ def inspect
41
+ "#<#{self.class.name} size: #{@idx}, max: #{@tasks.size}>"
42
+ end
43
+
44
+ private
45
+
46
+ def exec(*args, **kwargs, &block)
47
+ block&.call(*args, **kwargs)
48
+ rescue StandardError => e
49
+ e
50
+ end
13
51
  end
14
52
 
15
- def push(task)
16
- task.exec
53
+ module_function
54
+
55
+ def new_group(size:)
56
+ Group.new(size: size)
17
57
  end
18
58
 
19
59
  def close; end
@@ -73,7 +73,7 @@ class RedisClient
73
73
 
74
74
  def create(model: :none, size: 5)
75
75
  case model
76
- when :none then ::RedisClient::Cluster::ConcurrentWorker::None.new
76
+ when :none then ::RedisClient::Cluster::ConcurrentWorker::None
77
77
  when :on_demand then ::RedisClient::Cluster::ConcurrentWorker::OnDemand.new(size: size)
78
78
  when :pooled then ::RedisClient::Cluster::ConcurrentWorker::Pooled.new(size: size)
79
79
  else raise ArgumentError, "unknown model: #{model}"
@@ -25,8 +25,7 @@ class RedisClient
25
25
  end
26
26
 
27
27
  def any_primary_node_key(seed: nil)
28
- random = seed.nil? ? Random : Random.new(seed)
29
- @primary_node_keys.sample(random: random)
28
+ @primary_node_keys.sample(random: make_random(seed))
30
29
  end
31
30
 
32
31
  def process_topology_update!(replications, options) # rubocop:disable Metrics/AbcSize
@@ -59,6 +58,11 @@ class RedisClient
59
58
  @clients[node_key] = client
60
59
  end
61
60
  end
61
+
62
+ def make_random(seed)
63
+ # OPTIMIZE: Figure out the most elegant way to pin a node during a pipeline or scan.
64
+ seed.nil? ? Random : Random.new(seed)
65
+ end
62
66
  end
63
67
  end
64
68
  end
@@ -20,8 +20,7 @@ class RedisClient
20
20
  end
21
21
 
22
22
  def any_replica_node_key(seed: nil)
23
- random = seed.nil? ? Random : Random.new(seed)
24
- @existed_replicas.sample(random: random)&.first || any_primary_node_key(seed: seed)
23
+ @existed_replicas.sample(random: make_random(seed))&.first || any_primary_node_key(seed: seed)
25
24
  end
26
25
 
27
26
  def process_topology_update!(replications, options)
@@ -18,8 +18,7 @@ class RedisClient
18
18
  end
19
19
 
20
20
  def any_primary_node_key(seed: nil)
21
- random = seed.nil? ? Random : Random.new(seed)
22
- @primary_node_keys.sample(random: random)
21
+ @primary_node_keys.sample(random: make_random(seed))
23
22
  end
24
23
 
25
24
  alias any_replica_node_key any_primary_node_key
@@ -12,7 +12,7 @@ class RedisClient
12
12
  end
13
13
 
14
14
  def clients_for_scanning(seed: nil)
15
- random = seed.nil? ? Random : Random.new(seed)
15
+ random = make_random(seed)
16
16
  keys = @replications.map do |primary_node_key, replica_node_keys|
17
17
  replica_node_keys.empty? ? primary_node_key : replica_node_keys.sample(random: random)
18
18
  end
@@ -21,13 +21,13 @@ class RedisClient
21
21
  end
22
22
 
23
23
  def find_node_key_of_replica(primary_node_key, seed: nil)
24
- random = seed.nil? ? Random : Random.new(seed)
25
- @replications.fetch(primary_node_key, EMPTY_ARRAY).sample(random: random) || primary_node_key
24
+ replica_node_keys = @replications.fetch(primary_node_key, EMPTY_ARRAY)
25
+ replica_node_key = replica_node_keys.size <= 1 ? replica_node_keys.first : replica_node_keys.sample(random: make_random(seed))
26
+ replica_node_key || primary_node_key
26
27
  end
27
28
 
28
29
  def any_replica_node_key(seed: nil)
29
- random = seed.nil? ? Random : Random.new(seed)
30
- @replica_node_keys.sample(random: random) || any_primary_node_key(seed: seed)
30
+ @replica_node_keys.sample(random: make_random(seed)) || any_primary_node_key(seed: seed)
31
31
  end
32
32
  end
33
33
  end
@@ -12,7 +12,7 @@ class RedisClient
12
12
  end
13
13
 
14
14
  def clients_for_scanning(seed: nil)
15
- random = seed.nil? ? Random : Random.new(seed)
15
+ random = make_random(seed)
16
16
  keys = @replications.map do |primary_node_key, replica_node_keys|
17
17
  decide_use_primary?(random, replica_node_keys.size) ? primary_node_key : replica_node_keys.sample(random: random)
18
18
  end
@@ -21,7 +21,7 @@ class RedisClient
21
21
  end
22
22
 
23
23
  def find_node_key_of_replica(primary_node_key, seed: nil)
24
- random = seed.nil? ? Random : Random.new(seed)
24
+ random = make_random(seed)
25
25
 
26
26
  replica_node_keys = @replications.fetch(primary_node_key, EMPTY_ARRAY)
27
27
  if decide_use_primary?(random, replica_node_keys.size)
@@ -32,8 +32,7 @@ class RedisClient
32
32
  end
33
33
 
34
34
  def any_replica_node_key(seed: nil)
35
- random = seed.nil? ? Random : Random.new(seed)
36
- @replica_node_keys.sample(random: random) || any_primary_node_key(seed: seed)
35
+ @replica_node_keys.sample(random: make_random(seed)) || any_primary_node_key(seed: seed)
37
36
  end
38
37
 
39
38
  private
@@ -230,10 +230,10 @@ class RedisClient
230
230
 
231
231
  def append_pipeline(node_key)
232
232
  @pipelines ||= {}
233
- @pipelines[node_key] ||= ::RedisClient::Cluster::Pipeline::Extended.new(::RedisClient::Cluster::NoopCommandBuilder)
234
- @pipelines[node_key].add_outer_index(@size)
233
+ pi = (@pipelines[node_key] ||= ::RedisClient::Cluster::Pipeline::Extended.new(::RedisClient::Cluster::NoopCommandBuilder))
234
+ pi.add_outer_index(@size)
235
235
  @size += 1
236
- @pipelines[node_key]
236
+ pi
237
237
  end
238
238
 
239
239
  def do_pipelining(client, pipeline)
@@ -246,23 +246,27 @@ class RedisClient
246
246
  end
247
247
 
248
248
  def find_node_key(command, seed: nil)
249
- key = @command.extract_first_key(command)
250
- find_node_key_by_key(key, seed: seed, primary: @command.should_send_to_primary?(command))
249
+ cmd_spec = @command.get_spec(command.first)
250
+ find_node_key_by_key(
251
+ cmd_spec&.extract_first_key(command),
252
+ seed: seed,
253
+ primary: cmd_spec&.should_send_to_primary?
254
+ )
251
255
  end
252
256
 
253
257
  def find_primary_node_key(command)
254
- key = @command.extract_first_key(command)
255
- return nil unless key&.size&.> 0
258
+ key = @command.get_spec(command.first)&.extract_first_key(command)
259
+ return unless key&.size&.> 0
256
260
 
257
261
  find_node_key_by_key(key, primary: true)
258
262
  end
259
263
 
260
264
  def find_slot(command)
261
- find_slot_by_key(@command.extract_first_key(command))
265
+ find_slot_by_key(@command.get_spec(command.first)&.extract_first_key(command))
262
266
  end
263
267
 
264
268
  def find_slot_by_key(key)
265
- return if key.empty?
269
+ return if key.nil? || key.empty?
266
270
 
267
271
  ::RedisClient::Cluster::KeySlotConverter.convert(key)
268
272
  end
@@ -467,8 +471,13 @@ class RedisClient
467
471
 
468
472
  return assign_node_and_send_command(method, command, args, &block) if command.size <= keys_step + 1 || ::RedisClient::Cluster::KeySlotConverter.hash_tag_included?(command[1])
469
473
 
470
- seed = @config.use_replica? && @config.replica_affinity == :random ? nil : Random.new_seed
471
- pipeline = ::RedisClient::Cluster::Pipeline.new(self, @command_builder, @concurrent_worker, exception: true, seed: seed)
474
+ pipeline = ::RedisClient::Cluster::Pipeline.new(
475
+ self,
476
+ @command_builder,
477
+ @concurrent_worker,
478
+ exception: true,
479
+ seed: Random.new_seed
480
+ )
472
481
 
473
482
  single_command = Array.new(keys_step + 1)
474
483
  single_command[0] = single_key_cmd
@@ -95,13 +95,12 @@ class RedisClient
95
95
  end
96
96
 
97
97
  def pipelined(exception: true)
98
- seed = @config.use_replica? && @config.replica_affinity == :random ? nil : Random.new_seed
99
98
  pipeline = ::RedisClient::Cluster::Pipeline.new(
100
99
  router,
101
100
  @command_builder,
102
101
  @concurrent_worker,
103
102
  exception: exception,
104
- seed: seed
103
+ seed: Random.new_seed
105
104
  )
106
105
 
107
106
  yield pipeline
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis-cluster-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.16.2
4
+ version: 0.16.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Taishi Kasuga