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/redis/multi_store.rb')
-rw-r--r--lib/gitlab/redis/multi_store.rb62
1 files changed, 45 insertions, 17 deletions
diff --git a/lib/gitlab/redis/multi_store.rb b/lib/gitlab/redis/multi_store.rb
index d36ef6b99ee..7f4d611a490 100644
--- a/lib/gitlab/redis/multi_store.rb
+++ b/lib/gitlab/redis/multi_store.rb
@@ -6,15 +6,15 @@ module Gitlab
include Gitlab::Utils::StrongMemoize
class PipelinedDiffError < StandardError
- def initialize(result_primary, result_secondary)
- @result_primary = result_primary
- @result_secondary = result_secondary
+ def initialize(non_default_store_result, default_store_result)
+ @non_default_store_result = non_default_store_result
+ @default_store_result = default_store_result
end
def message
"Pipelined command executed on both stores successfully but results differ between them. " \
- "Result from the primary: #{@result_primary.inspect}. " \
- "Result from the secondary: #{@result_secondary.inspect}."
+ "Result from the non-default store: #{@non_default_store_result.inspect}. " \
+ "Result from the default store: #{@default_store_result.inspect}."
end
end
@@ -24,11 +24,17 @@ module Gitlab
end
end
+ class NestedReadonlyPipelineError < StandardError
+ def message
+ 'Nested use of with_readonly_pipeline is detected.'
+ end
+ end
+
attr_reader :primary_store, :secondary_store, :instance_name
FAILED_TO_READ_ERROR_MESSAGE = 'Failed to read from the redis default_store.'
- FAILED_TO_WRITE_ERROR_MESSAGE = 'Failed to write to the redis primary_store.'
- FAILED_TO_RUN_PIPELINE = 'Failed to execute pipeline on the redis primary_store.'
+ FAILED_TO_WRITE_ERROR_MESSAGE = 'Failed to write to the redis non_default_store.'
+ FAILED_TO_RUN_PIPELINE = 'Failed to execute pipeline on the redis non_default_store.'
SKIP_LOG_METHOD_MISSING_FOR_COMMANDS = %i[info].freeze
@@ -100,6 +106,25 @@ module Gitlab
validate_stores!
end
+ # Pipelines are sent to both instances by default since
+ # they could execute both read and write commands.
+ #
+ # But for pipelines that only consists of read commands, this method
+ # can be used to scope the pipeline and send it only to the default store.
+ def with_readonly_pipeline
+ raise NestedReadonlyPipelineError if readonly_pipeline?
+
+ Thread.current[:readonly_pipeline] = true
+
+ yield
+ ensure
+ Thread.current[:readonly_pipeline] = false
+ end
+
+ def readonly_pipeline?
+ Thread.current[:readonly_pipeline].present?
+ end
+
# rubocop:disable GitlabSecurity/PublicSend
READ_COMMANDS.each do |name|
define_method(name) do |*args, **kwargs, &block|
@@ -123,7 +148,7 @@ module Gitlab
PIPELINED_COMMANDS.each do |name|
define_method(name) do |*args, **kwargs, &block|
- if use_primary_and_secondary_stores?
+ if use_primary_and_secondary_stores? && !readonly_pipeline?
pipelined_both(name, *args, **kwargs, &block)
else
send_command(default_store, name, *args, **kwargs, &block)
@@ -192,7 +217,7 @@ module Gitlab
use_primary_store_as_default? ? primary_store : secondary_store
end
- def fallback_store
+ def non_default_store
use_primary_store_as_default? ? secondary_store : primary_store
end
@@ -252,36 +277,39 @@ module Gitlab
end
def write_both(command_name, *args, **kwargs, &block)
+ result = send_command(default_store, command_name, *args, **kwargs, &block)
+
+ # write to the non-default store only if write on default store is successful
begin
- send_command(primary_store, command_name, *args, **kwargs, &block)
+ send_command(non_default_store, command_name, *args, **kwargs, &block)
rescue StandardError => e
log_error(e, command_name,
multi_store_error_message: FAILED_TO_WRITE_ERROR_MESSAGE)
end
- send_command(secondary_store, command_name, *args, **kwargs, &block)
+ result
end
# Run the entire pipeline on both stores. We assume that `&block` is idempotent.
def pipelined_both(command_name, *args, **kwargs, &block)
+ result_default = send_command(default_store, command_name, *args, **kwargs, &block)
+
begin
- result_primary = send_command(primary_store, command_name, *args, **kwargs, &block)
+ result_non_default = send_command(non_default_store, command_name, *args, **kwargs, &block)
rescue StandardError => e
log_error(e, command_name, multi_store_error_message: FAILED_TO_RUN_PIPELINE)
end
- result_secondary = send_command(secondary_store, command_name, *args, **kwargs, &block)
-
# Pipelined commands return an array with all results. If they differ, log an error
- if result_primary && result_primary != result_secondary
- error = PipelinedDiffError.new(result_primary, result_secondary)
+ if result_non_default && result_non_default != result_default
+ error = PipelinedDiffError.new(result_non_default, result_default)
error.set_backtrace(Thread.current.backtrace[1..]) # Manually set backtrace, since the error is not `raise`d
log_error(error, command_name)
increment_pipelined_command_error_count(command_name)
end
- result_secondary
+ result_default
end
def same_redis_store?