summaryrefslogtreecommitdiff
path: root/lib/gitlab/database/reindexing/concurrent_reindex.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/gitlab/database/reindexing/concurrent_reindex.rb')
-rw-r--r--lib/gitlab/database/reindexing/concurrent_reindex.rb154
1 files changed, 0 insertions, 154 deletions
diff --git a/lib/gitlab/database/reindexing/concurrent_reindex.rb b/lib/gitlab/database/reindexing/concurrent_reindex.rb
deleted file mode 100644
index 7e2dd55d21b..00000000000
--- a/lib/gitlab/database/reindexing/concurrent_reindex.rb
+++ /dev/null
@@ -1,154 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Database
- module Reindexing
- class ConcurrentReindex
- include Gitlab::Utils::StrongMemoize
-
- ReindexError = Class.new(StandardError)
-
- PG_IDENTIFIER_LENGTH = 63
- TEMPORARY_INDEX_PREFIX = 'tmp_reindex_'
- REPLACED_INDEX_PREFIX = 'old_reindex_'
- STATEMENT_TIMEOUT = 9.hours
-
- # When dropping an index, we acquire a SHARE UPDATE EXCLUSIVE lock,
- # which only conflicts with DDL and vacuum. We therefore execute this with a rather
- # high lock timeout and a long pause in between retries. This is an alternative to
- # setting a high statement timeout, which would lead to a long running query with effects
- # on e.g. vacuum.
- REMOVE_INDEX_RETRY_CONFIG = [[1.minute, 9.minutes]] * 30
-
- attr_reader :index, :logger
-
- def initialize(index, logger: Gitlab::AppLogger)
- @index = index
- @logger = logger
- end
-
- def perform
- raise ReindexError, 'UNIQUE indexes are currently not supported' if index.unique?
- raise ReindexError, 'partitioned indexes are currently not supported' if index.partitioned?
- raise ReindexError, 'indexes serving an exclusion constraint are currently not supported' if index.exclusion?
- raise ReindexError, 'index is a left-over temporary index from a previous reindexing run' if index.name.start_with?(TEMPORARY_INDEX_PREFIX, REPLACED_INDEX_PREFIX)
-
- logger.info "Starting reindex of #{index}"
-
- with_rebuilt_index do |replacement_index|
- swap_index(replacement_index)
- end
- end
-
- private
-
- def with_rebuilt_index
- if Gitlab::Database::PostgresIndex.find_by(schema: index.schema, name: replacement_index_name)
- logger.debug("dropping dangling index from previous run (if it exists): #{replacement_index_name}")
- remove_index(index.schema, replacement_index_name)
- end
-
- create_replacement_index_statement = index.definition
- .sub(/CREATE INDEX #{index.name}/, "CREATE INDEX CONCURRENTLY #{replacement_index_name}")
-
- logger.info("creating replacement index #{replacement_index_name}")
- logger.debug("replacement index definition: #{create_replacement_index_statement}")
-
- set_statement_timeout do
- connection.execute(create_replacement_index_statement)
- end
-
- replacement_index = Gitlab::Database::PostgresIndex.find_by(schema: index.schema, name: replacement_index_name)
-
- unless replacement_index.valid_index?
- message = 'replacement index was created as INVALID'
- logger.error("#{message}, cleaning up")
- raise ReindexError, "failed to reindex #{index}: #{message}"
- end
-
- # Some expression indexes (aka functional indexes)
- # require additional statistics. The existing statistics
- # are tightly bound to the original index. We have to
- # rebuild statistics for the new index before dropping
- # the original one.
- rebuild_statistics if index.expression?
-
- yield replacement_index
- ensure
- begin
- remove_index(index.schema, replacement_index_name)
- rescue StandardError => e
- logger.error(e)
- end
- end
-
- def swap_index(replacement_index)
- logger.info("swapping replacement index #{replacement_index} with #{index}")
-
- with_lock_retries do
- rename_index(index.schema, index.name, replaced_index_name)
- rename_index(replacement_index.schema, replacement_index.name, index.name)
- rename_index(index.schema, replaced_index_name, replacement_index.name)
- end
- end
-
- def rename_index(schema, old_index_name, new_index_name)
- connection.execute(<<~SQL)
- ALTER INDEX #{quote_table_name(schema)}.#{quote_table_name(old_index_name)}
- RENAME TO #{quote_table_name(new_index_name)}
- SQL
- end
-
- def remove_index(schema, name)
- logger.info("Removing index #{schema}.#{name}")
-
- retries = Gitlab::Database::WithLockRetriesOutsideTransaction.new(
- timing_configuration: REMOVE_INDEX_RETRY_CONFIG,
- klass: self.class,
- logger: logger
- )
-
- retries.run(raise_on_exhaustion: false) do
- connection.execute(<<~SQL)
- DROP INDEX CONCURRENTLY
- IF EXISTS #{quote_table_name(schema)}.#{quote_table_name(name)}
- SQL
- end
- end
-
- def rebuild_statistics
- logger.info("rebuilding table statistics for #{index.schema}.#{index.tablename}")
-
- connection.execute(<<~SQL)
- ANALYZE #{quote_table_name(index.schema)}.#{quote_table_name(index.tablename)}
- SQL
- end
-
- def replacement_index_name
- @replacement_index_name ||= "#{TEMPORARY_INDEX_PREFIX}#{index.indexrelid}"
- end
-
- def replaced_index_name
- @replaced_index_name ||= "#{REPLACED_INDEX_PREFIX}#{index.indexrelid}"
- end
-
- def with_lock_retries(&block)
- arguments = { klass: self.class, logger: logger }
- Gitlab::Database::WithLockRetries.new(**arguments).run(raise_on_exhaustion: true, &block)
- end
-
- def set_statement_timeout
- execute("SET statement_timeout TO '%ds'" % STATEMENT_TIMEOUT)
- yield
- ensure
- execute('RESET statement_timeout')
- end
-
- delegate :execute, :quote_table_name, to: :connection
- def connection
- @connection ||= ActiveRecord::Base.connection
- end
- end
- end
- end
-end