Skip to content

Commit bc6debe

Browse files
authored
perf: use struct instead of hash for memory consumption (#140)
1 parent c395ecc commit bc6debe

File tree

2 files changed

+29
-91
lines changed

2 files changed

+29
-91
lines changed

lib/redis_client/cluster/command.rb

Lines changed: 22 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ class Cluster
99
class Command
1010
EMPTY_STRING = ''
1111

12+
RedisCommand = Struct.new('RedisCommand', :first_key_position, :write?, :readonly?, keyword_init: true)
13+
1214
class << self
1315
def load(nodes)
1416
errors = []
@@ -17,8 +19,8 @@ def load(nodes)
1719
break unless cmd.nil?
1820

1921
reply = node.call('COMMAND')
20-
details = parse_command_details(reply)
21-
cmd = ::RedisClient::Cluster::Command.new(details)
22+
commands = parse_command_reply(reply)
23+
cmd = ::RedisClient::Cluster::Command.new(commands)
2224
rescue ::RedisClient::Error => e
2325
errors << e
2426
end
@@ -30,15 +32,22 @@ def load(nodes)
3032

3133
private
3234

33-
def parse_command_details(rows)
35+
def parse_command_reply(rows)
3436
rows&.reject { |row| row[0].nil? }.to_h do |row|
35-
[row[0].downcase, { arity: row[1], flags: row[2], first: row[3], last: row[4], step: row[5] }]
37+
[
38+
row[0].downcase,
39+
RedisCommand.new(
40+
first_key_position: row[3],
41+
write?: row[2].include?('write'),
42+
readonly?: row[2].include?('readonly')
43+
)
44+
]
3645
end
3746
end
3847
end
3948

40-
def initialize(details)
41-
@details = pick_details(details)
49+
def initialize(commands)
50+
@commands = commands || {}
4251
end
4352

4453
def extract_first_key(command)
@@ -51,39 +60,23 @@ def extract_first_key(command)
5160
end
5261

5362
def should_send_to_primary?(command)
54-
dig_details(command, :write)
63+
name = ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_command(command)
64+
@commands[name]&.write?
5565
end
5666

5767
def should_send_to_replica?(command)
58-
dig_details(command, :readonly)
68+
name = ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_command(command)
69+
@commands[name]&.readonly?
5970
end
6071

6172
def exists?(name)
62-
key = ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_name(name)
63-
@details.key?(key)
73+
@commands.key?(::RedisClient::Cluster::NormalizedCmdName.instance.get_by_name(name))
6474
end
6575

6676
private
6777

68-
def pick_details(details)
69-
(details || {}).transform_values do |detail|
70-
{
71-
first_key_position: detail[:first],
72-
write: detail[:flags].include?('write'),
73-
readonly: detail[:flags].include?('readonly')
74-
}
75-
end
76-
end
77-
78-
def dig_details(command, key)
79-
name = ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_command(command)
80-
return if name.empty? || !@details.key?(name)
81-
82-
@details.fetch(name).fetch(key)
83-
end
84-
8578
def determine_first_key_position(command) # rubocop:disable Metrics/CyclomaticComplexity
86-
case ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_command(command)
79+
case name = ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_command(command)
8780
when 'eval', 'evalsha', 'zinterstore', 'zunionstore' then 3
8881
when 'object' then 2
8982
when 'memory'
@@ -93,7 +86,7 @@ def determine_first_key_position(command) # rubocop:disable Metrics/CyclomaticCo
9386
when 'xread', 'xreadgroup'
9487
determine_optional_key_position(command, 'streams')
9588
else
96-
dig_details(command, :first_key_position).to_i
89+
@commands[name]&.first_key_position.to_i
9790
end
9891
end
9992

test/redis_client/cluster/test_command.rb

Lines changed: 7 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -31,37 +31,36 @@ def test_load
3131
end
3232
end
3333

34-
def test_parse_command_details
35-
keys = %i[arity flags first last step].freeze
34+
def test_parse_command_reply
3635
[
3736
{
3837
rows: [
3938
['get', 2, Set['readonly', 'fast'], 1, 1, 1, Set['@read', '@string', '@fast'], Set[], Set[], Set[]],
4039
['set', -3, Set['write', 'denyoom', 'movablekeys'], 1, 1, 1, Set['@write', '@string', '@slow'], Set[], Set[], Set[]]
4140
],
4241
want: {
43-
'get' => { arity: 2, flags: Set['readonly', 'fast'], first: 1, last: 1, step: 1 },
44-
'set' => { arity: -3, flags: Set['write', 'denyoom', 'movablekeys'], first: 1, last: 1, step: 1 }
42+
'get' => { first_key_position: 1, write?: false, readonly?: true },
43+
'set' => { first_key_position: 1, write?: true, readonly?: false }
4544
}
4645
},
4746
{
4847
rows: [
4948
['GET', 2, Set['readonly', 'fast'], 1, 1, 1, Set['@read', '@string', '@fast'], Set[], Set[], Set[]]
5049
],
5150
want: {
52-
'get' => { arity: 2, flags: Set['readonly', 'fast'], first: 1, last: 1, step: 1 }
51+
'get' => { first_key_position: 1, write?: false, readonly?: true }
5352
}
5453
},
5554
{ rows: [[]], want: {} },
5655
{ rows: [], want: {} },
5756
{ rows: nil, want: {} }
5857
].each_with_index do |c, idx|
5958
msg = "Case: #{idx}"
60-
got = ::RedisClient::Cluster::Command.send(:parse_command_details, c[:rows])
59+
got = ::RedisClient::Cluster::Command.send(:parse_command_reply, c[:rows])
6160
assert_equal(c[:want].size, got.size, msg)
6261
assert_equal(c[:want].keys.sort, got.keys.sort, msg)
63-
c[:want].each do |k1, v|
64-
keys.each { |k2| assert_equal(v[k2], got[k1][k2], "#{msg}: #{k2}") }
62+
c[:want].each do |k, v|
63+
assert_equal(v, got[k].to_h, "#{msg}: #{k}")
6564
end
6665
end
6766
end
@@ -134,60 +133,6 @@ def test_exists?
134133
end
135134
end
136135

137-
def test_pick_details
138-
keys = %i[first_key_position write readonly].freeze
139-
[
140-
{
141-
details: {
142-
'get' => { arity: 2, flags: Set['readonly', 'fast'], first: 1, last: 1, step: 1 },
143-
'set' => { arity: -3, flags: Set['write', 'denyoom', 'movablekeys'], first: 1, last: 1, step: 1 }
144-
},
145-
want: {
146-
'get' => { first_key_position: 1, write: false, readonly: true },
147-
'set' => { first_key_position: 1, write: true, readonly: false }
148-
}
149-
},
150-
{ details: {}, want: {} },
151-
{ details: nil, want: {} }
152-
].each_with_index do |c, idx|
153-
msg = "Case: #{idx}"
154-
cmd = ::RedisClient::Cluster::Command.new(c[:details])
155-
got = cmd.send(:pick_details, c[:details])
156-
assert_equal(c[:want].size, got.size, msg)
157-
assert_equal(c[:want].keys.sort, got.keys.sort, msg)
158-
c[:want].each do |k1, v|
159-
keys.each { |k2| assert_equal(v[k2], got[k1][k2], "#{msg}: #{k2}") }
160-
end
161-
end
162-
end
163-
164-
def test_dig_details
165-
cmd = ::RedisClient::Cluster::Command.new(
166-
{
167-
'get' => { arity: 2, flags: Set['readonly', 'fast'], first: 1, last: 1, step: 1 },
168-
'set' => { arity: -3, flags: Set['write', 'denyoom', 'movablekeys'], first: 1, last: 1, step: 1 }
169-
}
170-
)
171-
[
172-
{ params: { command: %w[SET foo 1], key: :first_key_position }, want: 1 },
173-
{ params: { command: %w[SET foo 1], key: :write }, want: true },
174-
{ params: { command: %w[set foo 1], key: :write }, want: true },
175-
{ params: { command: %w[SET foo 1], key: :readonly }, want: false },
176-
{ params: { command: %w[GET foo], key: :first_key_position }, want: 1 },
177-
{ params: { command: %w[GET foo], key: :write }, want: false },
178-
{ params: { command: %w[GET foo], key: :readonly }, want: true },
179-
{ params: { command: %w[get foo], key: :readonly }, want: true },
180-
{ params: { command: %w[UNKNOWN foo], key: :readonly }, want: nil },
181-
{ params: { command: [['SET'], 'foo', 1], key: :write }, want: true },
182-
{ params: { command: [], key: :readonly }, want: nil },
183-
{ params: { command: nil, key: :readonly }, want: nil }
184-
].each_with_index do |c, idx|
185-
msg = "Case: #{idx}"
186-
got = cmd.send(:dig_details, c[:params][:command], c[:params][:key])
187-
c[:want].nil? ? assert_nil(got, msg) : assert_equal(c[:want], got, msg)
188-
end
189-
end
190-
191136
def test_determine_first_key_position
192137
cmd = ::RedisClient::Cluster::Command.load(@raw_clients)
193138
[

0 commit comments

Comments
 (0)