redis-cluster-client 0.8.1 → 0.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 53d1591b89859ddc92dc6ca349ff211d0a6ac14c68ad72e920cc9725d82587b4
4
- data.tar.gz: fcfeceffddc9201f8b8c5d6999d3e7aa8581c9f24f0d823b029a5561ef82887d
3
+ metadata.gz: 97383b0daf1dcc2e5fcd8e50640e9ecbd903562a10cca0f4996ada1807469f80
4
+ data.tar.gz: 675ae25f2fbf2d9d67234b4e1ad81894656f11a32fe4cd1ebf8ae73c90c67509
5
5
  SHA512:
6
- metadata.gz: d51aefc48e64ef0cdbded8b5c8e4b005dcfd103d9fa4ecfa0dff1d9aa4fed5d6f6aefd4dca4ac53e89867b5276812bc6c7f8cf3cb41c1184bd9d8f19ea601471
7
- data.tar.gz: 548da631726b4881f0a7712186f656733cb6941aef9ca3415498a6f048f59170fb74a59c30da68d655cee5e5773732d9d781c489f6cda2f0c92a341f1b28b892
6
+ metadata.gz: 851565ace738fb9bf51d1211ece97767520c94a7b02e49b053732de351d77a30e7104ae1f8143a0ee3903a2b99de39b298b3eec80c2fb94b08fb4747180bb03d
7
+ data.tar.gz: 0a0b6ada72bccfcdb7cecda533f8f31761f15f991e359bc211a4d54ba973b2c68983efd3a9a594e7baab11b614fecb058de98b71f1890f7e52150f40e2c34d1b
@@ -97,6 +97,12 @@ class RedisClient
97
97
  @commands.key?(::RedisClient::Cluster::NormalizedCmdName.instance.get_by_name(name))
98
98
  end
99
99
 
100
+ def determine_key_step(command)
101
+ name = ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_command(command)
102
+ # Some commands like EVALSHA have zero as the step in COMMANDS somehow.
103
+ @commands[name].key_step == 0 ? 1 : @commands[name].key_step
104
+ end
105
+
100
106
  private
101
107
 
102
108
  def determine_first_key_position(command) # rubocop:disable Metrics/CyclomaticComplexity
@@ -143,12 +149,6 @@ class RedisClient
143
149
  end
144
150
  end
145
151
 
146
- def determine_key_step(command)
147
- name = ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_command(command)
148
- # Some commands like EVALSHA have zero as the step in COMMANDS somehow.
149
- @commands[name].key_step == 0 ? 1 : @commands[name].key_step
150
- end
151
-
152
152
  def determine_optional_key_position(command, option_name) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
153
153
  idx = command&.flatten&.map(&:to_s)&.map(&:downcase)&.index(option_name&.downcase)
154
154
  idx.nil? ? 0 : idx + 1
@@ -5,6 +5,9 @@ class RedisClient
5
5
  class Node
6
6
  class BaseTopology
7
7
  IGNORE_GENERIC_CONFIG_KEYS = %i[url host port path].freeze
8
+ EMPTY_HASH = {}.freeze
9
+ EMPTY_ARRAY = [].freeze
10
+
8
11
  attr_reader :clients, :primary_clients, :replica_clients
9
12
 
10
13
  def initialize(pool, concurrent_worker, **kwargs)
@@ -92,13 +92,7 @@ class RedisClient
92
92
  end
93
93
  end
94
94
 
95
- def initialize(
96
- concurrent_worker,
97
- config:,
98
- pool: nil,
99
- **kwargs
100
- )
101
-
95
+ def initialize(concurrent_worker, config:, pool: nil, **kwargs)
102
96
  @concurrent_worker = concurrent_worker
103
97
  @slots = build_slot_node_mappings(EMPTY_ARRAY)
104
98
  @replications = build_replication_mappings(EMPTY_ARRAY)
@@ -106,6 +100,7 @@ class RedisClient
106
100
  @topology = klass.new(pool, @concurrent_worker, **kwargs)
107
101
  @config = config
108
102
  @mutex = Mutex.new
103
+ @last_reloaded_at = nil
109
104
  end
110
105
 
111
106
  def inspect
@@ -10,6 +10,7 @@ require 'redis_client/cluster/node_key'
10
10
  require 'redis_client/cluster/normalized_cmd_name'
11
11
  require 'redis_client/cluster/transaction'
12
12
  require 'redis_client/cluster/optimistic_locking'
13
+ require 'redis_client/cluster/pipeline'
13
14
  require 'redis_client/cluster/error_identification'
14
15
 
15
16
  class RedisClient
@@ -17,6 +18,11 @@ class RedisClient
17
18
  class Router
18
19
  ZERO_CURSOR_FOR_SCAN = '0'
19
20
  TSF = ->(f, x) { f.nil? ? x : f.call(x) }.curry
21
+ MULTIPLE_KEYS_COMMAND_TO_PIPELINE = {
22
+ 'mset' => 'set',
23
+ 'mget' => 'get',
24
+ 'del' => 'del'
25
+ }.freeze
20
26
 
21
27
  def initialize(config, concurrent_worker, pool: nil, **kwargs)
22
28
  @config = config.dup
@@ -48,6 +54,8 @@ class RedisClient
48
54
  when 'script' then send_script_command(method, command, args, &block)
49
55
  when 'pubsub' then send_pubsub_command(method, command, args, &block)
50
56
  when 'watch' then send_watch_command(command, &block)
57
+ when 'mset', 'mget', 'del'
58
+ send_multiple_keys_command(cmd, method, command, args, &block)
51
59
  when 'acl', 'auth', 'bgrewriteaof', 'bgsave', 'quit', 'save'
52
60
  @node.call_all(method, command, args).first.then(&TSF.call(block))
53
61
  when 'flushall', 'flushdb'
@@ -314,7 +322,6 @@ class RedisClient
314
322
  end
315
323
  end
316
324
 
317
- # for redis-rb
318
325
  def send_watch_command(command)
319
326
  raise ::RedisClient::Cluster::Transaction::ConsistencyError, 'A block required. And you need to use the block argument as a client for the transaction.' unless block_given?
320
327
 
@@ -327,6 +334,40 @@ class RedisClient
327
334
  end
328
335
  end
329
336
 
337
+ def send_multiple_keys_command(cmd, method, command, args, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
338
+ key_step = @command.determine_key_step(cmd)
339
+ if command.size <= key_step + 1 || !::RedisClient::Cluster::KeySlotConverter.extract_hash_tag(command[1]).empty? # rubocop:disable Style/IfUnlessModifier
340
+ return try_send(assign_node(command), method, command, args, &block)
341
+ end
342
+
343
+ seed = @config.use_replica? && @config.replica_affinity == :random ? nil : Random.new_seed
344
+ pipeline = ::RedisClient::Cluster::Pipeline.new(self, @command_builder, @concurrent_worker, exception: true, seed: seed)
345
+
346
+ # This implementation is prioritized to lessen memory consumption rather than readability.
347
+ single_key_cmd = MULTIPLE_KEYS_COMMAND_TO_PIPELINE[cmd]
348
+ single_command = Array.new(key_step + 1)
349
+ single_command[0] = single_key_cmd
350
+ if key_step == 1
351
+ command[1..].each do |key|
352
+ single_command[1] = key
353
+ pipeline.call_v(single_command)
354
+ end
355
+ else
356
+ command[1..].each_slice(key_step) do |v|
357
+ key_step.times { |i| single_command[i + 1] = v[i] }
358
+ pipeline.call_v(single_command)
359
+ end
360
+ end
361
+
362
+ replies = pipeline.execute
363
+ result = case cmd
364
+ when 'mset' then replies.first
365
+ when 'del' then replies.sum
366
+ else replies
367
+ end
368
+ block_given? ? yield(result) : result
369
+ end
370
+
330
371
  def update_cluster_info!
331
372
  @node.reload!
332
373
  end
@@ -8,6 +8,7 @@ class RedisClient
8
8
  class Transaction
9
9
  ConsistencyError = Class.new(::RedisClient::Error)
10
10
  MAX_REDIRECTION = 2
11
+ EMPTY_ARRAY = [].freeze
11
12
 
12
13
  def initialize(router, command_builder, node: nil, slot: nil, asking: false)
13
14
  @router = router
@@ -62,10 +63,10 @@ class RedisClient
62
63
  def execute
63
64
  @pending_commands.each(&:call)
64
65
 
65
- raise ArgumentError, 'empty transaction' if @pipeline._empty?
66
+ return EMPTY_ARRAY if @pipeline._empty?
66
67
  raise ConsistencyError, "couldn't determine the node: #{@pipeline._commands}" if @node.nil?
67
68
 
68
- settle
69
+ commit
69
70
  end
70
71
 
71
72
  private
@@ -92,8 +93,17 @@ class RedisClient
92
93
  @pending_commands.clear
93
94
  end
94
95
 
95
- def settle
96
+ def commit
96
97
  @pipeline.call('EXEC')
98
+ settle
99
+ end
100
+
101
+ def cancel
102
+ @pipeline.call('DISCARD')
103
+ settle
104
+ end
105
+
106
+ def settle
97
107
  # If we needed ASKING on the watch, we need ASKING on the multi as well.
98
108
  @node.call('ASKING') if @asking
99
109
  # Don't handle redirections at this level if we're in a watch (the watcher handles redirections
@@ -5,7 +5,6 @@ require 'redis_client/cluster/pipeline'
5
5
  require 'redis_client/cluster/pub_sub'
6
6
  require 'redis_client/cluster/router'
7
7
  require 'redis_client/cluster/transaction'
8
- require 'redis_client/cluster/pinning_node'
9
8
  require 'redis_client/cluster/optimistic_locking'
10
9
 
11
10
  class RedisClient
@@ -118,13 +117,8 @@ class RedisClient
118
117
  ::RedisClient::Cluster::PubSub.new(@router, @command_builder)
119
118
  end
120
119
 
121
- # TODO: This isn't an official public interface yet. Don't use in your production environment.
122
- # @see https://github.com/redis-rb/redis-cluster-client/issues/299
123
- def with(key: nil, hashtag: nil, write: true)
124
- key = process_with_arguments(key, hashtag)
125
- node_key = @router.find_node_key_by_key(key, primary: write)
126
- node = @router.find_node(node_key)
127
- node.with { |c| yield ::RedisClient::Cluster::PinningNode.new(c) }
120
+ def with(...)
121
+ raise NotImplementedError, 'No way to use'
128
122
  end
129
123
 
130
124
  def close
@@ -135,19 +129,6 @@ class RedisClient
135
129
 
136
130
  private
137
131
 
138
- def process_with_arguments(key, hashtag) # rubocop:disable Metrics/CyclomaticComplexity
139
- raise ArgumentError, 'Only one of key or hashtag may be provided' if key && hashtag
140
-
141
- if hashtag
142
- # The documentation says not to wrap your hashtag in {}, but people will probably
143
- # do it anyway and it's easy for us to fix here.
144
- key = hashtag&.match?(/^{.*}$/) ? hashtag : "{#{hashtag}}"
145
- end
146
- raise ArgumentError, 'One of key or hashtag must be provided' if key.nil? || key.empty?
147
-
148
- key
149
- end
150
-
151
132
  def method_missing(name, *args, **kwargs, &block)
152
133
  if @router.command_exists?(name)
153
134
  args.unshift(name)
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.8.1
4
+ version: 0.9.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-04-13 00:00:00.000000000 Z
11
+ date: 2024-05-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis-client
@@ -50,7 +50,6 @@ files:
50
50
  - lib/redis_client/cluster/node_key.rb
51
51
  - lib/redis_client/cluster/normalized_cmd_name.rb
52
52
  - lib/redis_client/cluster/optimistic_locking.rb
53
- - lib/redis_client/cluster/pinning_node.rb
54
53
  - lib/redis_client/cluster/pipeline.rb
55
54
  - lib/redis_client/cluster/pub_sub.rb
56
55
  - lib/redis_client/cluster/router.rb
@@ -78,7 +77,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
78
77
  - !ruby/object:Gem::Version
79
78
  version: '0'
80
79
  requirements: []
81
- rubygems_version: 3.5.3
80
+ rubygems_version: 3.5.9
82
81
  signing_key:
83
82
  specification_version: 4
84
83
  summary: A Redis cluster client for Ruby
@@ -1,35 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class RedisClient
4
- class Cluster
5
- class PinningNode
6
- def initialize(client)
7
- @client = client
8
- end
9
-
10
- def call(*args, **kwargs, &block)
11
- @client.call(*args, **kwargs, &block)
12
- end
13
-
14
- def call_v(args, &block)
15
- @client.call_v(args, &block)
16
- end
17
-
18
- def call_once(*args, **kwargs, &block)
19
- @client.call_once(*args, **kwargs, &block)
20
- end
21
-
22
- def call_once_v(args, &block)
23
- @client.call_once_v(args, &block)
24
- end
25
-
26
- def blocking_call(timeout, *args, **kwargs, &block)
27
- @client.blocking_call(timeout, *args, **kwargs, &block)
28
- end
29
-
30
- def blocking_call_v(timeout, args, &block)
31
- @client.blocking_call_v(timeout, args, &block)
32
- end
33
- end
34
- end
35
- end