redis-cluster-client 0.0.7 → 0.0.8

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: aff938c0517542c32f98206c28f1f605a52751e2e2e9a1bd10f5576285f568ee
4
- data.tar.gz: 8a3d45fba93334959b1ee67504d2703bb116a8133ef8dac2aa8f314fdb74b268
3
+ metadata.gz: d13300ba8c7f294d3e4c86932d13775ad22060c172900c01d521b1ed8c2278d6
4
+ data.tar.gz: d5b457978bb312a718ba57b48b56fac1ceb07a463889835c8190794007ecbf7d
5
5
  SHA512:
6
- metadata.gz: 63dec0155a50a0dbb7a17763c43291d371abe4e3df407340ba8edf125cf2c2bb338f607026a1d004dca489761cb2e6b5450e68a468f18799e5c3443b52a21be7
7
- data.tar.gz: 4de6c4688ca120d7f4b636bd884195b3cddc3bf8d217ea662a1f382ced46de2c9e24b0e967a801d175f0572a083b9e5ea835d54797422cf60ce799cda3002f74
6
+ metadata.gz: 37fb06428eed5a9cca5b42d27c034f057dd7d6f0c5d0c17e4b42c60a8fa3711127c5ed81f06e0565331f64aa762ccc30ee6674fb541e56a8b322ee50ab7a75ce
7
+ data.tar.gz: 06d46f93c8c7b7116378d2ef9e4380ccce3aac74c071b709cbacd677ec0b302302b52160c04b2c3523771f69446b96d84e8b4f729765073386c5e5f1f3f27f5b
@@ -68,12 +68,14 @@ class RedisClient
68
68
  @details.fetch(name).fetch(key)
69
69
  end
70
70
 
71
- def determine_first_key_position(command) # rubocop:disable Metrics/CyclomaticComplexity
71
+ def determine_first_key_position(command) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
72
72
  case command&.flatten&.first.to_s.downcase
73
- when 'eval', 'evalsha', 'migrate', 'zinterstore', 'zunionstore' then 3
73
+ when 'eval', 'evalsha', 'zinterstore', 'zunionstore' then 3
74
74
  when 'object' then 2
75
75
  when 'memory'
76
76
  command[1].to_s.casecmp('usage').zero? ? 2 : 0
77
+ when 'migrate'
78
+ command[3] == '""' ? determine_optional_key_position(command, 'keys') : 3
77
79
  when 'xread', 'xreadgroup'
78
80
  determine_optional_key_position(command, 'streams')
79
81
  else
@@ -51,5 +51,15 @@ class RedisClient
51
51
  super("Cluster client doesn't know which node the #{command} command should be sent to.")
52
52
  end
53
53
  end
54
+
55
+ class NodeMightBeDown < ::RedisClient::Error
56
+ def initialize(_ = '')
57
+ super(
58
+ 'The client is trying to fetch the latest cluster state '\
59
+ 'because a subset of nodes might be down. '\
60
+ 'It might continue to raise errors for a while.'
61
+ )
62
+ end
63
+ end
54
64
  end
55
65
  end
@@ -43,6 +43,8 @@ class RedisClient
43
43
  module_function
44
44
 
45
45
  def convert(key)
46
+ return nil if key.nil?
47
+
46
48
  crc = 0
47
49
  key.each_byte do |b|
48
50
  crc = ((crc << 8) & 0xffff) ^ XMODEM_CRC16_LOOKUP[((crc >> 8) ^ b) & 0xff]
@@ -12,6 +12,7 @@ class RedisClient
12
12
  SLOT_SIZE = 16_384
13
13
  MIN_SLOT = 0
14
14
  MAX_SLOT = SLOT_SIZE - 1
15
+ MAX_STARTUP_SAMPLE = 37
15
16
  IGNORE_GENERIC_CONFIG_KEYS = %i[url host port path].freeze
16
17
 
17
18
  ReloadNeeded = Class.new(::RedisClient::Error)
@@ -32,19 +33,33 @@ class RedisClient
32
33
  end
33
34
 
34
35
  class << self
35
- def load_info(options, **kwargs)
36
- startup_nodes = ::RedisClient::Cluster::Node.new(options, **kwargs)
37
-
38
- errors = startup_nodes.map do |n|
39
- reply = n.call('CLUSTER', 'NODES')
40
- return parse_node_info(reply)
41
- rescue ::RedisClient::ConnectionError, ::RedisClient::CommandError => e
42
- e
36
+ def load_info(options, **kwargs) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
37
+ startup_size = options.size > MAX_STARTUP_SAMPLE ? MAX_STARTUP_SAMPLE : options.size
38
+ node_info_list = Array.new(startup_size)
39
+ errors = Array.new(startup_size)
40
+ startup_options = options.to_a.sample(MAX_STARTUP_SAMPLE).to_h
41
+ startup_nodes = ::RedisClient::Cluster::Node.new(startup_options, **kwargs)
42
+ threads = startup_nodes.each_with_index.map do |raw_client, idx|
43
+ Thread.new(raw_client, idx) do |cli, i|
44
+ Thread.pass
45
+ reply = cli.call('CLUSTER', 'NODES')
46
+ node_info_list[i] = parse_node_info(reply)
47
+ rescue StandardError => e
48
+ errors[i] = e
49
+ ensure
50
+ cli&.close
51
+ end
52
+ end
53
+ threads.each(&:join)
54
+ raise ::RedisClient::Cluster::InitialSetupError, errors if node_info_list.all?(&:nil?)
55
+
56
+ grouped = node_info_list.compact.group_by do |rows|
57
+ rows.sort_by { |row| row[:id] }
58
+ .map { |r| "#{r[:id]}#{r[:node_key]}#{r[:role]}#{r[:primary_id]}#{r[:config_epoch]}" }
59
+ .join
43
60
  end
44
61
 
45
- raise ::RedisClient::Cluster::InitialSetupError, errors
46
- ensure
47
- startup_nodes&.each(&:close)
62
+ grouped.max_by { |_, v| v.size }[1].first
48
63
  end
49
64
 
50
65
  private
@@ -109,32 +124,58 @@ class RedisClient
109
124
  end
110
125
 
111
126
  def find_by(node_key)
127
+ raise ReloadNeeded if node_key.nil? || !@clients.key?(node_key)
128
+
112
129
  @clients.fetch(node_key)
113
- rescue KeyError
114
- raise ReloadNeeded
115
130
  end
116
131
 
117
132
  def call_all(method, *command, **kwargs, &block)
118
- try_map { |_, client| client.send(method, *command, **kwargs, &block) }.values
133
+ results, errors = try_map do |_, client|
134
+ client.send(method, *command, **kwargs, &block)
135
+ end
136
+
137
+ return results.values if errors.empty?
138
+
139
+ raise ::RedisClient::Cluster::ErrorCollection, errors
119
140
  end
120
141
 
121
142
  def call_primaries(method, *command, **kwargs, &block)
122
- try_map do |node_key, client|
143
+ results, errors = try_map do |node_key, client|
123
144
  next if replica?(node_key)
124
145
 
125
146
  client.send(method, *command, **kwargs, &block)
126
- end.values
147
+ end
148
+
149
+ return results.values if errors.empty?
150
+
151
+ raise ::RedisClient::Cluster::ErrorCollection, errors
127
152
  end
128
153
 
129
154
  def call_replicas(method, *command, **kwargs, &block)
130
155
  return call_primaries(method, *command, **kwargs, &block) if replica_disabled?
131
156
 
132
157
  replica_node_keys = @replications.values.map(&:sample)
133
- try_map do |node_key, client|
158
+ results, errors = try_map do |node_key, client|
134
159
  next if primary?(node_key) || !replica_node_keys.include?(node_key)
135
160
 
136
161
  client.send(method, *command, **kwargs, &block)
137
- end.values
162
+ end
163
+
164
+ return results.values if errors.empty?
165
+
166
+ raise ::RedisClient::Cluster::ErrorCollection, errors
167
+ end
168
+
169
+ def send_ping(method, *command, **kwargs, &block)
170
+ results, errors = try_map do |_, client|
171
+ client.send(method, *command, **kwargs, &block)
172
+ end
173
+
174
+ return results.values if errors.empty?
175
+
176
+ raise ReloadNeeded if errors.values.any?(::RedisClient::ConnectionError)
177
+
178
+ raise ::RedisClient::Cluster::ErrorCollection, errors
138
179
  end
139
180
 
140
181
  def scale_reading_clients
@@ -144,14 +185,9 @@ class RedisClient
144
185
  end
145
186
  end
146
187
 
147
- def slot_exists?(slot)
148
- slot = Integer(slot)
149
- return false if slot < MIN_SLOT || slot > MAX_SLOT
150
-
151
- !@slots[slot].nil?
152
- end
153
-
154
188
  def find_node_key_of_primary(slot)
189
+ return if slot.nil?
190
+
155
191
  slot = Integer(slot)
156
192
  return if slot < MIN_SLOT || slot > MAX_SLOT
157
193
 
@@ -159,6 +195,8 @@ class RedisClient
159
195
  end
160
196
 
161
197
  def find_node_key_of_replica(slot)
198
+ return if slot.nil?
199
+
162
200
  slot = Integer(slot)
163
201
  return if slot < MIN_SLOT || slot > MAX_SLOT
164
202
 
@@ -241,9 +279,7 @@ class RedisClient
241
279
  end
242
280
 
243
281
  threads.each(&:join)
244
- return results if errors.empty?
245
-
246
- raise ::RedisClient::Cluster::ErrorCollection, errors
282
+ [results, errors]
247
283
  end
248
284
  end
249
285
  end
@@ -170,8 +170,10 @@ class RedisClient
170
170
  end
171
171
 
172
172
  def close
173
- @node.each(&:close)
173
+ @node.call_all(:close)
174
174
  nil
175
+ rescue StandardError
176
+ # ignore
175
177
  end
176
178
 
177
179
  private
@@ -187,10 +189,11 @@ class RedisClient
187
189
  def send_command(method, *command, **kwargs, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
188
190
  cmd = command.first.to_s.downcase
189
191
  case cmd
190
- when 'acl', 'auth', 'bgrewriteaof', 'bgsave', 'quit', 'save', 'ping'
192
+ when 'acl', 'auth', 'bgrewriteaof', 'bgsave', 'quit', 'save'
191
193
  @node.call_all(method, *command, **kwargs, &block).first
192
194
  when 'flushall', 'flushdb'
193
195
  @node.call_primaries(method, *command, **kwargs, &block).first
196
+ when 'ping' then @node.send_ping(method, *command, **kwargs, &block).first
194
197
  when 'wait' then send_wait_command(method, *command, **kwargs, &block)
195
198
  when 'keys' then @node.call_replicas(method, *command, **kwargs, &block).flatten.sort
196
199
  when 'dbsize' then @node.call_replicas(method, *command, **kwargs, &block).sum
@@ -211,15 +214,18 @@ class RedisClient
211
214
  node = assign_node(*command)
212
215
  try_send(node, method, *command, **kwargs, &block)
213
216
  end
214
- rescue RedisClient::Cluster::ErrorCollection => e
215
- update_cluster_info! if e.errors.values.map(&:class).any?(::RedisClient::ConnectionError)
216
- raise
217
+ rescue RedisClient::Cluster::Node::ReloadNeeded
218
+ update_cluster_info!
219
+ raise ::RedisClient::Cluster::NodeMightBeDown
217
220
  end
218
221
 
219
222
  def send_wait_command(method, *command, retry_count: 3, **kwargs, &block)
220
223
  @node.call_primaries(method, *command, **kwargs, &block).sum
221
224
  rescue RedisClient::Cluster::ErrorCollection => e
222
- raise if retry_count <= 0 || e.errors.values.map(&:message).grep(/ERR WAIT cannot be used with replica instances/).empty?
225
+ raise if retry_count <= 0
226
+ raise if e.errors.values.none? do |err|
227
+ err.message.include?('WAIT cannot be used with replica instances')
228
+ end
223
229
 
224
230
  update_cluster_info!
225
231
  retry_count -= 1
@@ -353,44 +359,30 @@ class RedisClient
353
359
  find_node(node_key)
354
360
  end
355
361
 
356
- def find_node_key(*command, primary_only: false) # rubocop:disable Metrics/MethodLength
362
+ def find_node_key(*command, primary_only: false)
357
363
  key = @command.extract_first_key(command)
358
- if key.empty?
359
- return @node.primary_node_keys.sample if @command.should_send_to_primary?(command) || primary_only
360
-
361
- return @node.replica_node_keys.sample
362
- end
363
-
364
- slot = ::RedisClient::Cluster::KeySlotConverter.convert(key)
365
- return unless @node.slot_exists?(slot)
364
+ slot = key.empty? ? nil : ::RedisClient::Cluster::KeySlotConverter.convert(key)
366
365
 
367
366
  if @command.should_send_to_primary?(command) || primary_only
368
- @node.find_node_key_of_primary(slot)
367
+ @node.find_node_key_of_primary(slot) || @node.primary_node_keys.sample
369
368
  else
370
- @node.find_node_key_of_replica(slot)
369
+ @node.find_node_key_of_replica(slot) || @node.replica_node_keys.sample
371
370
  end
372
371
  end
373
372
 
374
373
  def find_node(node_key, retry_count: 3)
375
- return @node.sample if node_key.nil?
376
-
377
374
  @node.find_by(node_key)
378
375
  rescue ::RedisClient::Cluster::Node::ReloadNeeded
379
- raise(::RedisClient::ConnectionError, 'unstable cluster state') if retry_count <= 0
376
+ raise ::RedieClient::Cluster::NodeMightBeDown if retry_count <= 0
380
377
 
381
- update_cluster_info!(node_key)
378
+ update_cluster_info!
382
379
  retry_count -= 1
383
380
  retry
384
381
  end
385
382
 
386
- def update_cluster_info!(node_key = nil)
383
+ def update_cluster_info!
387
384
  @mutex.synchronize do
388
- unless node_key.nil?
389
- host, port = ::RedisClient::Cluster::NodeKey.split(node_key)
390
- @config.add_node(host, port)
391
- end
392
-
393
- @node.each(&:close)
385
+ close
394
386
  @node = fetch_cluster_info!(@config, pool: @pool, **@client_kwargs)
395
387
  end
396
388
  end
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.0.7
4
+ version: 0.0.8
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-06-19 00:00:00.000000000 Z
11
+ date: 2022-06-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis-client