From 778e015beaadf4715692edd7d1611fbb940c4533 Mon Sep 17 00:00:00 2001 From: schin1 <schin@gitlab.com> Date: Thu, 8 Dec 2022 15:09:22 +0800 Subject: [PATCH] Add `redis_allowed_cross_slot_count` into logs Counting number of allowed cross-slot commands is a cheap way to approximate the amount of cross-slot violations. The probability of an allowed cross-slot command is ~1 as long as multiple keys are used. --- doc/administration/logs/index.md | 2 ++ lib/gitlab/instrumentation/redis.rb | 3 ++- lib/gitlab/instrumentation/redis_base.rb | 15 +++++++++++++++ lib/gitlab/instrumentation/redis_payload.rb | 1 + .../gitlab/instrumentation/redis_base_spec.rb | 19 +++++++++++++++++++ .../instrumentation/redis_interceptor_spec.rb | 3 +++ spec/lib/gitlab/instrumentation/redis_spec.rb | 10 ++++++++-- .../lib/gitlab/instrumentation_helper_spec.rb | 9 +++++++-- 8 files changed, 57 insertions(+), 5 deletions(-) diff --git a/doc/administration/logs/index.md b/doc/administration/logs/index.md index efe91e944b725..9893c6935a352 100644 --- a/doc/administration/logs/index.md +++ b/doc/administration/logs/index.md @@ -161,11 +161,13 @@ seconds: - `gitaly_calls`: Total number of calls made to Gitaly - `redis_calls`: Total number of calls made to Redis - `redis_cross_slot_calls`: Total number of cross-slot calls made to Redis +- `redis_allowed_cross_slot_calls`: Total number of allowed cross-slot calls made to Redis - `redis_duration_s`: Total time to retrieve data from Redis - `redis_read_bytes`: Total bytes read from Redis - `redis_write_bytes`: Total bytes written to Redis - `redis_<instance>_calls`: Total number of calls made to a Redis instance - `redis_<instance>_cross_slot_calls`: Total number of cross-slot calls made to a Redis instance +- `redis_<instance>_allowed_cross_slot_calls`: Total number of allowed cross-slot calls made to a Redis instance - `redis_<instance>_duration_s`: Total time to retrieve data from a Redis instance - `redis_<instance>_read_bytes`: Total bytes read from a Redis instance - `redis_<instance>_write_bytes`: Total bytes written to a Redis instance diff --git a/lib/gitlab/instrumentation/redis.rb b/lib/gitlab/instrumentation/redis.rb index da0061a4d9924..a664656c467bf 100644 --- a/lib/gitlab/instrumentation/redis.rb +++ b/lib/gitlab/instrumentation/redis.rb @@ -39,7 +39,8 @@ def detail_store end end - %i[get_request_count get_cross_slot_request_count query_time read_bytes write_bytes].each do |method| + %i[get_request_count get_cross_slot_request_count get_allowed_cross_slot_request_count query_time read_bytes + write_bytes].each do |method| define_method method do STORAGES.sum(&method) end diff --git a/lib/gitlab/instrumentation/redis_base.rb b/lib/gitlab/instrumentation/redis_base.rb index f88181820d8bf..de24132a28e11 100644 --- a/lib/gitlab/instrumentation/redis_base.rb +++ b/lib/gitlab/instrumentation/redis_base.rb @@ -50,6 +50,11 @@ def increment_cross_slot_request_count(amount = 1) ::RequestStore[cross_slots_key] += amount end + def increment_allowed_cross_slot_request_count(amount = 1) + ::RequestStore[allowed_cross_slots_key] ||= 0 + ::RequestStore[allowed_cross_slots_key] += amount + end + def get_request_count ::RequestStore[request_count_key] || 0 end @@ -70,6 +75,10 @@ def get_cross_slot_request_count ::RequestStore[cross_slots_key] || 0 end + def get_allowed_cross_slot_request_count + ::RequestStore[allowed_cross_slots_key] || 0 + end + def query_time query_time = ::RequestStore[call_duration_key] || 0 query_time.round(::Gitlab::InstrumentationHelper::DURATION_PRECISION) @@ -85,6 +94,8 @@ def redis_cluster_validate!(commands) raise RedisClusterValidator::CrossSlotError, "Redis command #{result[:command_name]} arguments hash to different slots. See https://docs.gitlab.com/ee/development/redis.html#multi-key-commands" end + increment_allowed_cross_slot_request_count if result[:allowed] + result[:valid] end @@ -144,6 +155,10 @@ def cross_slots_key strong_memoize(:cross_slots_key) { build_key(:redis_cross_slot_request_count) } end + def allowed_cross_slots_key + strong_memoize(:allowed_cross_slots_key) { build_key(:redis_allowed_cross_slot_request_count) } + end + def build_key(namespace) "#{storage_key}_#{namespace}" end diff --git a/lib/gitlab/instrumentation/redis_payload.rb b/lib/gitlab/instrumentation/redis_payload.rb index 3a9835410af8d..62a4d1a846f79 100644 --- a/lib/gitlab/instrumentation/redis_payload.rb +++ b/lib/gitlab/instrumentation/redis_payload.rb @@ -21,6 +21,7 @@ def to_lazy_payload { "#{key_prefix}_calls": -> { get_request_count }, "#{key_prefix}_cross_slot_calls": -> { get_cross_slot_request_count }, + "#{key_prefix}_allowed_cross_slot_calls": -> { get_allowed_cross_slot_request_count }, "#{key_prefix}_duration_s": -> { query_time }, "#{key_prefix}_read_bytes": -> { read_bytes }, "#{key_prefix}_write_bytes": -> { write_bytes } diff --git a/spec/lib/gitlab/instrumentation/redis_base_spec.rb b/spec/lib/gitlab/instrumentation/redis_base_spec.rb index 9cf65a11bce85..656e6ffba058f 100644 --- a/spec/lib/gitlab/instrumentation/redis_base_spec.rb +++ b/spec/lib/gitlab/instrumentation/redis_base_spec.rb @@ -109,6 +109,25 @@ end end + describe '.increment_allowed_cross_slot_request_count' do + context 'storage key overlapping' do + it 'keys do not overlap across storages' do + 3.times { instrumentation_class_a.increment_allowed_cross_slot_request_count } + 2.times { instrumentation_class_b.increment_allowed_cross_slot_request_count } + + expect(instrumentation_class_a.get_allowed_cross_slot_request_count).to eq(3) + expect(instrumentation_class_b.get_allowed_cross_slot_request_count).to eq(2) + end + + it 'increments by the given amount' do + instrumentation_class_a.increment_allowed_cross_slot_request_count(2) + instrumentation_class_a.increment_allowed_cross_slot_request_count(3) + + expect(instrumentation_class_a.get_allowed_cross_slot_request_count).to eq(5) + end + end + end + describe '.increment_read_bytes' do context 'storage key overlapping' do it 'keys do not overlap across storages' do diff --git a/spec/lib/gitlab/instrumentation/redis_interceptor_spec.rb b/spec/lib/gitlab/instrumentation/redis_interceptor_spec.rb index 120430accf1fb..187a6ff17399d 100644 --- a/spec/lib/gitlab/instrumentation/redis_interceptor_spec.rb +++ b/spec/lib/gitlab/instrumentation/redis_interceptor_spec.rb @@ -83,12 +83,14 @@ it 'counts disallowed cross-slot requests' do expect(instrumentation_class).to receive(:increment_cross_slot_request_count).and_call_original + expect(instrumentation_class).not_to receive(:increment_allowed_cross_slot_request_count).and_call_original Gitlab::Redis::SharedState.with { |redis| redis.call(:mget, 'foo', 'bar') } end it 'does not count allowed cross-slot requests' do expect(instrumentation_class).not_to receive(:increment_cross_slot_request_count).and_call_original + expect(instrumentation_class).to receive(:increment_allowed_cross_slot_request_count).and_call_original Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do Gitlab::Redis::SharedState.with { |redis| redis.call(:mget, 'foo', 'bar') } @@ -97,6 +99,7 @@ it 'skips count for non-cross-slot requests' do expect(instrumentation_class).not_to receive(:increment_cross_slot_request_count).and_call_original + expect(instrumentation_class).not_to receive(:increment_allowed_cross_slot_request_count).and_call_original Gitlab::Redis::SharedState.with { |redis| redis.call(:mget, '{foo}bar', '{foo}baz') } end diff --git a/spec/lib/gitlab/instrumentation/redis_spec.rb b/spec/lib/gitlab/instrumentation/redis_spec.rb index 7b6f31c1a0802..3e02eadba4b7d 100644 --- a/spec/lib/gitlab/instrumentation/redis_spec.rb +++ b/spec/lib/gitlab/instrumentation/redis_spec.rb @@ -24,6 +24,7 @@ def stub_storages(method, value) it_behaves_like 'aggregation of redis storage data', :get_request_count it_behaves_like 'aggregation of redis storage data', :get_cross_slot_request_count + it_behaves_like 'aggregation of redis storage data', :get_allowed_cross_slot_request_count it_behaves_like 'aggregation of redis storage data', :query_time it_behaves_like 'aggregation of redis storage data', :read_bytes it_behaves_like 'aggregation of redis storage data', :write_bytes @@ -39,21 +40,26 @@ def stub_storages(method, value) stub_rails_env('staging') # to avoid raising CrossSlotError Gitlab::Redis::Cache.with { |redis| redis.mset('cache-test', 321, 'cache-test-2', 321) } + Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do + Gitlab::Redis::Cache.with { |redis| redis.mget('cache-test', 'cache-test-2') } + end Gitlab::Redis::SharedState.with { |redis| redis.set('shared-state-test', 123) } end it 'returns payload filtering out zeroed values' do expected_payload = { # Aggregated results - redis_calls: 2, + redis_calls: 3, redis_cross_slot_calls: 1, + redis_allowed_cross_slot_calls: 1, redis_duration_s: be >= 0, redis_read_bytes: be >= 0, redis_write_bytes: be >= 0, # Cache results - redis_cache_calls: 1, + redis_cache_calls: 2, redis_cache_cross_slot_calls: 1, + redis_cache_allowed_cross_slot_calls: 1, redis_cache_duration_s: be >= 0, redis_cache_read_bytes: be >= 0, redis_cache_write_bytes: be >= 0, diff --git a/spec/lib/gitlab/instrumentation_helper_spec.rb b/spec/lib/gitlab/instrumentation_helper_spec.rb index b4738ed663c9f..7d78d25f18e79 100644 --- a/spec/lib/gitlab/instrumentation_helper_spec.rb +++ b/spec/lib/gitlab/instrumentation_helper_spec.rb @@ -41,13 +41,17 @@ it 'adds Redis data and omits Gitaly data' do stub_rails_env('staging') # to avoid raising CrossSlotError Gitlab::Redis::Cache.with { |redis| redis.mset('test-cache', 123, 'test-cache2', 123) } + Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do + Gitlab::Redis::Cache.with { |redis| redis.mget('cache-test', 'cache-test-2') } + end Gitlab::Redis::Queues.with { |redis| redis.set('test-queues', 321) } subject # Aggregated payload - expect(payload[:redis_calls]).to eq(2) + expect(payload[:redis_calls]).to eq(3) expect(payload[:redis_cross_slot_calls]).to eq(1) + expect(payload[:redis_allowed_cross_slot_calls]).to eq(1) expect(payload[:redis_duration_s]).to be >= 0 expect(payload[:redis_read_bytes]).to be >= 0 expect(payload[:redis_write_bytes]).to be >= 0 @@ -59,8 +63,9 @@ expect(payload[:redis_queues_write_bytes]).to be >= 0 # Cache payload - expect(payload[:redis_cache_calls]).to eq(1) + expect(payload[:redis_cache_calls]).to eq(2) expect(payload[:redis_cache_cross_slot_calls]).to eq(1) + expect(payload[:redis_cache_allowed_cross_slot_calls]).to eq(1) expect(payload[:redis_cache_duration_s]).to be >= 0 expect(payload[:redis_cache_read_bytes]).to be >= 0 expect(payload[:redis_cache_write_bytes]).to be >= 0 -- GitLab