Skip to content

Commit 19c4eea

Browse files
authored
perf: optimize memory allocation for slot-node-mapping array (#143)
1 parent a05ef7c commit 19c4eea

File tree

2 files changed

+89
-8
lines changed

2 files changed

+89
-8
lines changed

lib/redis_client/cluster/node.rb

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,26 @@ def replica?
3535
end
3636
end
3737

38+
SLOT_OPTIMIZATION_MAX_SHARD_SIZE = 256
39+
SLOT_OPTIMIZATION_STRING = '0' * SLOT_SIZE
40+
Slot = Struct.new('RedisSlot', :slots, :primary_node_keys, keyword_init: true) do
41+
def [](slot)
42+
primary_node_keys[slots.getbyte(slot)]
43+
end
44+
45+
def []=(slot, primary_node_key)
46+
index = primary_node_keys.find_index(primary_node_key)
47+
if index.nil?
48+
raise(::RedisClient::Cluster::Node::ReloadNeeded, primary_node_key) if primary_node_keys.size >= SLOT_OPTIMIZATION_MAX_SHARD_SIZE
49+
50+
index = primary_node_keys.size
51+
primary_node_keys << primary_node_key
52+
end
53+
54+
slots.setbyte(slot, index)
55+
end
56+
end
57+
3858
class Config < ::RedisClient::Config
3959
def initialize(scale_read: false, **kwargs)
4060
@scale_read = scale_read
@@ -225,7 +245,7 @@ def make_topology_class(with_replica, replica_affinity)
225245
end
226246

227247
def build_slot_node_mappings(node_info_list)
228-
slots = Array.new(SLOT_SIZE)
248+
slots = make_array_for_slot_node_mappings(node_info_list)
229249
node_info_list.each do |info|
230250
next if info.slots.nil? || info.slots.empty?
231251

@@ -235,6 +255,15 @@ def build_slot_node_mappings(node_info_list)
235255
slots
236256
end
237257

258+
def make_array_for_slot_node_mappings(node_info_list)
259+
return Array.new(SLOT_SIZE) if node_info_list.count(&:primary?) > SLOT_OPTIMIZATION_MAX_SHARD_SIZE
260+
261+
::RedisClient::Cluster::Node::Slot.new(
262+
slots: String.new(SLOT_OPTIMIZATION_STRING, encoding: Encoding::BINARY, capacity: SLOT_SIZE),
263+
primary_node_keys: node_info_list.select(&:primary?).map(&:node_key)
264+
)
265+
end
266+
238267
def build_replication_mappings(node_info_list) # rubocop:disable Metrics/AbcSize
239268
dict = node_info_list.to_h { |info| [info.id, info] }
240269
node_info_list.each_with_object(Hash.new { |h, k| h[k] = [] }) do |info, acc|

test/redis_client/cluster/test_node.rb

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ def test_any_replica_node_key
334334
def test_update_slot
335335
sample_slot = 0
336336
base_node_key = @test_node.find_node_key_of_primary(sample_slot)
337-
another_node_key = @test_node_info_list.find { |info| info.node_key != base_node_key && info.primary? }
337+
another_node_key = @test_node_info_list.find { |info| info.node_key != base_node_key && info.primary? }&.node_key
338338
@test_node.update_slot(sample_slot, another_node_key)
339339
assert_equal(another_node_key, @test_node.find_node_key_of_primary(sample_slot))
340340
end
@@ -353,12 +353,12 @@ def test_make_topology_class
353353

354354
def test_build_slot_node_mappings
355355
node_info_list = [
356-
{ node_key: '127.0.0.1:7001', slots: [[0, 3000], [3002, 5460], [15_001, 15_001]] },
357-
{ node_key: '127.0.0.1:7002', slots: [[3001, 3001], [5461, 7000], [7002, 10_922]] },
358-
{ node_key: '127.0.0.1:7003', slots: [[7001, 7001], [10_923, 15_000], [15_002, 16_383]] },
359-
{ node_key: '127.0.0.1:7004', slots: [] },
360-
{ node_key: '127.0.0.1:7005', slots: [] },
361-
{ node_key: '127.0.0.1:7006', slots: [] }
356+
{ node_key: '127.0.0.1:7001', role: 'master', slots: [[0, 3000], [3002, 5460], [15_001, 15_001]] },
357+
{ node_key: '127.0.0.1:7002', role: 'master', slots: [[3001, 3001], [5461, 7000], [7002, 10_922]] },
358+
{ node_key: '127.0.0.1:7003', role: 'master', slots: [[7001, 7001], [10_923, 15_000], [15_002, 16_383]] },
359+
{ node_key: '127.0.0.1:7004', role: 'slave', slots: [] },
360+
{ node_key: '127.0.0.1:7005', role: 'slave', slots: [] },
361+
{ node_key: '127.0.0.1:7006', role: 'slave', slots: [] }
362362
].map { |info| ::RedisClient::Cluster::Node::Info.new(**info) }
363363

364364
got = @test_node.send(:build_slot_node_mappings, node_info_list)
@@ -372,6 +372,58 @@ def test_build_slot_node_mappings
372372
end
373373
end
374374

375+
def test_make_array_for_slot_node_mappings_optimized
376+
node_info_list = Array.new(256) do |i|
377+
::RedisClient::Cluster::Node::Info.new(
378+
node_key: "127.0.0.1:#{1024 + i + 1}",
379+
role: 'master'
380+
)
381+
end
382+
383+
want = node_info_list.first.node_key
384+
got = @test_node.send(:make_array_for_slot_node_mappings, node_info_list)
385+
assert_instance_of(Struct::RedisSlot, got)
386+
::RedisClient::Cluster::Node::SLOT_SIZE.times do |i|
387+
got[i] = want
388+
assert_equal(want, got[i], "Case: #{i}")
389+
end
390+
end
391+
392+
def test_make_array_for_slot_node_mappings_unoptimized
393+
node_info_list = Array.new(257) do |i|
394+
::RedisClient::Cluster::Node::Info.new(
395+
node_key: "127.0.0.1:#{1024 + i + 1}",
396+
role: 'master'
397+
)
398+
end
399+
400+
want = node_info_list.first.node_key
401+
got = @test_node.send(:make_array_for_slot_node_mappings, node_info_list)
402+
assert_instance_of(Array, got)
403+
::RedisClient::Cluster::Node::SLOT_SIZE.times do |i|
404+
got[i] = want
405+
assert_equal(want, got[i], "Case: #{i}")
406+
end
407+
end
408+
409+
def test_make_array_for_slot_node_mappings_max_shard_size
410+
node_info_list = Array.new(255) do |i|
411+
::RedisClient::Cluster::Node::Info.new(
412+
node_key: "127.0.0.1:#{1024 + i + 1}",
413+
role: 'master'
414+
)
415+
end
416+
417+
got = @test_node.send(:make_array_for_slot_node_mappings, node_info_list)
418+
assert_instance_of(Struct::RedisSlot, got)
419+
420+
::RedisClient::Cluster::Node::SLOT_SIZE.times { |i| got[i] = node_info_list.first.node_key }
421+
422+
got[0] = 'newbie:6379'
423+
assert_equal('newbie:6379', got[0])
424+
assert_raises(::RedisClient::Cluster::Node::ReloadNeeded) { got[0] = 'zombie:6379' }
425+
end
426+
375427
def test_build_replication_mappings_regular
376428
node_key1 = '127.0.0.1:7001'
377429
node_key2 = '127.0.0.1:7002'

0 commit comments

Comments
 (0)