1# frozen_string_literal: true 2 3module Gitlab 4 module Database 5 DATABASE_NAMES = %w[main ci].freeze 6 7 MAIN_DATABASE_NAME = 'main' 8 CI_DATABASE_NAME = 'ci' 9 DEFAULT_POOL_HEADROOM = 10 10 11 # This constant is used when renaming tables concurrently. 12 # If you plan to rename a table using the `rename_table_safely` method, add your table here one milestone before the rename. 13 # Example: 14 # TABLES_TO_BE_RENAMED = { 15 # 'old_name' => 'new_name' 16 # }.freeze 17 TABLES_TO_BE_RENAMED = {}.freeze 18 19 # Minimum PostgreSQL version requirement per documentation: 20 # https://docs.gitlab.com/ee/install/requirements.html#postgresql-requirements 21 MINIMUM_POSTGRES_VERSION = 12 22 23 # https://www.postgresql.org/docs/9.2/static/datatype-numeric.html 24 MAX_INT_VALUE = 2147483647 25 MIN_INT_VALUE = -2147483648 26 27 # The max value between MySQL's TIMESTAMP and PostgreSQL's timestampz: 28 # https://www.postgresql.org/docs/9.1/static/datatype-datetime.html 29 # https://dev.mysql.com/doc/refman/5.7/en/datetime.html 30 # FIXME: this should just be the max value of timestampz 31 MAX_TIMESTAMP_VALUE = Time.at((1 << 31) - 1).freeze 32 33 # The maximum number of characters for text fields, to avoid DoS attacks via parsing huge text fields 34 # https://gitlab.com/gitlab-org/gitlab-foss/issues/61974 35 MAX_TEXT_SIZE_LIMIT = 1_000_000 36 37 # Minimum schema version from which migrations are supported 38 # Migrations before this version may have been removed 39 MIN_SCHEMA_VERSION = 20190506135400 40 MIN_SCHEMA_GITLAB_VERSION = '11.11.0' 41 42 # Schema we store dynamically managed partitions in (e.g. for time partitioning) 43 DYNAMIC_PARTITIONS_SCHEMA = :gitlab_partitions_dynamic 44 45 # Schema we store static partitions in (e.g. for hash partitioning) 46 STATIC_PARTITIONS_SCHEMA = :gitlab_partitions_static 47 48 # This is an extensive list of postgres schemas owned by GitLab 49 # It does not include the default public schema 50 EXTRA_SCHEMAS = [DYNAMIC_PARTITIONS_SCHEMA, STATIC_PARTITIONS_SCHEMA].freeze 51 52 PRIMARY_DATABASE_NAME = ActiveRecord::Base.connection_db_config.name.to_sym 53 54 def self.database_base_models 55 @database_base_models ||= { 56 # Note that we use ActiveRecord::Base here and not ApplicationRecord. 57 # This is deliberate, as we also use these classes to apply load 58 # balancing to, and the load balancer must be enabled for _all_ models 59 # that inher from ActiveRecord::Base; not just our own models that 60 # inherit from ApplicationRecord. 61 main: ::ActiveRecord::Base, 62 ci: ::Ci::ApplicationRecord.connection_class? ? ::Ci::ApplicationRecord : nil 63 }.compact.with_indifferent_access.freeze 64 end 65 66 # This returns a list of base models with connection associated for a given gitlab_schema 67 def self.schemas_to_base_models 68 @schemas_to_base_models ||= { 69 gitlab_main: [self.database_base_models.fetch(:main)], 70 gitlab_ci: [self.database_base_models[:ci] || self.database_base_models.fetch(:main)], # use CI or fallback to main 71 gitlab_shared: self.database_base_models.values # all models 72 }.with_indifferent_access.freeze 73 end 74 75 def self.all_database_names 76 DATABASE_NAMES 77 end 78 79 # We configure the database connection pool size automatically based on the 80 # configured concurrency. We also add some headroom, to make sure we don't 81 # run out of connections when more threads besides the 'user-facing' ones 82 # are running. 83 # 84 # Read more about this in 85 # doc/development/database/client_side_connection_pool.md 86 def self.default_pool_size 87 headroom = 88 (ENV["DB_POOL_HEADROOM"].presence || DEFAULT_POOL_HEADROOM).to_i 89 90 Gitlab::Runtime.max_threads + headroom 91 end 92 93 def self.has_config?(database_name) 94 Gitlab::Application.config.database_configuration[Rails.env].include?(database_name.to_s) 95 end 96 97 def self.main_database?(name) 98 # The database is `main` if it is a first entry in `database.yml` 99 # Rails internally names them `primary` to avoid confusion 100 # with broad `primary` usage we use `main` instead 101 # 102 # TODO: The explicit `== 'main'` is needed in a transition period till 103 # the `database.yml` is not migrated into `main:` syntax 104 # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/65243 105 ActiveRecord::Base.configurations.primary?(name.to_s) || name.to_s == 'main' 106 end 107 108 def self.ci_database?(name) 109 name.to_s == CI_DATABASE_NAME 110 end 111 112 def self.check_postgres_version_and_print_warning 113 return if Gitlab::Runtime.rails_runner? 114 115 database_base_models.each do |name, model| 116 database = Gitlab::Database::Reflection.new(model) 117 118 next if database.postgresql_minimum_supported_version? 119 120 Kernel.warn ERB.new(Rainbow.new.wrap(<<~EOS).red).result 121 122 ██ ██ █████ ██████ ███ ██ ██ ███ ██ ██████ 123 ██ ██ ██ ██ ██ ██ ████ ██ ██ ████ ██ ██ 124 ██ █ ██ ███████ ██████ ██ ██ ██ ██ ██ ██ ██ ██ ███ 125 ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ 126 ███ ███ ██ ██ ██ ██ ██ ████ ██ ██ ████ ██████ 127 128 ****************************************************************************** 129 You are using PostgreSQL #{database.version} for the #{name} database, but PostgreSQL >= <%= Gitlab::Database::MINIMUM_POSTGRES_VERSION %> 130 is required for this version of GitLab. 131 <% if Rails.env.development? || Rails.env.test? %> 132 If using gitlab-development-kit, please find the relevant steps here: 133 https://gitlab.com/gitlab-org/gitlab-development-kit/-/blob/main/doc/howto/postgresql.md#upgrade-postgresql 134 <% end %> 135 Please upgrade your environment to a supported PostgreSQL version, see 136 https://docs.gitlab.com/ee/install/requirements.html#database for details. 137 ****************************************************************************** 138 EOS 139 rescue ActiveRecord::ActiveRecordError, PG::Error 140 # ignore - happens when Rake tasks yet have to create a database, e.g. for testing 141 end 142 end 143 144 def self.nulls_order(field, direction = :asc, nulls_order = :nulls_last) 145 raise ArgumentError unless [:nulls_last, :nulls_first].include?(nulls_order) 146 raise ArgumentError unless [:asc, :desc].include?(direction) 147 148 case nulls_order 149 when :nulls_last then nulls_last_order(field, direction) 150 when :nulls_first then nulls_first_order(field, direction) 151 end 152 end 153 154 def self.nulls_last_order(field, direction = 'ASC') 155 Arel.sql("#{field} #{direction} NULLS LAST") 156 end 157 158 def self.nulls_first_order(field, direction = 'ASC') 159 Arel.sql("#{field} #{direction} NULLS FIRST") 160 end 161 162 def self.random 163 "RANDOM()" 164 end 165 166 def self.true_value 167 "'t'" 168 end 169 170 def self.false_value 171 "'f'" 172 end 173 174 def self.sanitize_timestamp(timestamp) 175 MAX_TIMESTAMP_VALUE > timestamp ? timestamp : MAX_TIMESTAMP_VALUE.dup 176 end 177 178 def self.allow_cross_joins_across_databases(url:) 179 # this method is implemented in: 180 # spec/support/database/prevent_cross_joins.rb 181 yield 182 end 183 184 def self.add_post_migrate_path_to_rails(force: false) 185 return if ENV['SKIP_POST_DEPLOYMENT_MIGRATIONS'] && !force 186 187 Rails.application.config.paths['db'].each do |db_path| 188 path = Rails.root.join(db_path, 'post_migrate').to_s 189 190 unless Rails.application.config.paths['db/migrate'].include? path 191 Rails.application.config.paths['db/migrate'] << path 192 193 # Rails memoizes migrations at certain points where it won't read the above 194 # path just yet. As such we must also update the following list of paths. 195 ActiveRecord::Migrator.migrations_paths << path 196 end 197 end 198 end 199 200 def self.db_config_names 201 ::ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).map(&:name) 202 end 203 204 def self.db_config_for_connection(connection) 205 return unless connection 206 207 # The LB connection proxy does not have a direct db_config 208 # that can be referenced 209 return if connection.is_a?(::Gitlab::Database::LoadBalancing::ConnectionProxy) 210 211 # During application init we might receive `NullPool` 212 return unless connection.respond_to?(:pool) && 213 connection.pool.respond_to?(:db_config) 214 215 connection.pool.db_config 216 end 217 218 # At the moment, the connection can only be retrieved by 219 # Gitlab::Database::LoadBalancer#read or #read_write or from the 220 # ActiveRecord directly. Therefore, if the load balancer doesn't 221 # recognize the connection, this method returns the primary role 222 # directly. In future, we may need to check for other sources. 223 # Expected returned names: 224 # main, main_replica, ci, ci_replica, unknown 225 def self.db_config_name(connection) 226 db_config = db_config_for_connection(connection) 227 db_config&.name || 'unknown' 228 end 229 230 def self.read_only? 231 false 232 end 233 234 def self.read_write? 235 !read_only? 236 end 237 238 # Monkeypatch rails with upgraded database observability 239 def self.install_transaction_metrics_patches! 240 ActiveRecord::Base.prepend(ActiveRecordBaseTransactionMetrics) 241 end 242 243 def self.install_transaction_context_patches! 244 ActiveRecord::ConnectionAdapters::TransactionManager 245 .prepend(TransactionManagerContext) 246 ActiveRecord::ConnectionAdapters::RealTransaction 247 .prepend(RealTransactionContext) 248 end 249 250 # MonkeyPatch for ActiveRecord::Base for adding observability 251 module ActiveRecordBaseTransactionMetrics 252 extend ActiveSupport::Concern 253 254 class_methods do 255 # A patch over ActiveRecord::Base.transaction that provides 256 # observability into transactional methods. 257 def transaction(**options, &block) 258 transaction_type = get_transaction_type(connection.transaction_open?, options[:requires_new]) 259 260 ::Gitlab::Database::Metrics.subtransactions_increment(self.name) if transaction_type == :sub_transaction 261 262 payload = { connection: connection, transaction_type: transaction_type } 263 264 ActiveSupport::Notifications.instrument('transaction.active_record', payload) do 265 super(**options, &block) 266 end 267 end 268 269 private 270 271 def get_transaction_type(transaction_open, requires_new_flag) 272 if transaction_open 273 return :sub_transaction if requires_new_flag 274 275 return :fake_transaction 276 end 277 278 :real_transaction 279 end 280 end 281 end 282 283 # rubocop:disable Gitlab/ModuleWithInstanceVariables 284 module TransactionManagerContext 285 def transaction_context 286 @stack.first.try(:gitlab_transaction_context) 287 end 288 end 289 290 module RealTransactionContext 291 def gitlab_transaction_context 292 @gitlab_transaction_context ||= ::Gitlab::Database::Transaction::Context.new 293 end 294 295 def commit 296 gitlab_transaction_context.commit 297 298 super 299 end 300 301 def rollback 302 gitlab_transaction_context.rollback 303 304 super 305 end 306 end 307 # rubocop:enable Gitlab/ModuleWithInstanceVariables 308 end 309end 310 311Gitlab::Database.prepend_mod_with('Gitlab::Database') 312