debian-mirror-gitlab/lib/gitlab/database/load_balancing/sticking.rb

150 lines
5.1 KiB
Ruby
Raw Normal View History

2021-09-04 01:27:46 +05:30
# frozen_string_literal: true
module Gitlab
module Database
module LoadBalancing
# Module used for handling sticking connections to a primary, if
# necessary.
2021-11-18 22:05:49 +05:30
class Sticking
2021-09-04 01:27:46 +05:30
# The number of seconds after which a session should stop reading from
# the primary.
EXPIRATION = 30
2021-11-18 22:05:49 +05:30
def initialize(load_balancer)
@load_balancer = load_balancer
end
2021-09-04 01:27:46 +05:30
2021-11-18 22:05:49 +05:30
# Unsticks or continues sticking the current request.
#
# This method also updates the Rack environment so #call can later
# determine if we still need to stick or not.
#
# env - The Rack environment.
# namespace - The namespace to use for sticking.
# id - The identifier to use for sticking.
# model - The ActiveRecord model to scope sticking to.
def stick_or_unstick_request(env, namespace, id)
unstick_or_continue_sticking(namespace, id)
2021-12-11 22:18:48 +05:30
env[::Gitlab::Database::LoadBalancing::RackMiddleware::STICK_OBJECT] ||= Set.new
env[::Gitlab::Database::LoadBalancing::RackMiddleware::STICK_OBJECT] << [self, namespace, id]
2021-11-18 22:05:49 +05:30
end
# Sticks to the primary if a write was performed.
def stick_if_necessary(namespace, id)
stick(namespace, id) if ::Gitlab::Database::LoadBalancing::Session.current.performed_write?
2021-09-04 01:27:46 +05:30
end
2021-11-18 22:05:49 +05:30
def all_caught_up?(namespace, id)
2021-09-04 01:27:46 +05:30
location = last_write_location_for(namespace, id)
return true unless location
2021-11-18 22:05:49 +05:30
@load_balancer.select_up_to_date_host(location).tap do |found|
ActiveSupport::Notifications.instrument(
'caught_up_replica_pick.load_balancing',
{ result: found }
)
2021-09-30 23:02:18 +05:30
unstick(namespace, id) if found
2021-09-04 01:27:46 +05:30
end
end
# Selects hosts that have caught up with the primary. This ensures
# atomic selection of the host to prevent the host list changing
# in another thread.
#
# Returns true if one host was selected.
2021-11-18 22:05:49 +05:30
def select_caught_up_replicas(namespace, id)
2021-09-04 01:27:46 +05:30
location = last_write_location_for(namespace, id)
# Unlike all_caught_up?, we return false if no write location exists.
# We want to be sure we talk to a replica that has caught up for a specific
# write location. If no such location exists, err on the side of caution.
return false unless location
2021-11-18 22:05:49 +05:30
@load_balancer.select_up_to_date_host(location).tap do |selected|
2021-10-27 15:23:28 +05:30
unstick(namespace, id) if selected
2021-09-04 01:27:46 +05:30
end
end
# Sticks to the primary if necessary, otherwise unsticks an object (if
# it was previously stuck to the primary).
2021-11-18 22:05:49 +05:30
def unstick_or_continue_sticking(namespace, id)
return if all_caught_up?(namespace, id)
::Gitlab::Database::LoadBalancing::Session.current.use_primary!
2021-09-04 01:27:46 +05:30
end
# Select a replica that has caught up with the primary. If one has not been
# found, stick to the primary.
2021-11-18 22:05:49 +05:30
def select_valid_host(namespace, id)
replica_selected =
select_caught_up_replicas(namespace, id)
2021-09-04 01:27:46 +05:30
2021-11-18 22:05:49 +05:30
::Gitlab::Database::LoadBalancing::Session.current.use_primary! unless replica_selected
2021-09-04 01:27:46 +05:30
end
# Starts sticking to the primary for the given namespace and id, using
# the latest WAL pointer from the primary.
2021-11-18 22:05:49 +05:30
def stick(namespace, id)
2021-09-04 01:27:46 +05:30
mark_primary_write_location(namespace, id)
2021-11-18 22:05:49 +05:30
::Gitlab::Database::LoadBalancing::Session.current.use_primary!
2021-09-04 01:27:46 +05:30
end
2021-11-18 22:05:49 +05:30
def bulk_stick(namespace, ids)
2021-09-04 01:27:46 +05:30
with_primary_write_location do |location|
ids.each do |id|
set_write_location_for(namespace, id, location)
end
end
2021-11-18 22:05:49 +05:30
::Gitlab::Database::LoadBalancing::Session.current.use_primary!
2021-09-04 01:27:46 +05:30
end
2021-11-18 22:05:49 +05:30
def with_primary_write_location
# When only using the primary, there's no point in getting write
# locations, as the primary is always in sync with itself.
return if @load_balancer.primary_only?
2021-09-04 01:27:46 +05:30
2021-11-18 22:05:49 +05:30
location = @load_balancer.primary_write_location
2021-09-04 01:27:46 +05:30
return if location.blank?
yield(location)
end
2021-11-18 22:05:49 +05:30
def mark_primary_write_location(namespace, id)
2021-09-04 01:27:46 +05:30
with_primary_write_location do |location|
set_write_location_for(namespace, id, location)
end
end
2021-11-18 22:05:49 +05:30
def unstick(namespace, id)
2021-09-04 01:27:46 +05:30
Gitlab::Redis::SharedState.with do |redis|
redis.del(redis_key_for(namespace, id))
end
end
2021-11-18 22:05:49 +05:30
def set_write_location_for(namespace, id, location)
2021-09-04 01:27:46 +05:30
Gitlab::Redis::SharedState.with do |redis|
redis.set(redis_key_for(namespace, id), location, ex: EXPIRATION)
end
end
2021-11-18 22:05:49 +05:30
def last_write_location_for(namespace, id)
2021-09-04 01:27:46 +05:30
Gitlab::Redis::SharedState.with do |redis|
2022-01-26 12:08:38 +05:30
redis.get(redis_key_for(namespace, id))
2021-09-04 01:27:46 +05:30
end
end
2021-11-18 22:05:49 +05:30
def redis_key_for(namespace, id)
name = @load_balancer.name
"database-load-balancing/write-location/#{name}/#{namespace}/#{id}"
2021-09-04 01:27:46 +05:30
end
end
end
end
end