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 +4 -4
- data/lib/redis_client/cluster/command.rb +46 -47
- data/lib/redis_client/cluster/concurrent_worker/none.rb +48 -8
- data/lib/redis_client/cluster/concurrent_worker.rb +1 -1
- data/lib/redis_client/cluster/node/base_topology.rb +6 -2
- data/lib/redis_client/cluster/node/latency_replica.rb +1 -2
- data/lib/redis_client/cluster/node/primary_only.rb +1 -2
- data/lib/redis_client/cluster/node/random_replica.rb +5 -5
- data/lib/redis_client/cluster/node/random_replica_or_primary.rb +3 -4
- data/lib/redis_client/cluster/pipeline.rb +3 -3
- data/lib/redis_client/cluster/router.rb +17 -8
- data/lib/redis_client/cluster.rb +1 -2
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5c16432e083671229128632c116401dc9b1bd56f6b3b1e3c8f107956901d29f2
|
|
4
|
+
data.tar.gz: b464a6f87e8c952a5e4f5b22d0609a9be09b3c6c7817eb91ed779e0b142a4290
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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 :
|
|
13
|
+
private_constant :EMPTY_HASH
|
|
15
14
|
|
|
16
|
-
|
|
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::
|
|
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
|
|
83
|
-
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
16
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
|
|
25
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
233
|
+
pi = (@pipelines[node_key] ||= ::RedisClient::Cluster::Pipeline::Extended.new(::RedisClient::Cluster::NoopCommandBuilder))
|
|
234
|
+
pi.add_outer_index(@size)
|
|
235
235
|
@size += 1
|
|
236
|
-
|
|
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
|
-
|
|
250
|
-
find_node_key_by_key(
|
|
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
|
|
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
|
-
|
|
471
|
-
|
|
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
|
data/lib/redis_client/cluster.rb
CHANGED
|
@@ -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:
|
|
103
|
+
seed: Random.new_seed
|
|
105
104
|
)
|
|
106
105
|
|
|
107
106
|
yield pipeline
|