summaryrefslogtreecommitdiff
path: root/lib/gitlab/database/load_balancing/configuration.rb
blob: e769cb5c35c0436713a058426cbd2945e1178781 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# frozen_string_literal: true

module Gitlab
  module Database
    module LoadBalancing
      # Configuration settings for a single LoadBalancer instance.
      class Configuration
        attr_accessor :hosts, :max_replication_difference,
                      :max_replication_lag_time, :replica_check_interval,
                      :service_discovery

        # Creates a configuration object for the given ActiveRecord model.
        def self.for_model(model)
          cfg = model.connection_db_config.configuration_hash.deep_symbolize_keys
          lb_cfg = cfg[:load_balancing] || {}
          config = new(model)

          if (diff = lb_cfg[:max_replication_difference])
            config.max_replication_difference = diff
          end

          if (lag = lb_cfg[:max_replication_lag_time])
            config.max_replication_lag_time = lag.to_f
          end

          if (interval = lb_cfg[:replica_check_interval])
            config.replica_check_interval = interval.to_f
          end

          if (hosts = lb_cfg[:hosts])
            config.hosts = hosts
          end

          discover = lb_cfg[:discover] || {}

          # We iterate over the known/default keys so we don't end up with
          # random keys in our configuration hash.
          config.service_discovery.each do |key, _|
            if (value = discover[key])
              config.service_discovery[key] = value
            end
          end

          config.reuse_primary_connection!

          config
        end

        def initialize(model, hosts = [])
          @max_replication_difference = 8.megabytes
          @max_replication_lag_time = 60.0
          @replica_check_interval = 60.0
          @model = model
          @hosts = hosts
          @service_discovery = {
            nameserver: 'localhost',
            port: 8600,
            record: nil,
            record_type: 'A',
            interval: 60,
            disconnect_timeout: 120,
            use_tcp: false
          }

          # Temporary model for GITLAB_LOAD_BALANCING_REUSE_PRIMARY_
          # To be removed with FF
          @primary_model = nil
        end

        def db_config_name
          @model.connection_db_config.name.to_sym
        end

        # With connection re-use the primary connection can be overwritten
        # to be used from different model
        def primary_connection_specification_name
          (@primary_model || @model).connection_specification_name
        end

        def primary_db_config
          (@primary_model || @model).connection_db_config
        end

        def replica_db_config
          @model.connection_db_config
        end

        def pool_size
          # The pool size may change when booting up GitLab, as GitLab enforces
          # a certain number of threads. If a Configuration is memoized, this
          # can lead to incorrect pool sizes.
          #
          # To support this scenario, we always attempt to read the pool size
          # from the model's configuration.
          @model.connection_db_config.configuration_hash[:pool] ||
            Database.default_pool_size
        end

        # Returns `true` if the use of load balancing replicas should be
        # enabled.
        #
        # This is disabled for Rake tasks to ensure e.g. database migrations
        # always produce consistent results.
        def load_balancing_enabled?
          return false if Gitlab::Runtime.rake?

          hosts.any? || service_discovery_enabled?
        end

        # This is disabled for Rake tasks to ensure e.g. database migrations
        # always produce consistent results.
        def service_discovery_enabled?
          return false if Gitlab::Runtime.rake?

          service_discovery[:record].present?
        end

        # TODO: This is temporary code to allow re-use of primary connection
        # if the two connections are pointing to the same host. This is needed
        # to properly support transaction visibility.
        #
        # This behavior is required to support [Phase 3](https://gitlab.com/groups/gitlab-org/-/epics/6160#progress).
        # This method is meant to be removed as soon as it is finished.
        #
        # The remapping is done as-is:
        #   export GITLAB_LOAD_BALANCING_REUSE_PRIMARY_<name-of-connection>=<new-name-of-connection>
        #
        # Ex.:
        #   export GITLAB_LOAD_BALANCING_REUSE_PRIMARY_ci=main
        #
        def reuse_primary_connection!
          new_connection = ENV["GITLAB_LOAD_BALANCING_REUSE_PRIMARY_#{db_config_name}"]
          return unless new_connection.present?

          @primary_model = Gitlab::Database.database_base_models[new_connection.to_sym]

          unless @primary_model
            raise "Invalid value for 'GITLAB_LOAD_BALANCING_REUSE_PRIMARY_#{db_config_name}=#{new_connection}'"
          end
        end
      end
    end
  end
end