Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'lib/gitlab/instrumentation')
-rw-r--r--lib/gitlab/instrumentation/redis_base.rb4
-rw-r--r--lib/gitlab/instrumentation/redis_cluster_validator.rb223
-rw-r--r--lib/gitlab/instrumentation/redis_interceptor.rb11
3 files changed, 186 insertions, 52 deletions
diff --git a/lib/gitlab/instrumentation/redis_base.rb b/lib/gitlab/instrumentation/redis_base.rb
index 0bd10597f24..268c6cdf459 100644
--- a/lib/gitlab/instrumentation/redis_base.rb
+++ b/lib/gitlab/instrumentation/redis_base.rb
@@ -66,8 +66,8 @@ module Gitlab
query_time.round(::Gitlab::InstrumentationHelper::DURATION_PRECISION)
end
- def redis_cluster_validate!(command)
- ::Gitlab::Instrumentation::RedisClusterValidator.validate!(command) if @redis_cluster_validation
+ def redis_cluster_validate!(commands)
+ ::Gitlab::Instrumentation::RedisClusterValidator.validate!(commands) if @redis_cluster_validation
end
def enable_redis_cluster_validation
diff --git a/lib/gitlab/instrumentation/redis_cluster_validator.rb b/lib/gitlab/instrumentation/redis_cluster_validator.rb
index 005751fb0db..36d3e088956 100644
--- a/lib/gitlab/instrumentation/redis_cluster_validator.rb
+++ b/lib/gitlab/instrumentation/redis_cluster_validator.rb
@@ -10,57 +10,189 @@ module Gitlab
#
# Gitlab::Redis::Cache
# .with { |redis| redis.call('COMMAND') }
- # .select { |command| command[3] != command[4] }
- # .map { |command| [command[0].upcase, { first: command[3], last: command[4], step: command[5] }] }
+ # .select { |cmd| cmd[3] != 0 }
+ # .map { |cmd| [
+ # cmd[0].upcase,
+ # { first: cmd[3], last: cmd[4], step: cmd[5], single_key: cmd[3] == cmd[4] }
+ # ]
+ # }
# .sort_by(&:first)
# .to_h
- #
- MULTI_KEY_COMMANDS = {
- "BITOP" => { first: 2, last: -1, step: 1 },
- "BLPOP" => { first: 1, last: -2, step: 1 },
- "BRPOP" => { first: 1, last: -2, step: 1 },
- "BRPOPLPUSH" => { first: 1, last: 2, step: 1 },
- "BZPOPMAX" => { first: 1, last: -2, step: 1 },
- "BZPOPMIN" => { first: 1, last: -2, step: 1 },
- "DEL" => { first: 1, last: -1, step: 1 },
- "EXISTS" => { first: 1, last: -1, step: 1 },
- "MGET" => { first: 1, last: -1, step: 1 },
- "MSET" => { first: 1, last: -1, step: 2 },
- "MSETNX" => { first: 1, last: -1, step: 2 },
- "PFCOUNT" => { first: 1, last: -1, step: 1 },
- "PFMERGE" => { first: 1, last: -1, step: 1 },
- "RENAME" => { first: 1, last: 2, step: 1 },
- "RENAMENX" => { first: 1, last: 2, step: 1 },
- "RPOPLPUSH" => { first: 1, last: 2, step: 1 },
- "SDIFF" => { first: 1, last: -1, step: 1 },
- "SDIFFSTORE" => { first: 1, last: -1, step: 1 },
- "SINTER" => { first: 1, last: -1, step: 1 },
- "SINTERSTORE" => { first: 1, last: -1, step: 1 },
- "SMOVE" => { first: 1, last: 2, step: 1 },
- "SUNION" => { first: 1, last: -1, step: 1 },
- "SUNIONSTORE" => { first: 1, last: -1, step: 1 },
- "UNLINK" => { first: 1, last: -1, step: 1 },
- "WATCH" => { first: 1, last: -1, step: 1 }
+ REDIS_COMMANDS = {
+ "APPEND" => { first: 1, last: 1, step: 1, single_key: true },
+ "BITCOUNT" => { first: 1, last: 1, step: 1, single_key: true },
+ "BITFIELD" => { first: 1, last: 1, step: 1, single_key: true },
+ "BITFIELD_RO" => { first: 1, last: 1, step: 1, single_key: true },
+ "BITOP" => { first: 2, last: -1, step: 1, single_key: false },
+ "BITPOS" => { first: 1, last: 1, step: 1, single_key: true },
+ "BLMOVE" => { first: 1, last: 2, step: 1, single_key: false },
+ "BLPOP" => { first: 1, last: -2, step: 1, single_key: false },
+ "BRPOP" => { first: 1, last: -2, step: 1, single_key: false },
+ "BRPOPLPUSH" => { first: 1, last: 2, step: 1, single_key: false },
+ "BZPOPMAX" => { first: 1, last: -2, step: 1, single_key: false },
+ "BZPOPMIN" => { first: 1, last: -2, step: 1, single_key: false },
+ "COPY" => { first: 1, last: 2, step: 1, single_key: false },
+ "DECR" => { first: 1, last: 1, step: 1, single_key: true },
+ "DECRBY" => { first: 1, last: 1, step: 1, single_key: true },
+ "DEL" => { first: 1, last: -1, step: 1, single_key: false },
+ "DUMP" => { first: 1, last: 1, step: 1, single_key: true },
+ "EXISTS" => { first: 1, last: -1, step: 1, single_key: false },
+ "EXPIRE" => { first: 1, last: 1, step: 1, single_key: true },
+ "EXPIREAT" => { first: 1, last: 1, step: 1, single_key: true },
+ "GEOADD" => { first: 1, last: 1, step: 1, single_key: true },
+ "GEODIST" => { first: 1, last: 1, step: 1, single_key: true },
+ "GEOHASH" => { first: 1, last: 1, step: 1, single_key: true },
+ "GEOPOS" => { first: 1, last: 1, step: 1, single_key: true },
+ "GEORADIUS" => { first: 1, last: 1, step: 1, single_key: true },
+ "GEORADIUSBYMEMBER" => { first: 1, last: 1, step: 1, single_key: true },
+ "GEORADIUSBYMEMBER_RO" => { first: 1, last: 1, step: 1, single_key: true },
+ "GEORADIUS_RO" => { first: 1, last: 1, step: 1, single_key: true },
+ "GEOSEARCH" => { first: 1, last: 1, step: 1, single_key: true },
+ "GEOSEARCHSTORE" => { first: 1, last: 2, step: 1, single_key: false },
+ "GET" => { first: 1, last: 1, step: 1, single_key: true },
+ "GETBIT" => { first: 1, last: 1, step: 1, single_key: true },
+ "GETDEL" => { first: 1, last: 1, step: 1, single_key: true },
+ "GETEX" => { first: 1, last: 1, step: 1, single_key: true },
+ "GETRANGE" => { first: 1, last: 1, step: 1, single_key: true },
+ "GETSET" => { first: 1, last: 1, step: 1, single_key: true },
+ "HDEL" => { first: 1, last: 1, step: 1, single_key: true },
+ "HEXISTS" => { first: 1, last: 1, step: 1, single_key: true },
+ "HGET" => { first: 1, last: 1, step: 1, single_key: true },
+ "HGETALL" => { first: 1, last: 1, step: 1, single_key: true },
+ "HINCRBY" => { first: 1, last: 1, step: 1, single_key: true },
+ "HINCRBYFLOAT" => { first: 1, last: 1, step: 1, single_key: true },
+ "HKEYS" => { first: 1, last: 1, step: 1, single_key: true },
+ "HLEN" => { first: 1, last: 1, step: 1, single_key: true },
+ "HMGET" => { first: 1, last: 1, step: 1, single_key: true },
+ "HMSET" => { first: 1, last: 1, step: 1, single_key: true },
+ "HRANDFIELD" => { first: 1, last: 1, step: 1, single_key: true },
+ "HSCAN" => { first: 1, last: 1, step: 1, single_key: true },
+ "HSET" => { first: 1, last: 1, step: 1, single_key: true },
+ "HSETNX" => { first: 1, last: 1, step: 1, single_key: true },
+ "HSTRLEN" => { first: 1, last: 1, step: 1, single_key: true },
+ "HVALS" => { first: 1, last: 1, step: 1, single_key: true },
+ "INCR" => { first: 1, last: 1, step: 1, single_key: true },
+ "INCRBY" => { first: 1, last: 1, step: 1, single_key: true },
+ "INCRBYFLOAT" => { first: 1, last: 1, step: 1, single_key: true },
+ "LINDEX" => { first: 1, last: 1, step: 1, single_key: true },
+ "LINSERT" => { first: 1, last: 1, step: 1, single_key: true },
+ "LLEN" => { first: 1, last: 1, step: 1, single_key: true },
+ "LMOVE" => { first: 1, last: 2, step: 1, single_key: false },
+ "LPOP" => { first: 1, last: 1, step: 1, single_key: true },
+ "LPOS" => { first: 1, last: 1, step: 1, single_key: true },
+ "LPUSH" => { first: 1, last: 1, step: 1, single_key: true },
+ "LPUSHX" => { first: 1, last: 1, step: 1, single_key: true },
+ "LRANGE" => { first: 1, last: 1, step: 1, single_key: true },
+ "LREM" => { first: 1, last: 1, step: 1, single_key: true },
+ "LSET" => { first: 1, last: 1, step: 1, single_key: true },
+ "LTRIM" => { first: 1, last: 1, step: 1, single_key: true },
+ "MGET" => { first: 1, last: -1, step: 1, single_key: false },
+ "MIGRATE" => { first: 3, last: 3, step: 1, single_key: true },
+ "MOVE" => { first: 1, last: 1, step: 1, single_key: true },
+ "MSET" => { first: 1, last: -1, step: 2, single_key: false },
+ "MSETNX" => { first: 1, last: -1, step: 2, single_key: false },
+ "OBJECT" => { first: 2, last: 2, step: 1, single_key: true },
+ "PERSIST" => { first: 1, last: 1, step: 1, single_key: true },
+ "PEXPIRE" => { first: 1, last: 1, step: 1, single_key: true },
+ "PEXPIREAT" => { first: 1, last: 1, step: 1, single_key: true },
+ "PFADD" => { first: 1, last: 1, step: 1, single_key: true },
+ "PFCOUNT" => { first: 1, last: -1, step: 1, single_key: false },
+ "PFDEBUG" => { first: 2, last: 2, step: 1, single_key: true },
+ "PFMERGE" => { first: 1, last: -1, step: 1, single_key: false },
+ "PSETEX" => { first: 1, last: 1, step: 1, single_key: true },
+ "PTTL" => { first: 1, last: 1, step: 1, single_key: true },
+ "RENAME" => { first: 1, last: 2, step: 1, single_key: false },
+ "RENAMENX" => { first: 1, last: 2, step: 1, single_key: false },
+ "RESTORE" => { first: 1, last: 1, step: 1, single_key: true },
+ "RESTORE-ASKING" => { first: 1, last: 1, step: 1, single_key: true },
+ "RPOP" => { first: 1, last: 1, step: 1, single_key: true },
+ "RPOPLPUSH" => { first: 1, last: 2, step: 1, single_key: false },
+ "RPUSH" => { first: 1, last: 1, step: 1, single_key: true },
+ "RPUSHX" => { first: 1, last: 1, step: 1, single_key: true },
+ "SADD" => { first: 1, last: 1, step: 1, single_key: true },
+ "SCARD" => { first: 1, last: 1, step: 1, single_key: true },
+ "SDIFF" => { first: 1, last: -1, step: 1, single_key: false },
+ "SDIFFSTORE" => { first: 1, last: -1, step: 1, single_key: false },
+ "SET" => { first: 1, last: 1, step: 1, single_key: true },
+ "SETBIT" => { first: 1, last: 1, step: 1, single_key: true },
+ "SETEX" => { first: 1, last: 1, step: 1, single_key: true },
+ "SETNX" => { first: 1, last: 1, step: 1, single_key: true },
+ "SETRANGE" => { first: 1, last: 1, step: 1, single_key: true },
+ "SINTER" => { first: 1, last: -1, step: 1, single_key: false },
+ "SINTERSTORE" => { first: 1, last: -1, step: 1, single_key: false },
+ "SISMEMBER" => { first: 1, last: 1, step: 1, single_key: true },
+ "SMEMBERS" => { first: 1, last: 1, step: 1, single_key: true },
+ "SMISMEMBER" => { first: 1, last: 1, step: 1, single_key: true },
+ "SMOVE" => { first: 1, last: 2, step: 1, single_key: false },
+ "SORT" => { first: 1, last: 1, step: 1, single_key: true },
+ "SPOP" => { first: 1, last: 1, step: 1, single_key: true },
+ "SRANDMEMBER" => { first: 1, last: 1, step: 1, single_key: true },
+ "SREM" => { first: 1, last: 1, step: 1, single_key: true },
+ "SSCAN" => { first: 1, last: 1, step: 1, single_key: true },
+ "STRLEN" => { first: 1, last: 1, step: 1, single_key: true },
+ "SUBSTR" => { first: 1, last: 1, step: 1, single_key: true },
+ "SUNION" => { first: 1, last: -1, step: 1, single_key: false },
+ "SUNIONSTORE" => { first: 1, last: -1, step: 1, single_key: false },
+ "TOUCH" => { first: 1, last: -1, step: 1, single_key: false },
+ "TTL" => { first: 1, last: 1, step: 1, single_key: true },
+ "TYPE" => { first: 1, last: 1, step: 1, single_key: true },
+ "UNLINK" => { first: 1, last: -1, step: 1, single_key: false },
+ "WATCH" => { first: 1, last: -1, step: 1, single_key: false },
+ "XACK" => { first: 1, last: 1, step: 1, single_key: true },
+ "XADD" => { first: 1, last: 1, step: 1, single_key: true },
+ "XAUTOCLAIM" => { first: 1, last: 1, step: 1, single_key: true },
+ "XCLAIM" => { first: 1, last: 1, step: 1, single_key: true },
+ "XDEL" => { first: 1, last: 1, step: 1, single_key: true },
+ "XGROUP" => { first: 2, last: 2, step: 1, single_key: true },
+ "XINFO" => { first: 2, last: 2, step: 1, single_key: true },
+ "XLEN" => { first: 1, last: 1, step: 1, single_key: true },
+ "XPENDING" => { first: 1, last: 1, step: 1, single_key: true },
+ "XRANGE" => { first: 1, last: 1, step: 1, single_key: true },
+ "XREVRANGE" => { first: 1, last: 1, step: 1, single_key: true },
+ "XSETID" => { first: 1, last: 1, step: 1, single_key: true },
+ "XTRIM" => { first: 1, last: 1, step: 1, single_key: true },
+ "ZADD" => { first: 1, last: 1, step: 1, single_key: true },
+ "ZCARD" => { first: 1, last: 1, step: 1, single_key: true },
+ "ZCOUNT" => { first: 1, last: 1, step: 1, single_key: true },
+ "ZDIFFSTORE" => { first: 1, last: 1, step: 1, single_key: true },
+ "ZINCRBY" => { first: 1, last: 1, step: 1, single_key: true },
+ "ZINTERSTORE" => { first: 1, last: 1, step: 1, single_key: true },
+ "ZLEXCOUNT" => { first: 1, last: 1, step: 1, single_key: true },
+ "ZMSCORE" => { first: 1, last: 1, step: 1, single_key: true },
+ "ZPOPMAX" => { first: 1, last: 1, step: 1, single_key: true },
+ "ZPOPMIN" => { first: 1, last: 1, step: 1, single_key: true },
+ "ZRANDMEMBER" => { first: 1, last: 1, step: 1, single_key: true },
+ "ZRANGE" => { first: 1, last: 1, step: 1, single_key: true },
+ "ZRANGEBYLEX" => { first: 1, last: 1, step: 1, single_key: true },
+ "ZRANGEBYSCORE" => { first: 1, last: 1, step: 1, single_key: true },
+ "ZRANGESTORE" => { first: 1, last: 2, step: 1, single_key: false },
+ "ZRANK" => { first: 1, last: 1, step: 1, single_key: true },
+ "ZREM" => { first: 1, last: 1, step: 1, single_key: true },
+ "ZREMRANGEBYLEX" => { first: 1, last: 1, step: 1, single_key: true },
+ "ZREMRANGEBYRANK" => { first: 1, last: 1, step: 1, single_key: true },
+ "ZREMRANGEBYSCORE" => { first: 1, last: 1, step: 1, single_key: true },
+ "ZREVRANGE" => { first: 1, last: 1, step: 1, single_key: true },
+ "ZREVRANGEBYLEX" => { first: 1, last: 1, step: 1, single_key: true },
+ "ZREVRANGEBYSCORE" => { first: 1, last: 1, step: 1, single_key: true },
+ "ZREVRANK" => { first: 1, last: 1, step: 1, single_key: true },
+ "ZSCAN" => { first: 1, last: 1, step: 1, single_key: true },
+ "ZSCORE" => { first: 1, last: 1, step: 1, single_key: true },
+ "ZUNIONSTORE" => { first: 1, last: 1, step: 1, single_key: true }
}.freeze
CrossSlotError = Class.new(StandardError)
class << self
- def validate!(command)
+ def validate!(commands)
return unless Rails.env.development? || Rails.env.test?
return if allow_cross_slot_commands?
+ return if commands.empty?
- command_name = command.first.to_s.upcase
- argument_positions = MULTI_KEY_COMMANDS[command_name]
-
- return unless argument_positions
-
- arguments = command.flatten[argument_positions[:first]..argument_positions[:last]]
-
- key_slots = arguments.each_slice(argument_positions[:step]).map do |args|
- key_slot(args.first)
- end
+ # early exit for single-command (non-pipelined) if it is a single-key-command
+ command_name = commands.size > 1 ? "PIPELINE/MULTI" : commands.first.first.to_s.upcase
+ return if commands.size == 1 && REDIS_COMMANDS.dig(command_name, :single_key)
+ key_slots = commands.map { |command| key_slots(command) }.flatten
if key_slots.uniq.many? # rubocop: disable CodeReuse/ActiveRecord
raise CrossSlotError, "Redis command #{command_name} arguments hash to different slots. See https://docs.gitlab.com/ee/development/redis.html#multi-key-commands"
end
@@ -78,6 +210,17 @@ module Gitlab
private
+ def key_slots(command)
+ argument_positions = REDIS_COMMANDS[command.first.to_s.upcase]
+
+ return [] unless argument_positions
+
+ arguments = command.flatten[argument_positions[:first]..argument_positions[:last]]
+ arguments.each_slice(argument_positions[:step]).map do |args|
+ key_slot(args.first)
+ end
+ end
+
def allow_cross_slot_commands?
Thread.current[:allow_cross_slot_commands].to_i > 0
end
diff --git a/lib/gitlab/instrumentation/redis_interceptor.rb b/lib/gitlab/instrumentation/redis_interceptor.rb
index 7e2acb91b94..f19279df2fe 100644
--- a/lib/gitlab/instrumentation/redis_interceptor.rb
+++ b/lib/gitlab/instrumentation/redis_interceptor.rb
@@ -5,14 +5,6 @@ module Gitlab
module RedisInterceptor
APDEX_EXCLUDE = %w[brpop blpop brpoplpush bzpopmin bzpopmax xread xreadgroup].freeze
- class MysteryRedisDurationError < StandardError
- attr_reader :backtrace
-
- def initialize(backtrace)
- @backtrace = backtrace
- end
- end
-
def call(command)
instrument_call([command]) do
super
@@ -41,8 +33,7 @@ module Gitlab
def instrument_call(commands)
start = Gitlab::Metrics::System.monotonic_time # must come first so that 'start' is always defined
instrumentation_class.instance_count_request(commands.size)
-
- commands.each { |c| instrumentation_class.redis_cluster_validate!(c) }
+ instrumentation_class.redis_cluster_validate!(commands)
yield
rescue ::Redis::BaseError => ex