Draft: Patch mget in Redis::Cluster for cross-slot safety and efficiency
What does this MR do and why?
This MR patches the mget
command to send pipeline get
s to the right node and reconstructs the responses to match the input keys ordering.
As discussed in !104030 (closed), the alternative to breaking up a single mget
command into multiple get
is to optimise it by pipelining the get
to each node. This means our multiplicative increase is bounded by the number of redis shards/master-nodes rather than the command's argument length.
This MR includes a CacheCluster
which reads from a Redis Cluster.
See gitlab-com/gl-infra/scalability#2004 (closed)
Screenshots or screen recordings
Screenshots are required for UI changes, and strongly recommended for all other merge requests.
How to set up and validate locally
Numbered steps to set up and validate the change are strongly suggested.
Example below:
- Set up a local redis cluster
Use config config/redis.cache_cluster.yml
development:
url: "redis://127.0.0.1:7001"
cluster:
- "redis://127.0.0.1:7001"
- "redis://127.0.0.1:7101"
- "redis://127.0.0.1:7201"
Click to expand
#!/bin/bash
set -efo pipefail
COLOR='\033[1;36m'
NC='\033[0m' # No Color
function info {
echo -e "${COLOR}$@${NC}"
}
killall redis-server || true
find . -type d -name '7*' | parallel rm -r
(parallel -j0 --lb --tag 'mkdir -p {} && cp redis.conf {} && sd 7000 {} {}/redis.conf && cd {} && redis-server redis.conf' ::: 700{1,2,3} 710{1,2,3} 720{1,2,3}) &
sleep 1
redis-cli -p 7001 CLUSTER MEET 127.0.0.1 7002
redis-cli -p 7001 CLUSTER MEET 127.0.0.1 7003
sleep 5
redis-cli -p 7002 CLUSTER REPLICATE "$(redis-cli -p 7001 --raw CLUSTER MYID)"
redis-cli -p 7003 CLUSTER REPLICATE "$(redis-cli -p 7001 --raw CLUSTER MYID)"
redis-cli -p 7001 CLUSTER MEET 127.0.0.1 7101
redis-cli -p 7001 CLUSTER MEET 127.0.0.1 7102
redis-cli -p 7001 CLUSTER MEET 127.0.0.1 7103
sleep 5
redis-cli -p 7102 CLUSTER REPLICATE "$(redis-cli -p 7101 --raw CLUSTER MYID)"
redis-cli -p 7103 CLUSTER REPLICATE "$(redis-cli -p 7101 --raw CLUSTER MYID)"
redis-cli -p 7001 CLUSTER MEET 127.0.0.1 7201
redis-cli -p 7001 CLUSTER MEET 127.0.0.1 7202
redis-cli -p 7001 CLUSTER MEET 127.0.0.1 7203
sleep 5
redis-cli -p 7202 CLUSTER REPLICATE "$(redis-cli -p 7201 --raw CLUSTER MYID)"
redis-cli -p 7203 CLUSTER REPLICATE "$(redis-cli -p 7201 --raw CLUSTER MYID)"
sleep 5
redis-cli -p 7001 CLUSTER ADDSLOTS $(seq 0 16383)
sleep 5
redis-cli --cluster rebalance 127.0.0.1:7001 --cluster-use-empty-masters
sleep 5
redis-cli --cluster info 127.0.0.1:7001
- Seed data into redis cluster
[1] pry(main)> Gitlab::Redis::CacheCluster.with {|r| r.set("a", 'a')}
=> "OK"
[2] pry(main)> Gitlab::Redis::CacheCluster.with {|r| r.set("b", 'b')}
=> "OK"
[3] pry(main)> Gitlab::Redis::CacheCluster.with {|r| r.set("c", 'c')}
=> "OK"
[4] pry(main)> Gitlab::Redis::CacheCluster.with {|r| r.set("d", 'd')}
=> "OK"
- Read data using
mget
[5] pry(main)> Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
[5] pry(main)* Gitlab::Redis::CacheCluster.with {|r| r.mget("a", "b", "c", "d")}
[5] pry(main)* end
=> ["a", "b", "c", "d"]
- Same setup works with
Rails.cache
[1] pry(main)> Rails.cache.write("a", "a")
=> "OK"
[2] pry(main)> Rails.cache.write("b", "b")
=> "OK"
[3] pry(main)> Rails.cache.write("c", "c")
=> "OK"
[4] pry(main)> Rails.cache.read_multi("a", "b", "c")
=> {"a"=>"a", "b"=>"b", "c"=>"c"}
Note that a, b, c all in different keyslots and nodes.
127.0.0.1:7001> cluster keyslot a
(integer) 15495
127.0.0.1:7001> cluster keyslot b
(integer) 3300
127.0.0.1:7001> cluster keyslot c
(integer) 7365
MR acceptance checklist
This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.
-
I have evaluated the MR acceptance checklist for this MR.