redis-cluster-client 0.4.17 → 0.5.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: bc35dc957459313cb97b78fae42fadb612788e8e65eb49449db24b66294057fa
4
- data.tar.gz: 03cf3604892eb1a881123bdfeabaeac30816ed940a827c5390b2320b3e9e26f0
3
+ metadata.gz: ac7d6ca1474a42ad2d4027de72dbd087f6d248b3678ad2e360ed55b56dc60790
4
+ data.tar.gz: ce4de83f3f601772df8c95381a65172245967bd9568ff7959aec5858e55fb7af
5
5
  SHA512:
6
- metadata.gz: 7f8de53c53938381fb88c53ac2f13e8b7a78a3ee5653493fe0496acebaf0da57b568fb8c4436f8d19a7ba84d55018d9e4786c3726af2586581d0e7daee7b383f
7
- data.tar.gz: 7b65b9e12fd251ca8a6c278c4fb245fc08b114c9cbfac58f88e5aca81d8b9ac9aa4939ef9dbf2ee042f313764b3205a9490453b7a2310915338e50c2871e4c47
6
+ metadata.gz: 3cb2167fb97c6ab7ccba66e888521a1a119e2f17a0c373829bce0abb41518ead4005c0a7c176373c45942e9cd9fca71355b9898e7cc2b67db22d6291a76b324e
7
+ data.tar.gz: 1da6192fcb6f33359b508ff4e3445ec029373ff9606f78170051a682a29371524d6f08864f4a0887b3cbc298e462d13fe8cc9080eb29bd9cf8d965c7a766409e
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'redis_client/cluster/node/replica_mixin'
4
+
5
+ class RedisClient
6
+ class Cluster
7
+ class Node
8
+ class RandomReplicaOrPrimary
9
+ include ::RedisClient::Cluster::Node::ReplicaMixin
10
+
11
+ def replica_clients
12
+ keys = @replications.values.filter_map(&:sample)
13
+ @clients.select { |k, _| keys.include?(k) }
14
+ end
15
+
16
+ def clients_for_scanning(seed: nil)
17
+ random = seed.nil? ? Random : Random.new(seed)
18
+ keys = @replications.map do |primary_node_key, replica_node_keys|
19
+ decide_use_primary?(random, replica_node_keys.size) ? primary_node_key : replica_node_keys.sample(random: random)
20
+ end
21
+
22
+ clients.select { |k, _| keys.include?(k) }
23
+ end
24
+
25
+ def find_node_key_of_replica(primary_node_key, seed: nil)
26
+ random = seed.nil? ? Random : Random.new(seed)
27
+
28
+ replica_node_keys = @replications.fetch(primary_node_key, EMPTY_ARRAY)
29
+ if decide_use_primary?(random, replica_node_keys.size)
30
+ primary_node_key
31
+ else
32
+ replica_node_keys.sample(random: random) || primary_node_key
33
+ end
34
+ end
35
+
36
+ def any_replica_node_key(seed: nil)
37
+ random = seed.nil? ? Random : Random.new(seed)
38
+ @replica_node_keys.sample(random: random) || any_primary_node_key(seed: seed)
39
+ end
40
+
41
+ private
42
+
43
+ # Randomly equally likely choose node to read between primary and all replicas
44
+ # e.g. 1 primary + 1 replica = 50% probability to read from primary
45
+ # e.g. 1 primary + 2 replica = 33% probability to read from primary
46
+ # e.g. 1 primary + 0 replica = 100% probability to read from primary
47
+ def decide_use_primary?(random, replica_nodes)
48
+ primary_nodes = 1.0
49
+ total = primary_nodes + replica_nodes
50
+ random.rand < primary_nodes / total
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -5,6 +5,7 @@ require 'redis_client/config'
5
5
  require 'redis_client/cluster/errors'
6
6
  require 'redis_client/cluster/node/primary_only'
7
7
  require 'redis_client/cluster/node/random_replica'
8
+ require 'redis_client/cluster/node/random_replica_or_primary'
8
9
  require 'redis_client/cluster/node/latency_replica'
9
10
 
10
11
  class RedisClient
@@ -284,6 +285,8 @@ class RedisClient
284
285
  def make_topology_class(with_replica, replica_affinity)
285
286
  if with_replica && replica_affinity == :random
286
287
  ::RedisClient::Cluster::Node::RandomReplica
288
+ elsif with_replica && replica_affinity == :random_with_primary
289
+ ::RedisClient::Cluster::Node::RandomReplicaOrPrimary
287
290
  elsif with_replica && replica_affinity == :latency
288
291
  ::RedisClient::Cluster::Node::LatencyReplica
289
292
  else
@@ -149,7 +149,7 @@ class RedisClient
149
149
  all_replies = errors = nil
150
150
  @pipelines&.each_slice(MAX_THREADS) do |chuncked_pipelines|
151
151
  chuncked_pipelines
152
- .map { |node_key, pipeline| [node_key, build_thread_for_pipeline(@router, node_key, pipeline)] }
152
+ .map { |node_key, pipeline| [node_key, build_thread_for_pipeline(@router.find_node(node_key), pipeline)] }
153
153
  .each do |node_key, thread|
154
154
  case v = thread.value
155
155
  when ::RedisClient::Cluster::Pipeline::RedirectionNeeded
@@ -182,9 +182,9 @@ class RedisClient
182
182
  @pipelines[node_key]
183
183
  end
184
184
 
185
- def build_thread_for_pipeline(router, node_key, pipeline)
186
- Thread.new(router, node_key, pipeline) do |rt, nk, pl|
187
- replies = do_pipelining(rt.find_node(nk), pl)
185
+ def build_thread_for_pipeline(client, pipeline)
186
+ Thread.new(client, pipeline) do |cli, pl|
187
+ replies = do_pipelining(cli, pl)
188
188
  raise ReplySizeError, "commands: #{pl._size}, replies: #{replies.size}" if pl._size != replies.size
189
189
 
190
190
  replies
@@ -6,44 +6,44 @@ class RedisClient
6
6
  class Cluster
7
7
  class PubSub
8
8
  class State
9
- def initialize(client)
9
+ def initialize(client, queue)
10
10
  @client = client
11
11
  @worker = nil
12
+ @queue = queue
12
13
  end
13
14
 
14
15
  def call(command)
15
16
  @client.call_v(command)
16
17
  end
17
18
 
19
+ def ensure_worker
20
+ @worker = spawn_worker(@client, @queue) unless @worker&.alive?
21
+ end
22
+
18
23
  def close
19
24
  @worker.exit if @worker&.alive?
20
25
  @client.close
21
26
  end
22
27
 
23
- def take_message(timeout)
24
- @worker = subscribe(@client, timeout) if @worker.nil?
25
- return if @worker.alive?
26
-
27
- message = @worker.value
28
- @worker = nil
29
- message
30
- end
31
-
32
28
  private
33
29
 
34
- def subscribe(client, timeout)
35
- Thread.new(client, timeout) do |pubsub, to|
36
- pubsub.next_event(to)
37
- rescue StandardError => e
38
- e
30
+ def spawn_worker(client, queue)
31
+ Thread.new(client, queue) do |pubsub, q|
32
+ loop do
33
+ q << pubsub.next_event
34
+ rescue StandardError => e
35
+ q << e
36
+ end
39
37
  end
40
38
  end
41
39
  end
42
40
 
41
+ BUF_SIZE = Integer(ENV.fetch('REDIS_CLIENT_PUBSUB_BUF_SIZE', 1024))
42
+
43
43
  def initialize(router, command_builder)
44
44
  @router = router
45
45
  @command_builder = command_builder
46
- @state_list = []
46
+ @queue = SizedQueue.new(BUF_SIZE)
47
47
  @state_dict = {}
48
48
  end
49
49
 
@@ -56,26 +56,25 @@ class RedisClient
56
56
  end
57
57
 
58
58
  def close
59
- @state_list.each(&:close)
60
- @state_list.clear
59
+ @state_dict.each_value(&:close)
61
60
  @state_dict.clear
61
+ @queue.clear
62
62
  end
63
63
 
64
64
  def next_event(timeout = nil)
65
- return if @state_list.empty?
66
-
67
- @state_list.shuffle!
65
+ @state_dict.each_value(&:ensure_worker)
68
66
  max_duration = calc_max_duration(timeout)
69
67
  starting = obtain_current_time
68
+
70
69
  loop do
71
70
  break if max_duration > 0 && obtain_current_time - starting > max_duration
72
71
 
73
- @state_list.each do |pubsub|
74
- message = pubsub.take_message(timeout)
75
- return message if message
72
+ case event = @queue.pop(true)
73
+ when StandardError then raise event
74
+ when Array then break event
76
75
  end
77
-
78
- sleep 0.001
76
+ rescue ThreadError
77
+ sleep 0.005
79
78
  end
80
79
  end
81
80
 
@@ -100,8 +99,7 @@ class RedisClient
100
99
  def add_state(node_key)
101
100
  return @state_dict[node_key] if @state_dict.key?(node_key)
102
101
 
103
- state = State.new(@router.find_node(node_key).pubsub)
104
- @state_list << state
102
+ state = State.new(@router.find_node(node_key).pubsub, @queue)
105
103
  @state_dict[node_key] = state
106
104
  end
107
105
 
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.17
4
+ version: 0.5.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: 2023-09-02 00:00:00.000000000 Z
11
+ date: 2023-09-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis-client
@@ -40,6 +40,7 @@ files:
40
40
  - lib/redis_client/cluster/node/latency_replica.rb
41
41
  - lib/redis_client/cluster/node/primary_only.rb
42
42
  - lib/redis_client/cluster/node/random_replica.rb
43
+ - lib/redis_client/cluster/node/random_replica_or_primary.rb
43
44
  - lib/redis_client/cluster/node/replica_mixin.rb
44
45
  - lib/redis_client/cluster/node_key.rb
45
46
  - lib/redis_client/cluster/normalized_cmd_name.rb