redis-cluster-client 0.3.5 → 0.3.6

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: a697c2bd33f615cb435ee48c11c301bba56cad4eac139f30db70a183d888d891
4
- data.tar.gz: 52200e9400bd7bd7fde2019c52b6cf1e26c6c019cbf1ed12a25c2167cd7ac129
3
+ metadata.gz: 24e153973525e8cd3905e92e851194a5c1f4349cecd71ca4a71db34fa678d1d8
4
+ data.tar.gz: 963ac53058e861d50c55160f55f9b064e853c49b29811075ece80ce34fa7b0f8
5
5
  SHA512:
6
- metadata.gz: 05dc86fd1aa3cbf3cd3f0fa43d9d1c8463fcbe9e0ce1bcfe094f6095fd92d878a4bb21e6d6fa62203e91f3dc642e5869552f0b83d579cfa158b791501e1cb600
7
- data.tar.gz: 8f80c5072d4d1bdbeb3e2cb4c9f92493537a6d9002edd71426598844ba28a497171c055135bb19a1d89a325c194026195100f9a0aea89c0beac3d8e9424f7f58
6
+ metadata.gz: 841bdd5aa580dcd7c3169fe88f36b8ed888ffffd3cf50b9f75449365c430afb41d79d88f2265183119f0ed012ace418868679cad468bcf39b25aed30b6207da1
7
+ data.tar.gz: 5f36751289ba73d5471cfe44d9e3d10cb70fd517249ce48ad7ea4254de0595c869fb900fecd3b0abd690fe977a732650ebf8da80a5d95ed1464a3345b79243fa
@@ -10,7 +10,7 @@ class RedisClient
10
10
  EMPTY_STRING = ''
11
11
 
12
12
  class << self
13
- def load(nodes) # rubocop:disable Metrics/MethodLength
13
+ def load(nodes)
14
14
  errors = []
15
15
  cmd = nil
16
16
  nodes&.each do |node|
@@ -32,10 +32,7 @@ class RedisClient
32
32
 
33
33
  def parse_command_details(rows)
34
34
  rows&.reject { |row| row[0].nil? }.to_h do |row|
35
- [
36
- ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_name(row[0]),
37
- { arity: row[1], flags: row[2], first: row[3], last: row[4], step: row[5] }
38
- ]
35
+ [row[0].downcase, { arity: row[1], flags: row[2], first: row[3], last: row[4], step: row[5] }]
39
36
  end
40
37
  end
41
38
  end
@@ -85,7 +82,7 @@ class RedisClient
85
82
  @details.fetch(name).fetch(key)
86
83
  end
87
84
 
88
- def determine_first_key_position(command) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
85
+ def determine_first_key_position(command) # rubocop:disable Metrics/CyclomaticComplexity
89
86
  case ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_command(command)
90
87
  when 'eval', 'evalsha', 'zinterstore', 'zunionstore' then 3
91
88
  when 'object' then 2
@@ -34,14 +34,13 @@ class RedisClient
34
34
 
35
35
  def any_replica_node_key(seed: nil)
36
36
  random = seed.nil? ? Random : Random.new(seed)
37
- @existed_replicas.sample(random: random).first
37
+ @existed_replicas.sample(random: random)&.first
38
38
  end
39
39
 
40
40
  private
41
41
 
42
- def measure_latencies(clients) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
43
- latencies = nil
44
- clients.each_slice(::RedisClient::Cluster::Node::MAX_THREADS) do |chuncked_clients|
42
+ def measure_latencies(clients) # rubocop:disable Metrics/AbcSize
43
+ clients.each_slice(::RedisClient::Cluster::Node::MAX_THREADS).each_with_object({}) do |chuncked_clients, acc|
45
44
  threads = chuncked_clients.map do |k, v|
46
45
  Thread.new(k, v) do |node_key, client|
47
46
  Thread.pass
@@ -63,12 +62,9 @@ class RedisClient
63
62
 
64
63
  threads.each do |t|
65
64
  t.join
66
- latencies ||= {}
67
- latencies[t.thread_variable_get(:node_key)] = t.thread_variable_get(:latency)
65
+ acc[t.thread_variable_get(:node_key)] = t.thread_variable_get(:latency)
68
66
  end
69
67
  end
70
-
71
- latencies
72
68
  end
73
69
 
74
70
  def select_replica_clients(replications, clients)
@@ -37,7 +37,7 @@ class RedisClient
37
37
  end
38
38
 
39
39
  class << self
40
- def load_info(options, **kwargs) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
40
+ def load_info(options, **kwargs) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
41
41
  startup_size = options.size > MAX_STARTUP_SAMPLE ? MAX_STARTUP_SAMPLE : options.size
42
42
  node_info_list = errors = nil
43
43
  startup_options = options.to_a.sample(MAX_STARTUP_SAMPLE).to_h
@@ -83,7 +83,7 @@ class RedisClient
83
83
 
84
84
  # @see https://redis.io/commands/cluster-nodes/
85
85
  # @see https://github.com/redis/redis/blob/78960ad57b8a5e6af743d789ed8fd767e37d42b8/src/cluster.c#L4660-L4683
86
- def parse_node_info(info) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
86
+ def parse_node_info(info) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
87
87
  rows = info.split("\n").map(&:split)
88
88
  rows.each { |arr| arr[2] = arr[2].split(',') }
89
89
  rows.select! { |arr| arr[7] == 'connected' && (arr[2] & %w[fail? fail handshake noaddr noflags]).empty? }
@@ -242,7 +242,7 @@ class RedisClient
242
242
  raise ::RedisClient::Cluster::ErrorCollection, errors
243
243
  end
244
244
 
245
- def try_map(clients) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
245
+ def try_map(clients) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
246
246
  results = errors = nil
247
247
  clients.each_slice(MAX_THREADS) do |chuncked_clients|
248
248
  threads = chuncked_clients.map do |k, v|
@@ -28,6 +28,7 @@ class RedisClient
28
28
 
29
29
  def clear
30
30
  @mutex.synchronize { @cache.clear }
31
+ true
31
32
  end
32
33
 
33
34
  private
@@ -2,84 +2,173 @@
2
2
 
3
3
  require 'redis_client'
4
4
  require 'redis_client/cluster/errors'
5
+ require 'redis_client/connection_mixin'
6
+ require 'redis_client/middlewares'
7
+ require 'redis_client/pooled'
5
8
 
6
9
  class RedisClient
7
10
  class Cluster
8
11
  class Pipeline
12
+ class Extended < ::RedisClient::Pipeline
13
+ attr_reader :outer_indices
14
+
15
+ def initialize(command_builder)
16
+ super
17
+ @outer_indices = nil
18
+ end
19
+
20
+ def add_outer_index(index)
21
+ @outer_indices ||= []
22
+ @outer_indices << index
23
+ end
24
+
25
+ def get_inner_index(outer_index)
26
+ @outer_indices&.find_index(outer_index)
27
+ end
28
+
29
+ def get_callee_method(inner_index)
30
+ if @timeouts.is_a?(Array) && !@timeouts[inner_index].nil?
31
+ :blocking_call_v
32
+ elsif _retryable?
33
+ :call_once_v
34
+ else
35
+ :call_v
36
+ end
37
+ end
38
+
39
+ def get_command(inner_index)
40
+ @commands.is_a?(Array) ? @commands[inner_index] : nil
41
+ end
42
+
43
+ def get_timeout(inner_index)
44
+ @timeouts.is_a?(Array) ? @timeouts[inner_index] : nil
45
+ end
46
+
47
+ def get_block(inner_index)
48
+ @blocks.is_a?(Array) ? @blocks[inner_index] : nil
49
+ end
50
+ end
51
+
52
+ ::RedisClient::ConnectionMixin.module_eval do
53
+ def call_pipelined_aware_of_redirection(commands, timeouts) # rubocop:disable Metrics/AbcSize
54
+ size = commands.size
55
+ results = Array.new(commands.size)
56
+ @pending_reads += size
57
+ write_multi(commands)
58
+
59
+ redirection_indices = nil
60
+ size.times do |index|
61
+ timeout = timeouts && timeouts[index]
62
+ result = read(timeout)
63
+ @pending_reads -= 1
64
+ if result.is_a?(CommandError)
65
+ result._set_command(commands[index])
66
+ if result.message.start_with?('MOVED', 'ASK')
67
+ redirection_indices ||= []
68
+ redirection_indices << index
69
+ end
70
+ end
71
+
72
+ results[index] = result
73
+ end
74
+
75
+ return results if redirection_indices.nil?
76
+
77
+ err = ::RedisClient::Cluster::Pipeline::RedirectionNeeded.new
78
+ err.replies = results
79
+ err.indices = redirection_indices
80
+ raise err
81
+ end
82
+ end
83
+
9
84
  ReplySizeError = Class.new(::RedisClient::Error)
85
+
86
+ class RedirectionNeeded < ::RedisClient::Error
87
+ attr_accessor :replies, :indices
88
+ end
89
+
10
90
  MAX_THREADS = Integer(ENV.fetch('REDIS_CLIENT_MAX_THREADS', 5))
11
91
 
12
92
  def initialize(router, command_builder, seed: Random.new_seed)
13
93
  @router = router
14
94
  @command_builder = command_builder
15
- @grouped = {}
16
- @size = 0
17
95
  @seed = seed
96
+ @pipelines = nil
97
+ @size = 0
18
98
  end
19
99
 
20
100
  def call(*args, **kwargs, &block)
21
101
  command = @command_builder.generate(args, kwargs)
22
102
  node_key = @router.find_node_key(command, seed: @seed)
23
- add_row(node_key, [@size, :call_v, command, block])
103
+ append_pipeline(node_key).call_v(command, &block)
24
104
  end
25
105
 
26
106
  def call_v(args, &block)
27
107
  command = @command_builder.generate(args)
28
108
  node_key = @router.find_node_key(command, seed: @seed)
29
- add_row(node_key, [@size, :call_v, command, block])
109
+ append_pipeline(node_key).call_v(command, &block)
30
110
  end
31
111
 
32
112
  def call_once(*args, **kwargs, &block)
33
113
  command = @command_builder.generate(args, kwargs)
34
114
  node_key = @router.find_node_key(command, seed: @seed)
35
- add_row(node_key, [@size, :call_once_v, command, block])
115
+ append_pipeline(node_key).call_once_v(command, &block)
36
116
  end
37
117
 
38
118
  def call_once_v(args, &block)
39
119
  command = @command_builder.generate(args)
40
120
  node_key = @router.find_node_key(command, seed: @seed)
41
- add_row(node_key, [@size, :call_once_v, command, block])
121
+ append_pipeline(node_key).call_once_v(command, &block)
42
122
  end
43
123
 
44
124
  def blocking_call(timeout, *args, **kwargs, &block)
45
125
  command = @command_builder.generate(args, kwargs)
46
126
  node_key = @router.find_node_key(command, seed: @seed)
47
- add_row(node_key, [@size, :blocking_call_v, timeout, command, block])
127
+ append_pipeline(node_key).blocking_call_v(timeout, command, &block)
48
128
  end
49
129
 
50
130
  def blocking_call_v(timeout, args, &block)
51
131
  command = @command_builder.generate(args)
52
132
  node_key = @router.find_node_key(command, seed: @seed)
53
- add_row(node_key, [@size, :blocking_call_v, timeout, command, block])
133
+ append_pipeline(node_key).blocking_call_v(timeout, command, &block)
54
134
  end
55
135
 
56
136
  def empty?
57
137
  @size.zero?
58
138
  end
59
139
 
60
- # TODO: https://github.com/redis-rb/redis-cluster-client/issues/37 handle redirections
61
- def execute # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
140
+ def execute # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
62
141
  all_replies = errors = nil
63
- @grouped.each_slice(MAX_THREADS) do |chuncked_grouped|
64
- threads = chuncked_grouped.map do |k, v|
65
- Thread.new(@router, k, v) do |router, node_key, rows|
142
+ @pipelines&.each_slice(MAX_THREADS) do |chuncked_pipelines|
143
+ threads = chuncked_pipelines.map do |node_key, pipeline|
144
+ Thread.new(node_key, pipeline) do |nk, pl|
66
145
  Thread.pass
67
- replies = do_pipelining(router.find_node(node_key), rows)
68
- raise ReplySizeError, "commands: #{rows.size}, replies: #{replies.size}" if rows.size != replies.size
146
+ Thread.current.thread_variable_set(:node_key, nk)
147
+ replies = do_pipelining(@router.find_node(nk), pl)
148
+ raise ReplySizeError, "commands: #{pl._size}, replies: #{replies.size}" if pl._size != replies.size
69
149
 
70
- Thread.current.thread_variable_set(:rows, rows)
71
150
  Thread.current.thread_variable_set(:replies, replies)
151
+ rescue ::RedisClient::Cluster::Pipeline::RedirectionNeeded => e
152
+ Thread.current.thread_variable_set(:redirection_needed, e)
72
153
  rescue StandardError => e
73
- Thread.current.thread_variable_set(:node_key, node_key)
74
154
  Thread.current.thread_variable_set(:error, e)
75
155
  end
76
156
  end
77
157
 
78
158
  threads.each do |t|
79
159
  t.join
160
+
80
161
  if t.thread_variable?(:replies)
81
162
  all_replies ||= Array.new(@size)
82
- t.thread_variable_get(:rows).each_with_index { |r, i| all_replies[r.first] = t.thread_variable_get(:replies)[i] }
163
+ @pipelines[t.thread_variable_get(:node_key)]
164
+ .outer_indices
165
+ .each_with_index { |outer, inner| all_replies[outer] = t.thread_variable_get(:replies)[inner] }
166
+ elsif t.thread_variable?(:redirection_needed)
167
+ all_replies ||= Array.new(@size)
168
+ pipeline = @pipelines[t.thread_variable_get(:node_key)]
169
+ err = t.thread_variable_get(:redirection_needed)
170
+ err.indices.each { |i| err.replies[i] = handle_redirection(err.replies[i], pipeline, i) }
171
+ pipeline.outer_indices.each_with_index { |outer, inner| all_replies[outer] = err.replies[inner] }
83
172
  elsif t.thread_variable?(:error)
84
173
  errors ||= {}
85
174
  errors[t.thread_variable_get(:node_key)] = t.thread_variable_get(:error)
@@ -87,28 +176,78 @@ class RedisClient
87
176
  end
88
177
  end
89
178
 
90
- return all_replies if errors.nil?
179
+ raise ::RedisClient::Cluster::ErrorCollection, errors unless errors.nil?
91
180
 
92
- raise ::RedisClient::Cluster::ErrorCollection, errors
181
+ all_replies
93
182
  end
94
183
 
95
184
  private
96
185
 
97
- def add_row(node_key, row)
98
- @grouped[node_key] = [] unless @grouped.key?(node_key)
99
- @grouped[node_key] << row
186
+ def append_pipeline(node_key)
187
+ @pipelines ||= {}
188
+ @pipelines[node_key] ||= ::RedisClient::Cluster::Pipeline::Extended.new(@command_builder)
189
+ @pipelines[node_key].add_outer_index(@size)
100
190
  @size += 1
191
+ @pipelines[node_key]
101
192
  end
102
193
 
103
- def do_pipelining(node, rows)
104
- node.pipelined do |pipeline|
105
- rows.each do |row|
106
- case row.size
107
- when 4 then pipeline.send(row[1], row[2], &row[3])
108
- when 5 then pipeline.send(row[1], row[2], row[3], &row[4])
109
- end
194
+ def do_pipelining(client, pipeline)
195
+ case client
196
+ when ::RedisClient then send_pipeline(client, pipeline)
197
+ when ::RedisClient::Pooled then client.with { |cli| send_pipeline(cli, pipeline) }
198
+ else raise NotImplementedError, "#{client.class.name}#pipelined for cluster client"
199
+ end
200
+ end
201
+
202
+ def send_pipeline(client, pipeline)
203
+ results = client.send(:ensure_connected, retryable: pipeline._retryable?) do |connection|
204
+ commands = pipeline._commands
205
+ ::RedisClient::Middlewares.call_pipelined(commands, client.config) do
206
+ connection.call_pipelined_aware_of_redirection(commands, pipeline._timeouts)
110
207
  end
111
208
  end
209
+
210
+ pipeline._coerce!(results)
211
+ end
212
+
213
+ def handle_redirection(err, pipeline, inner_index)
214
+ return err unless err.is_a?(::RedisClient::CommandError)
215
+
216
+ if err.message.start_with?('MOVED')
217
+ node = @router.assign_redirection_node(err.message)
218
+ try_redirection(node, pipeline, inner_index)
219
+ elsif err.message.start_with?('ASK')
220
+ node = @router.assign_asking_node(err.message)
221
+ try_asking(node) ? try_redirection(node, pipeline, inner_index) : err
222
+ else
223
+ err
224
+ end
225
+ end
226
+
227
+ def try_redirection(node, pipeline, inner_index)
228
+ redirect_command(node, pipeline, inner_index)
229
+ rescue StandardError => e
230
+ e
231
+ end
232
+
233
+ def redirect_command(node, pipeline, inner_index)
234
+ method = pipeline.get_callee_method(inner_index)
235
+ command = pipeline.get_command(inner_index)
236
+ timeout = pipeline.get_timeout(inner_index)
237
+ block = pipeline.get_block(inner_index)
238
+ args = timeout.nil? ? [] : [timeout]
239
+
240
+ if block.nil?
241
+ @router.try_send(node, method, command, args)
242
+ else
243
+ @router.try_send(node, method, command, args, &block)
244
+ end
245
+ end
246
+
247
+ def try_asking(node)
248
+ node.call('ASKING') == 'OK'
249
+ rescue StandardError
250
+ false
112
251
  end
113
252
  end
114
253
  end
@@ -25,7 +25,7 @@ class RedisClient
25
25
  @command_builder = @config.command_builder
26
26
  end
27
27
 
28
- def send_command(method, command, *args, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
28
+ def send_command(method, command, *args, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
29
29
  cmd = ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_command(command)
30
30
  case cmd
31
31
  when 'acl', 'auth', 'bgrewriteaof', 'bgsave', 'quit', 'save'
@@ -65,7 +65,7 @@ class RedisClient
65
65
 
66
66
  # @see https://redis.io/topics/cluster-spec#redirection-and-resharding
67
67
  # Redirection and resharding
68
- def try_send(node, method, command, args, retry_count: 3, &block) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
68
+ def try_send(node, method, command, args, retry_count: 3, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
69
69
  if args.empty?
70
70
  # prevent memory allocation for variable-length args
71
71
  node.send(method, command, &block)
@@ -100,7 +100,7 @@ class RedisClient
100
100
  retry
101
101
  end
102
102
 
103
- def try_delegate(node, method, *args, retry_count: 3, **kwargs, &block) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
103
+ def try_delegate(node, method, *args, retry_count: 3, **kwargs, &block) # rubocop:disable Metrics/AbcSize
104
104
  node.send(method, *args, **kwargs, &block)
105
105
  rescue ::RedisClient::CommandError => e
106
106
  raise if retry_count <= 0
@@ -129,7 +129,7 @@ class RedisClient
129
129
  retry
130
130
  end
131
131
 
132
- def scan(*command, seed: nil, **kwargs) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
132
+ def scan(*command, seed: nil, **kwargs) # rubocop:disable Metrics/AbcSize
133
133
  command = @command_builder.generate(command, kwargs)
134
134
 
135
135
  command[1] = ZERO_CURSOR_FOR_SCAN if command.size == 1
@@ -172,7 +172,7 @@ class RedisClient
172
172
  def find_node(node_key, retry_count: 3)
173
173
  @node.find_by(node_key)
174
174
  rescue ::RedisClient::Cluster::Node::ReloadNeeded
175
- raise ::RedieClient::Cluster::NodeMightBeDown if retry_count <= 0
175
+ raise ::RedisClient::Cluster::NodeMightBeDown if retry_count <= 0
176
176
 
177
177
  update_cluster_info!
178
178
  retry_count -= 1
@@ -183,6 +183,18 @@ class RedisClient
183
183
  @command.exists?(name)
184
184
  end
185
185
 
186
+ def assign_redirection_node(err_msg)
187
+ _, slot, node_key = err_msg.split
188
+ slot = slot.to_i
189
+ @node.update_slot(slot, node_key)
190
+ find_node(node_key)
191
+ end
192
+
193
+ def assign_asking_node(err_msg)
194
+ _, _, node_key = err_msg.split
195
+ find_node(node_key)
196
+ end
197
+
186
198
  private
187
199
 
188
200
  def send_wait_command(method, command, args, retry_count: 3, &block)
@@ -258,19 +270,7 @@ class RedisClient
258
270
  end
259
271
  end
260
272
 
261
- def assign_redirection_node(err_msg)
262
- _, slot, node_key = err_msg.split
263
- slot = slot.to_i
264
- @node.update_slot(slot, node_key)
265
- find_node(node_key)
266
- end
267
-
268
- def assign_asking_node(err_msg)
269
- _, _, node_key = err_msg.split
270
- find_node(node_key)
271
- end
272
-
273
- def fetch_cluster_info(config, pool: nil, **kwargs) # rubocop:disable Metrics/MethodLength
273
+ def fetch_cluster_info(config, pool: nil, **kwargs)
274
274
  node_info = ::RedisClient::Cluster::Node.load_info(config.per_node_key, **kwargs)
275
275
  node_addrs = node_info.map { |info| ::RedisClient::Cluster::NodeKey.hashify(info[:node_key]) }
276
276
  config.update_node(node_addrs)
@@ -81,7 +81,7 @@ class RedisClient
81
81
  seed = @config.use_replica? && @config.replica_affinity == :random ? nil : Random.new_seed
82
82
  pipeline = ::RedisClient::Cluster::Pipeline.new(@router, @command_builder, seed: seed)
83
83
  yield pipeline
84
- return [] if pipeline.empty? == 0
84
+ return [] if pipeline.empty?
85
85
 
86
86
  pipeline.execute
87
87
  end
@@ -114,7 +114,7 @@ class RedisClient
114
114
  end
115
115
  end
116
116
 
117
- def parse_node_url(addr) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
117
+ def parse_node_url(addr) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
118
118
  return if addr.empty?
119
119
 
120
120
  uri = URI(addr)
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.3.5
4
+ version: 0.3.6
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-09-21 00:00:00.000000000 Z
11
+ date: 2022-09-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis-client