150 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			Ruby
		
	
	
	
			
		
		
	
	
			150 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			Ruby
		
	
	
	
| # frozen_string_literal: true
 | |
| 
 | |
| module Gitlab
 | |
|   module Database
 | |
|     module LoadBalancing
 | |
|       # The exceptions raised for connection errors.
 | |
|       CONNECTION_ERRORS = if defined?(PG)
 | |
|                             [
 | |
|                               PG::ConnectionBad,
 | |
|                               PG::ConnectionDoesNotExist,
 | |
|                               PG::ConnectionException,
 | |
|                               PG::ConnectionFailure,
 | |
|                               PG::UnableToSend,
 | |
|                               # During a failover this error may be raised when
 | |
|                               # writing to a primary.
 | |
|                               PG::ReadOnlySqlTransaction
 | |
|                             ].freeze
 | |
|                           else
 | |
|                             [].freeze
 | |
|                           end
 | |
| 
 | |
|       ProxyNotConfiguredError = Class.new(StandardError)
 | |
| 
 | |
|       # The connection proxy to use for load balancing (if enabled).
 | |
|       def self.proxy
 | |
|         unless load_balancing_proxy = ActiveRecord::Base.load_balancing_proxy
 | |
|           Gitlab::ErrorTracking.track_exception(
 | |
|             ProxyNotConfiguredError.new(
 | |
|               "Attempting to access the database load balancing proxy, but it wasn't configured.\n" \
 | |
|               "Did you forget to call '#{self.name}.configure_proxy'?"
 | |
|             ))
 | |
|         end
 | |
| 
 | |
|         load_balancing_proxy
 | |
|       end
 | |
| 
 | |
|       # Returns a Hash containing the load balancing configuration.
 | |
|       def self.configuration
 | |
|         Gitlab::Database.main.config[:load_balancing] || {}
 | |
|       end
 | |
| 
 | |
|       # Returns the maximum replica lag size in bytes.
 | |
|       def self.max_replication_difference
 | |
|         (configuration['max_replication_difference'] || 8.megabytes).to_i
 | |
|       end
 | |
| 
 | |
|       # Returns the maximum lag time for a replica.
 | |
|       def self.max_replication_lag_time
 | |
|         (configuration['max_replication_lag_time'] || 60.0).to_f
 | |
|       end
 | |
| 
 | |
|       # Returns the interval (in seconds) to use for checking the status of a
 | |
|       # replica.
 | |
|       def self.replica_check_interval
 | |
|         (configuration['replica_check_interval'] || 60).to_f
 | |
|       end
 | |
| 
 | |
|       # Returns the additional hosts to use for load balancing.
 | |
|       def self.hosts
 | |
|         configuration['hosts'] || []
 | |
|       end
 | |
| 
 | |
|       def self.service_discovery_enabled?
 | |
|         configuration.dig('discover', 'record').present?
 | |
|       end
 | |
| 
 | |
|       def self.service_discovery_configuration
 | |
|         conf = configuration['discover'] || {}
 | |
| 
 | |
|         {
 | |
|           nameserver: conf['nameserver'] || 'localhost',
 | |
|           port: conf['port'] || 8600,
 | |
|           record: conf['record'],
 | |
|           record_type: conf['record_type'] || 'A',
 | |
|           interval: conf['interval'] || 60,
 | |
|           disconnect_timeout: conf['disconnect_timeout'] || 120,
 | |
|           use_tcp: conf['use_tcp'] || false
 | |
|         }
 | |
|       end
 | |
| 
 | |
|       def self.pool_size
 | |
|         Gitlab::Database.main.pool_size
 | |
|       end
 | |
| 
 | |
|       # Returns true if load balancing is to be enabled.
 | |
|       def self.enable?
 | |
|         return false if Gitlab::Runtime.rake?
 | |
|         return false unless self.configured?
 | |
| 
 | |
|         true
 | |
|       end
 | |
| 
 | |
|       # Returns true if load balancing has been configured. Since
 | |
|       # Sidekiq does not currently use load balancing, we
 | |
|       # may want Web application servers to detect replication lag by
 | |
|       # posting the write location of the database if load balancing is
 | |
|       # configured.
 | |
|       def self.configured?
 | |
|         hosts.any? || service_discovery_enabled?
 | |
|       end
 | |
| 
 | |
|       def self.start_service_discovery
 | |
|         return unless service_discovery_enabled?
 | |
| 
 | |
|         ServiceDiscovery.new(service_discovery_configuration).start
 | |
|       end
 | |
| 
 | |
|       # Configures proxying of requests.
 | |
|       def self.configure_proxy(proxy = ConnectionProxy.new(hosts))
 | |
|         ActiveRecord::Base.load_balancing_proxy = proxy
 | |
|       end
 | |
| 
 | |
|       def self.active_record_models
 | |
|         ActiveRecord::Base.descendants
 | |
|       end
 | |
| 
 | |
|       DB_ROLES = [
 | |
|         ROLE_PRIMARY = :primary,
 | |
|         ROLE_REPLICA = :replica,
 | |
|         ROLE_UNKNOWN = :unknown
 | |
|       ].freeze
 | |
| 
 | |
|       # Returns the role (primary/replica) of the database the connection is
 | |
|       # connecting to. At the moment, the connection can only be retrieved by
 | |
|       # Gitlab::Database::LoadBalancer#read or #read_write or from the
 | |
|       # ActiveRecord directly. Therefore, if the load balancer doesn't
 | |
|       # recognize the connection, this method returns the primary role
 | |
|       # directly. In future, we may need to check for other sources.
 | |
|       def self.db_role_for_connection(connection)
 | |
|         return ROLE_UNKNOWN unless connection
 | |
| 
 | |
|         # The connection proxy does not have a role assigned
 | |
|         # as this is dependent on a execution context
 | |
|         return ROLE_UNKNOWN if connection.is_a?(ConnectionProxy)
 | |
| 
 | |
|         # During application init we might receive `NullPool`
 | |
|         return ROLE_UNKNOWN unless connection.respond_to?(:pool) &&
 | |
|           connection.pool.respond_to?(:db_config) &&
 | |
|           connection.pool.db_config.respond_to?(:name)
 | |
| 
 | |
|         if connection.pool.db_config.name.ends_with?(LoadBalancer::REPLICA_SUFFIX)
 | |
|           ROLE_REPLICA
 | |
|         else
 | |
|           ROLE_PRIMARY
 | |
|         end
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| end
 |