Skip to content

Commit a05ef7c

Browse files
authored
perf: use struct instead of hash for memory efficiency (#142)
1 parent aa2033f commit a05ef7c

File tree

9 files changed

+287
-238
lines changed

9 files changed

+287
-238
lines changed

lib/redis_client/cluster/node.rb

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,20 @@ class Node
2020
IGNORE_GENERIC_CONFIG_KEYS = %i[url host port path].freeze
2121

2222
ReloadNeeded = Class.new(::RedisClient::Error)
23+
Info = Struct.new(
24+
'RedisNode',
25+
:id, :node_key, :role, :primary_id, :ping_sent,
26+
:pong_recv, :config_epoch, :link_state, :slots,
27+
keyword_init: true
28+
) do
29+
def primary?
30+
role == 'master'
31+
end
32+
33+
def replica?
34+
role == 'slave'
35+
end
36+
end
2337

2438
class Config < ::RedisClient::Config
2539
def initialize(scale_read: false, **kwargs)
@@ -48,7 +62,7 @@ def load_info(options, **kwargs) # rubocop:disable Metrics/AbcSize, Metrics/Cycl
4862
Thread.pass
4963
Thread.current.thread_variable_set(:index, i)
5064
reply = cli.call('CLUSTER', 'NODES')
51-
Thread.current.thread_variable_set(:info, parse_node_info(reply))
65+
Thread.current.thread_variable_set(:info, parse_cluster_node_reply(reply))
5266
rescue StandardError => e
5367
Thread.current.thread_variable_set(:error, e)
5468
ensure
@@ -70,10 +84,11 @@ def load_info(options, **kwargs) # rubocop:disable Metrics/AbcSize, Metrics/Cycl
7084

7185
raise ::RedisClient::Cluster::InitialSetupError, errors if node_info_list.nil?
7286

73-
grouped = node_info_list.compact.group_by do |rows|
74-
rows.sort_by { |row| row[:id] }
75-
.map { |r| "#{r[:id]}#{r[:node_key]}#{r[:role]}#{r[:primary_id]}#{r[:config_epoch]}" }
76-
.join
87+
grouped = node_info_list.compact.group_by do |info_list|
88+
info_list
89+
.sort_by(&:id)
90+
.map { |i| "#{i.id}#{i.node_key}#{i.role}#{i.primary_id}#{i.config_epoch}" }
91+
.join
7792
end
7893

7994
grouped.max_by { |_, v| v.size }[1].first
@@ -83,8 +98,8 @@ def load_info(options, **kwargs) # rubocop:disable Metrics/AbcSize, Metrics/Cycl
8398

8499
# @see https://redis.io/commands/cluster-nodes/
85100
# @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
87-
rows = info.split("\n").map(&:split)
101+
def parse_cluster_node_reply(reply) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
102+
rows = reply.split("\n").map(&:split)
88103
rows.each { |arr| arr[2] = arr[2].split(',') }
89104
rows.select! { |arr| arr[7] == 'connected' && (arr[2] & %w[fail? fail handshake noaddr noflags]).empty? }
90105
rows.each do |arr|
@@ -99,23 +114,25 @@ def parse_node_info(info) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticC
99114
end
100115

101116
rows.map do |arr|
102-
{ id: arr[0], node_key: arr[1], role: arr[2], primary_id: arr[3], ping_sent: arr[4],
103-
pong_recv: arr[5], config_epoch: arr[6], link_state: arr[7], slots: arr[8] }
117+
::RedisClient::Cluster::Node::Info.new(
118+
id: arr[0], node_key: arr[1], role: arr[2], primary_id: arr[3], ping_sent: arr[4],
119+
pong_recv: arr[5], config_epoch: arr[6], link_state: arr[7], slots: arr[8]
120+
)
104121
end
105122
end
106123
end
107124

108125
def initialize(
109126
options,
110-
node_info: [],
127+
node_info_list: [],
111128
with_replica: false,
112129
replica_affinity: :random,
113130
pool: nil,
114131
**kwargs
115132
)
116133

117-
@slots = build_slot_node_mappings(node_info)
118-
@replications = build_replication_mappings(node_info)
134+
@slots = build_slot_node_mappings(node_info_list)
135+
@replications = build_replication_mappings(node_info_list)
119136
@topology = make_topology_class(with_replica, replica_affinity).new(@replications, options, pool, **kwargs)
120137
@mutex = Mutex.new
121138
end
@@ -207,23 +224,23 @@ def make_topology_class(with_replica, replica_affinity)
207224
end
208225
end
209226

210-
def build_slot_node_mappings(node_info)
227+
def build_slot_node_mappings(node_info_list)
211228
slots = Array.new(SLOT_SIZE)
212-
node_info.each do |info|
213-
next if info[:slots].nil? || info[:slots].empty?
229+
node_info_list.each do |info|
230+
next if info.slots.nil? || info.slots.empty?
214231

215-
info[:slots].each { |start, last| (start..last).each { |i| slots[i] = info[:node_key] } }
232+
info.slots.each { |start, last| (start..last).each { |i| slots[i] = info.node_key } }
216233
end
217234

218235
slots
219236
end
220237

221-
def build_replication_mappings(node_info) # rubocop:disable Metrics/AbcSize
222-
dict = node_info.to_h { |info| [info[:id], info] }
223-
node_info.each_with_object(Hash.new { |h, k| h[k] = [] }) do |info, acc|
224-
primary_info = dict[info[:primary_id]]
225-
acc[primary_info[:node_key]] << info[:node_key] unless primary_info.nil?
226-
acc[info[:node_key]] if info[:role] == 'master' # for the primary which have no replicas
238+
def build_replication_mappings(node_info_list) # rubocop:disable Metrics/AbcSize
239+
dict = node_info_list.to_h { |info| [info.id, info] }
240+
node_info_list.each_with_object(Hash.new { |h, k| h[k] = [] }) do |info, acc|
241+
primary_info = dict[info.primary_id]
242+
acc[primary_info.node_key] << info.node_key unless primary_info.nil?
243+
acc[info.node_key] if info.primary? # for the primary which have no replicas
227244
end
228245
end
229246

lib/redis_client/cluster/router.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -270,12 +270,12 @@ def send_pubsub_command(method, command, args, &block) # rubocop:disable Metrics
270270
end
271271

272272
def fetch_cluster_info(config, pool: nil, **kwargs)
273-
node_info = ::RedisClient::Cluster::Node.load_info(config.per_node_key, **kwargs)
274-
node_addrs = node_info.map { |info| ::RedisClient::Cluster::NodeKey.hashify(info[:node_key]) }
273+
node_info_list = ::RedisClient::Cluster::Node.load_info(config.per_node_key, **kwargs)
274+
node_addrs = node_info_list.map { |i| ::RedisClient::Cluster::NodeKey.hashify(i.node_key) }
275275
config.update_node(node_addrs)
276276
::RedisClient::Cluster::Node.new(
277277
config.per_node_key,
278-
node_info: node_info,
278+
node_info_list: node_info_list,
279279
pool: pool,
280280
with_replica: config.use_replica?,
281281
replica_affinity: config.replica_affinity,

0 commit comments

Comments
 (0)