Update cache workload to be Redis Cluster compatible
What does this MR do and why?
This MR updates cache-workloads to be compatible with Redis Cluster. The changes:
- Implements the logic to enable cache workload to run on both Redis Cluster and a standalone Redis instance. It does so via
Gitlab::Redis::CrossSlot::Pipeline
which wraps over a Redis::Client. - Updates
config/redis.yml.example
to include cluster details for cache (!121918 (merged))
The initial version of this MR proposes a pipelined
function that performs pipelined operations in a Redis cluster context. This function is supported in redis-rb
v5 via redis-cluster-client. But for now, since we are blocked on upgrades by Rails 7 (#367857 (comment 1065557960))
It creates a Future
for each command, groups them into n
pipeline requests (while maintaining ordering), and sends it out to each node. Each Gitlab::Patch::RedisPipline::Future
object will map to the redis-rb's Future
object.
This MR also updates call-sites w.r.t the list of cross-slot calls for Gitlab::Redis::Cache
gitlab-com/gl-infra/scalability#2320 (closed).
Parent issue: gitlab-com/gl-infra/scalability#1992 (closed)
Background
- we started out patching mget into pipelined gets -- !104335 (closed)
- then tried to generalise a pipeline wrapper -- !104829 (closed)
- there were under-coverage issues for code-path accessed only when using a Redis Cluster, hence !121918 (merged) was created and merged into this MR.
This MR is a targeted attempt to add cross-slot pipeline functionality, redis-cache migration will be done in a separate MR.
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
- Set up a local Redis Cluster (try https://gitlab.com/gitlab-org/gitlab-development-kit/-/blob/main/data/announcements/0004_redis_cluster_support.yml)
- Update
config/redis.cache.yml
➜ gitlab git:(sc1-crossslot-pipeline) ✗ cat config/redis.yml
---
development:
cache:
cluster:
- redis://gdk.test:6001
- Run pipeline commands in
gdk rails c
Click to expand to show `CrossSlot` usage on Redis Cluster
[2] pry(main)> rc.set('a', 1)
=> "OK"
[3] pry(main)> rc.set('b', 2)
=> "OK"
[4] pry(main)> rc.set('c', 3)
=> "OK"
[5] pry(main)> rc.set('d', 4)
=> "OK"
[6] pry(main)> rc.set('e', 5)
=> "OK"
[7] pry(main)> futures = []
keys = ['a', 'b', 'c', 'd', 'e']
Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
Gitlab::Redis::Cache.with do |redis|
Gitlab::Redis::CrossSlot::Pipeline.new(redis).pipelined do |p|
keys.each do |key|
futures << p.get(key)
futures << p.set('f', 1, ex: 500)
end
end
end
=> ["1", "OK", "2", "OK", "3", "OK", "4", "OK", "5", "OK"]
[8] pry(main)> futures
=> [#<Gitlab::Redis::CrossSlot::Future:0x0000000136076e80 @redis_future=<Redis::Future [:get, "a"]>>,
#<Gitlab::Redis::CrossSlot::Future:0x0000000136076b60 @redis_future=<Redis::Future [:set, "f", "1", "EX", 500]>>,
#<Gitlab::Redis::CrossSlot::Future:0x0000000136076908 @redis_future=<Redis::Future [:get, "b"]>>,
#<Gitlab::Redis::CrossSlot::Future:0x0000000136076660 @redis_future=<Redis::Future [:set, "f", "1", "EX", 500]>>,
#<Gitlab::Redis::CrossSlot::Future:0x0000000136076408 @redis_future=<Redis::Future [:get, "c"]>>,
#<Gitlab::Redis::CrossSlot::Future:0x0000000136076138 @redis_future=<Redis::Future [:set, "f", "1", "EX", 500]>>,
#<Gitlab::Redis::CrossSlot::Future:0x0000000136075eb8 @redis_future=<Redis::Future [:get, "d"]>>,
#<Gitlab::Redis::CrossSlot::Future:0x0000000136075c60 @redis_future=<Redis::Future [:set, "f", "1", "EX", 500]>>,
#<Gitlab::Redis::CrossSlot::Future:0x0000000136075a08 @redis_future=<Redis::Future [:get, "e"]>>,
#<Gitlab::Redis::CrossSlot::Future:0x00000001360757b0 @redis_future=<Redis::Future [:set, "f", "1", "EX", 500]>>]
[9] pry(main)> futures.map(&:value)
=> ["1", "OK", "2", "OK", "3", "OK", "4", "OK", "5", "OK"]
[10] pry(main)> Gitlab::Redis::Cache.with {|r| r.ttl('f')}
=> 458
[11] pry(main)> Gitlab::Redis::Cache.with {|r| r.get('f')}
=> "1"
Click to show what happens CrossSlot is not used with Redis Cluster
[5] pry(main)> Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
[5] pry(main)* Gitlab::Redis::Cache.with do |redis|
[5] pry(main)* redis.pipelined do |p|
[5] pry(main)* keys.each {|key| p.get(key)}
[5] pry(main)* end
[5] pry(main)* end
[5] pry(main)* end
Redis::Cluster::CrossSlotPipeliningError: Cluster client couldn't send pipelining to single node. The commands include cross slot keys. ["a", "b", "c", "d", "e"]
from /Users/sylvesterchin/.asdf/installs/ruby/3.0.5/lib/ruby/gems/3.0.0/gems/redis-4.8.0/lib/redis/cluster.rb:83:in `call_pipeline'
Click to expand that `CrossSlot` works with general non-Redis Cluster connections
[6] pry(main)> Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
[6] pry(main)* Gitlab::Redis::Sessions.with do |redis|
[6] pry(main)* redis.pipelined do |p|
[6] pry(main)* p.set('a', 1)
[6] pry(main)* p.set('b', 2)
[6] pry(main)* end
[6] pry(main)* end
[6] pry(main)* end
=> ["OK", "OK"]
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.