diff options
Diffstat (limited to 'lib/gitlab/database/partitioning/partition_manager.rb')
-rw-r--r-- | lib/gitlab/database/partitioning/partition_manager.rb | 64 |
1 files changed, 62 insertions, 2 deletions
diff --git a/lib/gitlab/database/partitioning/partition_manager.rb b/lib/gitlab/database/partitioning/partition_manager.rb index 124fae582d3..cc5c49cc24a 100644 --- a/lib/gitlab/database/partitioning/partition_manager.rb +++ b/lib/gitlab/database/partitioning/partition_manager.rb @@ -4,9 +4,12 @@ module Gitlab module Database module Partitioning class PartitionManager + include ::Gitlab::Utils::StrongMemoize + UnsafeToDetachPartitionError = Class.new(StandardError) - LEASE_TIMEOUT = 1.minute + LEASE_TIMEOUT = 1.hour + STATEMENT_TIMEOUT = 1.hour MANAGEMENT_LEASE_KEY = 'database_partition_management_%s' RETAIN_DETACHED_PARTITIONS_FOR = 1.week @@ -16,7 +19,7 @@ module Gitlab @connection_name = @connection.pool.db_config.name end - def sync_partitions + def sync_partitions(analyze: true) return skip_synching_partitions unless table_partitioned? Gitlab::AppLogger.info( @@ -33,6 +36,8 @@ module Gitlab create(partitions_to_create) unless partitions_to_create.empty? detach(partitions_to_detach) unless partitions_to_detach.empty? + + run_analyze_on_partitioned_table if analyze end rescue ArgumentError => e Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e) @@ -146,6 +151,61 @@ module Gitlab connection_name: @connection_name ) end + + def run_analyze_on_partitioned_table + return if Feature.disabled?(:database_analyze_on_partitioned_tables) + return if ineligible_for_analyzing? + + primary_transaction(statement_timeout: STATEMENT_TIMEOUT) do + # Running ANALYZE on partitioned table will go through itself and its partitions + connection.execute("ANALYZE VERBOSE #{model.quoted_table_name}") + end + end + + def ineligible_for_analyzing? + analyze_interval.blank? || + first_model_partition.blank? || + last_analyzed_at_within_interval? + end + + def last_analyzed_at_within_interval? + table_to_query = first_model_partition.identifier + + primary_transaction do + # We don't need to get the last_analyze_time from partitioned table, + # because it's not supported and always returns NULL for PG version below 14 + # Therefore, we can always get the last_analyze_time from the first partition + last_analyzed_at = connection.select_value( + "SELECT pg_stat_get_last_analyze_time('#{table_to_query}'::regclass)" + ) + last_analyzed_at.present? && last_analyzed_at >= Time.current - analyze_interval + end + end + + def first_model_partition + Gitlab::Database::SharedModel.using_connection(connection) do + Gitlab::Database::PostgresPartition.for_parent_table(model.table_name).first + end + end + strong_memoize_attr :first_model_partition + + def analyze_interval + model.partitioning_strategy.analyze_interval + end + + def primary_transaction(statement_timeout: nil) + Gitlab::Database::LoadBalancing::Session.current.use_primary do + connection.transaction(requires_new: false) do + if statement_timeout.present? + connection.execute( + format("SET LOCAL statement_timeout TO '%ds'", statement_timeout) + ) + end + + yield + end + end + end end end end |