redis-cluster-client 0.11.6 → 0.12.0
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 +2 -2
- data/lib/redis_client/cluster/errors.rb +27 -18
- data/lib/redis_client/cluster/node.rb +4 -4
- data/lib/redis_client/cluster/pipeline.rb +4 -4
- data/lib/redis_client/cluster/router.rb +17 -10
- data/lib/redis_client/cluster/transaction.rb +5 -4
- data/lib/redis_client/cluster_config.rb +16 -2
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2465b865cd79c78cca3a1e7e3950989cce94f6675b0497763e03e261d3fa9c6f
|
4
|
+
data.tar.gz: df607279a58171aca75835b767f3bf5965256db0fcb7b540bfcb3a8f302eb638
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bfb9031660b642bb14768dc8edc66fb15073be9ca23ddc061d279a351c497a83ff8a3aad21903e2d0a37aa6824da35761188ba4dc1fe8ae0182b54584edaa5e5
|
7
|
+
data.tar.gz: 9fab63d0595bfb7dac85a07c255a41573ab8d44598605919fd4182e99cf3da585bf931abe63da9e2f470815c6ef0d34ab24a5b7f2de346ba29870fb894c556ee
|
@@ -25,7 +25,7 @@ class RedisClient
|
|
25
25
|
)
|
26
26
|
|
27
27
|
class << self
|
28
|
-
def load(nodes, slow_command_timeout: -1)
|
28
|
+
def load(nodes, slow_command_timeout: -1) # rubocop:disable Metrics/AbcSize
|
29
29
|
cmd = errors = nil
|
30
30
|
|
31
31
|
nodes&.each do |node|
|
@@ -43,7 +43,7 @@ class RedisClient
|
|
43
43
|
|
44
44
|
return cmd unless cmd.nil?
|
45
45
|
|
46
|
-
raise ::RedisClient::Cluster::InitialSetupError
|
46
|
+
raise ::RedisClient::Cluster::InitialSetupError.from_errors(errors)
|
47
47
|
end
|
48
48
|
|
49
49
|
private
|
@@ -4,51 +4,60 @@ require 'redis_client'
|
|
4
4
|
|
5
5
|
class RedisClient
|
6
6
|
class Cluster
|
7
|
+
class Error < ::RedisClient::Error
|
8
|
+
def with_config(config)
|
9
|
+
@config = config
|
10
|
+
self
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
7
14
|
ERR_ARG_NORMALIZATION = ->(arg) { Array[arg].flatten.reject { |e| e.nil? || (e.respond_to?(:empty?) && e.empty?) } }
|
8
15
|
|
9
16
|
private_constant :ERR_ARG_NORMALIZATION
|
10
17
|
|
11
|
-
class InitialSetupError <
|
12
|
-
def
|
18
|
+
class InitialSetupError < Error
|
19
|
+
def self.from_errors(errors)
|
13
20
|
msg = ERR_ARG_NORMALIZATION.call(errors).map(&:message).uniq.join(',')
|
14
|
-
|
21
|
+
new("Redis client could not fetch cluster information: #{msg}")
|
15
22
|
end
|
16
23
|
end
|
17
24
|
|
18
|
-
class OrchestrationCommandNotSupported <
|
19
|
-
def
|
25
|
+
class OrchestrationCommandNotSupported < Error
|
26
|
+
def self.from_command(command)
|
20
27
|
str = ERR_ARG_NORMALIZATION.call(command).map(&:to_s).join(' ').upcase
|
21
28
|
msg = "#{str} command should be used with care " \
|
22
29
|
'only by applications orchestrating Redis Cluster, like redis-cli, ' \
|
23
30
|
'and the command if used out of the right context can leave the cluster ' \
|
24
31
|
'in a wrong state or cause data loss.'
|
25
|
-
|
32
|
+
new(msg)
|
26
33
|
end
|
27
34
|
end
|
28
35
|
|
29
|
-
class ErrorCollection <
|
36
|
+
class ErrorCollection < Error
|
30
37
|
attr_reader :errors
|
31
38
|
|
32
|
-
def
|
33
|
-
@errors = {}
|
39
|
+
def self.with_errors(errors)
|
34
40
|
if !errors.is_a?(Hash) || errors.empty?
|
35
|
-
|
36
|
-
|
41
|
+
new(errors.to_s).with_errors({})
|
42
|
+
else
|
43
|
+
messages = errors.map { |node_key, error| "#{node_key}: (#{error.class}) #{error.message}" }
|
44
|
+
new(messages.join(', ')).with_errors(errors)
|
37
45
|
end
|
46
|
+
end
|
38
47
|
|
39
|
-
|
40
|
-
|
41
|
-
|
48
|
+
def with_errors(errors)
|
49
|
+
@errors = errors if @errors.nil?
|
50
|
+
self
|
42
51
|
end
|
43
52
|
end
|
44
53
|
|
45
|
-
class AmbiguousNodeError <
|
46
|
-
def
|
47
|
-
|
54
|
+
class AmbiguousNodeError < Error
|
55
|
+
def self.from_command(command)
|
56
|
+
new("Cluster client doesn't know which node the #{command} command should be sent to.")
|
48
57
|
end
|
49
58
|
end
|
50
59
|
|
51
|
-
class NodeMightBeDown <
|
60
|
+
class NodeMightBeDown < Error
|
52
61
|
def initialize(_ = '')
|
53
62
|
super(
|
54
63
|
'The client is trying to fetch the latest cluster state ' \
|
@@ -28,7 +28,7 @@ class RedisClient
|
|
28
28
|
private_constant :USE_CHAR_ARRAY_SLOT, :SLOT_SIZE, :MIN_SLOT, :MAX_SLOT,
|
29
29
|
:DEAD_FLAGS, :ROLE_FLAGS, :EMPTY_ARRAY, :EMPTY_HASH
|
30
30
|
|
31
|
-
ReloadNeeded = Class.new(::RedisClient::Error)
|
31
|
+
ReloadNeeded = Class.new(::RedisClient::Cluster::Error)
|
32
32
|
|
33
33
|
Info = Struct.new(
|
34
34
|
'RedisClusterNode',
|
@@ -148,7 +148,7 @@ class RedisClient
|
|
148
148
|
|
149
149
|
raise ReloadNeeded if errors.values.any?(::RedisClient::ConnectionError)
|
150
150
|
|
151
|
-
raise ::RedisClient::Cluster::ErrorCollection
|
151
|
+
raise ::RedisClient::Cluster::ErrorCollection.with_errors(errors)
|
152
152
|
end
|
153
153
|
|
154
154
|
def clients_for_scanning(seed: nil)
|
@@ -267,7 +267,7 @@ class RedisClient
|
|
267
267
|
result_values, errors = call_multiple_nodes(clients, method, command, args, &block)
|
268
268
|
return result_values if errors.nil? || errors.empty?
|
269
269
|
|
270
|
-
raise ::RedisClient::Cluster::ErrorCollection
|
270
|
+
raise ::RedisClient::Cluster::ErrorCollection.with_errors(errors)
|
271
271
|
end
|
272
272
|
|
273
273
|
def try_map(clients, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
@@ -334,7 +334,7 @@ class RedisClient
|
|
334
334
|
|
335
335
|
work_group.close
|
336
336
|
|
337
|
-
raise ::RedisClient::Cluster::InitialSetupError
|
337
|
+
raise ::RedisClient::Cluster::InitialSetupError.from_errors(errors) if node_info_list.nil?
|
338
338
|
|
339
339
|
grouped = node_info_list.compact.group_by do |info_list|
|
340
340
|
info_list.sort_by!(&:id)
|
@@ -108,13 +108,13 @@ class RedisClient
|
|
108
108
|
end
|
109
109
|
end
|
110
110
|
|
111
|
-
ReplySizeError = Class.new(::RedisClient::Error)
|
111
|
+
ReplySizeError = Class.new(::RedisClient::Cluster::Error)
|
112
112
|
|
113
|
-
class StaleClusterState < ::RedisClient::Error
|
113
|
+
class StaleClusterState < ::RedisClient::Cluster::Error
|
114
114
|
attr_accessor :replies, :first_exception
|
115
115
|
end
|
116
116
|
|
117
|
-
class RedirectionNeeded < ::RedisClient::Error
|
117
|
+
class RedirectionNeeded < ::RedisClient::Cluster::Error
|
118
118
|
attr_accessor :replies, :indices, :first_exception
|
119
119
|
end
|
120
120
|
|
@@ -204,7 +204,7 @@ class RedisClient
|
|
204
204
|
|
205
205
|
work_group.close
|
206
206
|
@router.renew_cluster_state if cluster_state_errors
|
207
|
-
raise ::RedisClient::Cluster::ErrorCollection
|
207
|
+
raise ::RedisClient::Cluster::ErrorCollection.with_errors(errors).with_config(@router.config) unless errors.nil?
|
208
208
|
|
209
209
|
required_redirections&.each do |node_key, v|
|
210
210
|
raise v.first_exception if v.first_exception
|
@@ -21,10 +21,10 @@ class RedisClient
|
|
21
21
|
|
22
22
|
private_constant :ZERO_CURSOR_FOR_SCAN, :TSF
|
23
23
|
|
24
|
+
attr_reader :config
|
25
|
+
|
24
26
|
def initialize(config, concurrent_worker, pool: nil, **kwargs)
|
25
|
-
@config = config
|
26
|
-
@original_config = config.dup if config.connect_with_original_config
|
27
|
-
@connect_with_original_config = config.connect_with_original_config
|
27
|
+
@config = config
|
28
28
|
@concurrent_worker = concurrent_worker
|
29
29
|
@pool = pool
|
30
30
|
@client_kwargs = kwargs
|
@@ -32,6 +32,9 @@ class RedisClient
|
|
32
32
|
@node.reload!
|
33
33
|
@command = ::RedisClient::Cluster::Command.load(@node.replica_clients.shuffle, slow_command_timeout: config.slow_command_timeout)
|
34
34
|
@command_builder = @config.command_builder
|
35
|
+
rescue ::RedisClient::Cluster::InitialSetupError => e
|
36
|
+
e.with_config(config)
|
37
|
+
raise
|
35
38
|
end
|
36
39
|
|
37
40
|
def send_command(method, command, *args, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
@@ -58,9 +61,9 @@ class RedisClient
|
|
58
61
|
when 'flushall', 'flushdb'
|
59
62
|
@node.call_primaries(method, command, args).first.then(&TSF.call(block))
|
60
63
|
when 'readonly', 'readwrite', 'shutdown'
|
61
|
-
raise ::RedisClient::Cluster::OrchestrationCommandNotSupported
|
64
|
+
raise ::RedisClient::Cluster::OrchestrationCommandNotSupported.from_command(cmd).with_config(@config)
|
62
65
|
when 'discard', 'exec', 'multi', 'unwatch'
|
63
|
-
raise ::RedisClient::Cluster::AmbiguousNodeError
|
66
|
+
raise ::RedisClient::Cluster::AmbiguousNodeError.from_command(cmd).with_config(@config)
|
64
67
|
else
|
65
68
|
node = assign_node(command)
|
66
69
|
try_send(node, method, command, args, &block)
|
@@ -69,7 +72,7 @@ class RedisClient
|
|
69
72
|
raise
|
70
73
|
rescue ::RedisClient::Cluster::Node::ReloadNeeded
|
71
74
|
renew_cluster_state
|
72
|
-
raise ::RedisClient::Cluster::NodeMightBeDown
|
75
|
+
raise ::RedisClient::Cluster::NodeMightBeDown.new.with_config(@config)
|
73
76
|
rescue ::RedisClient::ConnectionError
|
74
77
|
renew_cluster_state
|
75
78
|
raise
|
@@ -77,6 +80,7 @@ class RedisClient
|
|
77
80
|
renew_cluster_state if e.message.start_with?('CLUSTERDOWN')
|
78
81
|
raise
|
79
82
|
rescue ::RedisClient::Cluster::ErrorCollection => e
|
83
|
+
e.with_config(@config)
|
80
84
|
raise if e.errors.any?(::RedisClient::CircuitBreaker::OpenCircuitError)
|
81
85
|
|
82
86
|
renew_cluster_state if e.errors.values.any? do |err|
|
@@ -189,7 +193,7 @@ class RedisClient
|
|
189
193
|
node_key = primary ? @node.find_node_key_of_primary(slot) : @node.find_node_key_of_replica(slot)
|
190
194
|
if node_key.nil?
|
191
195
|
renew_cluster_state
|
192
|
-
raise ::RedisClient::Cluster::NodeMightBeDown
|
196
|
+
raise ::RedisClient::Cluster::NodeMightBeDown.new.with_config(@config)
|
193
197
|
end
|
194
198
|
node_key
|
195
199
|
else
|
@@ -303,7 +307,7 @@ class RedisClient
|
|
303
307
|
case subcommand = ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_subcommand(command)
|
304
308
|
when 'addslots', 'delslots', 'failover', 'forget', 'meet', 'replicate',
|
305
309
|
'reset', 'set-config-epoch', 'setslot'
|
306
|
-
raise ::RedisClient::Cluster::OrchestrationCommandNotSupported
|
310
|
+
raise ::RedisClient::Cluster::OrchestrationCommandNotSupported.from_command(['cluster', subcommand]).with_config(@config)
|
307
311
|
when 'saveconfig' then @node.call_all(method, command, args).first.then(&TSF.call(block))
|
308
312
|
when 'getkeysinslot'
|
309
313
|
raise ArgumentError, command.join(' ') if command.size != 4
|
@@ -347,7 +351,10 @@ class RedisClient
|
|
347
351
|
end
|
348
352
|
|
349
353
|
def send_watch_command(command)
|
350
|
-
|
354
|
+
unless block_given?
|
355
|
+
msg = 'A block required. And you need to use the block argument as a client for the transaction.'
|
356
|
+
raise ::RedisClient::Cluster::Transaction::ConsistencyError.new(msg).with_config(@config)
|
357
|
+
end
|
351
358
|
|
352
359
|
::RedisClient::Cluster::OptimisticLocking.new(self).watch(command[1..]) do |c, slot, asking|
|
353
360
|
transaction = ::RedisClient::Cluster::Transaction.new(
|
@@ -401,7 +408,7 @@ class RedisClient
|
|
401
408
|
def handle_node_reload_error(retry_count: 1)
|
402
409
|
yield
|
403
410
|
rescue ::RedisClient::Cluster::Node::ReloadNeeded
|
404
|
-
raise ::RedisClient::Cluster::NodeMightBeDown if retry_count <= 0
|
411
|
+
raise ::RedisClient::Cluster::NodeMightBeDown.new.with_config(@config) if retry_count <= 0
|
405
412
|
|
406
413
|
retry_count -= 1
|
407
414
|
renew_cluster_state
|
@@ -1,12 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'redis_client'
|
4
|
+
require 'redis_client/cluster/errors'
|
4
5
|
require 'redis_client/cluster/pipeline'
|
5
6
|
|
6
7
|
class RedisClient
|
7
8
|
class Cluster
|
8
9
|
class Transaction
|
9
|
-
ConsistencyError = Class.new(::RedisClient::Error)
|
10
|
+
ConsistencyError = Class.new(::RedisClient::Cluster::Error)
|
10
11
|
|
11
12
|
MAX_REDIRECTION = 2
|
12
13
|
EMPTY_ARRAY = [].freeze
|
@@ -67,7 +68,7 @@ class RedisClient
|
|
67
68
|
@pending_commands.each(&:call)
|
68
69
|
|
69
70
|
return EMPTY_ARRAY if @pipeline._empty?
|
70
|
-
raise ConsistencyError
|
71
|
+
raise ConsistencyError.new("couldn't determine the node: #{@pipeline._commands}").with_config(@router.config) if @node.nil?
|
71
72
|
|
72
73
|
commit
|
73
74
|
end
|
@@ -163,7 +164,7 @@ class RedisClient
|
|
163
164
|
|
164
165
|
def handle_command_error!(err, redirect:) # rubocop:disable Metrics/AbcSize
|
165
166
|
if err.message.start_with?('CROSSSLOT')
|
166
|
-
raise ConsistencyError
|
167
|
+
raise ConsistencyError.new("#{err.message}: #{err.command}").with_config(@router.config)
|
167
168
|
elsif err.message.start_with?('MOVED')
|
168
169
|
node = @router.assign_redirection_node(err.message)
|
169
170
|
send_transaction(node, redirect: redirect - 1)
|
@@ -183,7 +184,7 @@ class RedisClient
|
|
183
184
|
return if slots.size == 1 && @watching_slot.nil?
|
184
185
|
return if slots.size == 1 && @watching_slot == slots.first
|
185
186
|
|
186
|
-
raise(
|
187
|
+
raise ConsistencyError.new("the transaction should be executed to a slot in a node: #{commands}").with_config(@router.config)
|
187
188
|
end
|
188
189
|
|
189
190
|
def try_asking(node)
|
@@ -3,6 +3,7 @@
|
|
3
3
|
require 'uri'
|
4
4
|
require 'redis_client'
|
5
5
|
require 'redis_client/cluster'
|
6
|
+
require 'redis_client/cluster/errors'
|
6
7
|
require 'redis_client/cluster/node_key'
|
7
8
|
require 'redis_client/command_builder'
|
8
9
|
|
@@ -27,10 +28,10 @@ class RedisClient
|
|
27
28
|
:VALID_SCHEMES, :VALID_NODES_KEYS, :MERGE_CONFIG_KEYS, :IGNORE_GENERIC_CONFIG_KEYS,
|
28
29
|
:MAX_WORKERS, :SLOW_COMMAND_TIMEOUT, :MAX_STARTUP_SAMPLE
|
29
30
|
|
30
|
-
InvalidClientConfigError = Class.new(::RedisClient::Error)
|
31
|
+
InvalidClientConfigError = Class.new(::RedisClient::Cluster::Error)
|
31
32
|
|
32
33
|
attr_reader :command_builder, :client_config, :replica_affinity, :slow_command_timeout,
|
33
|
-
:connect_with_original_config, :startup_nodes, :max_startup_sample
|
34
|
+
:connect_with_original_config, :startup_nodes, :max_startup_sample, :id
|
34
35
|
|
35
36
|
def initialize( # rubocop:disable Metrics/ParameterLists
|
36
37
|
nodes: DEFAULT_NODES,
|
@@ -59,6 +60,7 @@ class RedisClient
|
|
59
60
|
@client_implementation = client_implementation
|
60
61
|
@slow_command_timeout = slow_command_timeout
|
61
62
|
@max_startup_sample = max_startup_sample
|
63
|
+
@id = client_config[:id]
|
62
64
|
end
|
63
65
|
|
64
66
|
def inspect
|
@@ -92,6 +94,18 @@ class RedisClient
|
|
92
94
|
augment_client_config(config)
|
93
95
|
end
|
94
96
|
|
97
|
+
def resolved?
|
98
|
+
true
|
99
|
+
end
|
100
|
+
|
101
|
+
def sentinel?
|
102
|
+
false
|
103
|
+
end
|
104
|
+
|
105
|
+
def server_url
|
106
|
+
nil
|
107
|
+
end
|
108
|
+
|
95
109
|
private
|
96
110
|
|
97
111
|
def merge_concurrency_option(option)
|
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.
|
4
|
+
version: 0.12.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Taishi Kasuga
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-11-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis-client
|
@@ -77,7 +77,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
77
77
|
- !ruby/object:Gem::Version
|
78
78
|
version: '0'
|
79
79
|
requirements: []
|
80
|
-
rubygems_version: 3.5.
|
80
|
+
rubygems_version: 3.5.22
|
81
81
|
signing_key:
|
82
82
|
specification_version: 4
|
83
83
|
summary: A Redis cluster client for Ruby
|