Commit c6ced24d authored by Andreas Brandl's avatar Andreas Brandl

Consider truncated index names

PG limits index names to 63 chars and has to add a suffix for the
temporary index while reindexing.
parent 09cdfb16
...@@ -9,6 +9,7 @@ module Gitlab ...@@ -9,6 +9,7 @@ module Gitlab
TEMPORARY_INDEX_PATTERN = '\_ccnew[0-9]*' TEMPORARY_INDEX_PATTERN = '\_ccnew[0-9]*'
STATEMENT_TIMEOUT = 9.hours STATEMENT_TIMEOUT = 9.hours
PG_MAX_INDEX_NAME_LENGTH = 63
# When dropping an index, we acquire a SHARE UPDATE EXCLUSIVE lock, # 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 # which only conflicts with DDL and vacuum. We therefore execute this with a rather
...@@ -38,6 +39,7 @@ module Gitlab ...@@ -38,6 +39,7 @@ module Gitlab
# While this has been backpatched, we continue to disable expression indexes until further review. # While this has been backpatched, we continue to disable expression indexes until further review.
raise ReindexError, 'expression indexes are currently not supported' if index.expression? raise ReindexError, 'expression indexes are currently not supported' if index.expression?
begin
with_logging do with_logging do
set_statement_timeout do set_statement_timeout do
execute("REINDEX INDEX CONCURRENTLY #{quote_table_name(index.schema)}.#{quote_table_name(index.name)}") execute("REINDEX INDEX CONCURRENTLY #{quote_table_name(index.schema)}.#{quote_table_name(index.name)}")
...@@ -46,6 +48,7 @@ module Gitlab ...@@ -46,6 +48,7 @@ module Gitlab
ensure ensure
cleanup_dangling_indexes cleanup_dangling_indexes
end end
end
private private
...@@ -79,10 +82,23 @@ module Gitlab ...@@ -79,10 +82,23 @@ module Gitlab
end end
def cleanup_dangling_indexes def cleanup_dangling_indexes
Gitlab::Database::PostgresIndex.match("#{Regexp.escape(index.name)}#{TEMPORARY_INDEX_PATTERN}").each do |lingering_index| Gitlab::Database::PostgresIndex.match("#{TEMPORARY_INDEX_PATTERN}$").each do |lingering_index|
# Example lingering index name: some_index_ccnew1
# Example prefix: 'some_index'
prefix = lingering_index.name.gsub(/#{TEMPORARY_INDEX_PATTERN}/, '')
# Example suffix: '_ccnew1'
suffix = lingering_index.name.match(/#{TEMPORARY_INDEX_PATTERN}/)[0]
# Only remove if the lingering index name could have been chosen
# as a result of a REINDEX operation (considering that PostgreSQL
# truncates index names to 63 chars and adds a suffix).
if index.name[0...PG_MAX_INDEX_NAME_LENGTH - suffix.length] == prefix
remove_index(lingering_index) remove_index(lingering_index)
end end
end end
end
def remove_index(index) def remove_index(index)
logger.info("Removing dangling index #{index.identifier}") logger.info("Removing dangling index #{index.identifier}")
......
...@@ -8,7 +8,7 @@ RSpec.describe Gitlab::Database::Reindexing::ReindexConcurrently, '#perform' do ...@@ -8,7 +8,7 @@ RSpec.describe Gitlab::Database::Reindexing::ReindexConcurrently, '#perform' do
let(:table_name) { '_test_reindex_table' } let(:table_name) { '_test_reindex_table' }
let(:column_name) { '_test_column' } let(:column_name) { '_test_column' }
let(:index_name) { '_test_reindex_index' } let(:index_name) { '_test_reindex_index' }
let(:index) { Gitlab::Database::PostgresIndex.by_identifier("public.#{index_name}") } let(:index) { Gitlab::Database::PostgresIndex.by_identifier("public.#{iname(index_name)}") }
let(:logger) { double('logger', debug: nil, info: nil, error: nil ) } let(:logger) { double('logger', debug: nil, info: nil, error: nil ) }
let(:connection) { ActiveRecord::Base.connection } let(:connection) { ActiveRecord::Base.connection }
...@@ -73,26 +73,27 @@ RSpec.describe Gitlab::Database::Reindexing::ReindexConcurrently, '#perform' do ...@@ -73,26 +73,27 @@ RSpec.describe Gitlab::Database::Reindexing::ReindexConcurrently, '#perform' do
context 'with dangling indexes matching TEMPORARY_INDEX_PATTERN, i.e. /some\_index\_ccnew(\d)*/' do context 'with dangling indexes matching TEMPORARY_INDEX_PATTERN, i.e. /some\_index\_ccnew(\d)*/' do
before do before do
# dangling indexes # dangling indexes
connection.execute("CREATE INDEX #{index_name}_ccnew ON #{table_name} (#{column_name})") connection.execute("CREATE INDEX #{iname(index_name, '_ccnew')} ON #{table_name} (#{column_name})")
connection.execute("CREATE INDEX #{index_name}_ccnew2 ON #{table_name} (#{column_name})") connection.execute("CREATE INDEX #{iname(index_name, '_ccnew2')} ON #{table_name} (#{column_name})")
# Unrelated index - don't drop # Unrelated index - don't drop
connection.execute("CREATE INDEX some_other_index_ccnew ON #{table_name} (#{column_name})") connection.execute("CREATE INDEX some_other_index_ccnew ON #{table_name} (#{column_name})")
end end
shared_examples_for 'dropping the dangling index' do
it 'drops the dangling indexes while controlling lock_timeout' do it 'drops the dangling indexes while controlling lock_timeout' do
expect_to_execute_in_order( expect_to_execute_in_order(
# Regular index rebuild # Regular index rebuild
"SET statement_timeout TO '32400s'", "SET statement_timeout TO '32400s'",
"REINDEX INDEX CONCURRENTLY \"public\".\"#{index.name}\"", "REINDEX INDEX CONCURRENTLY \"public\".\"#{index_name}\"",
"RESET statement_timeout", "RESET statement_timeout",
# Drop _ccnew index # Drop _ccnew index
"SET lock_timeout TO '60000ms'", "SET lock_timeout TO '60000ms'",
"DROP INDEX CONCURRENTLY IF EXISTS \"public\".\"#{index.name}_ccnew\"", "DROP INDEX CONCURRENTLY IF EXISTS \"public\".\"#{iname(index_name, '_ccnew')}\"",
"RESET idle_in_transaction_session_timeout; RESET lock_timeout", "RESET idle_in_transaction_session_timeout; RESET lock_timeout",
# Drop _ccnew2 index # Drop _ccnew2 index
"SET lock_timeout TO '60000ms'", "SET lock_timeout TO '60000ms'",
"DROP INDEX CONCURRENTLY IF EXISTS \"public\".\"#{index.name}_ccnew2\"", "DROP INDEX CONCURRENTLY IF EXISTS \"public\".\"#{iname(index_name, '_ccnew2')}\"",
"RESET idle_in_transaction_session_timeout; RESET lock_timeout" "RESET idle_in_transaction_session_timeout; RESET lock_timeout"
) )
...@@ -100,6 +101,27 @@ RSpec.describe Gitlab::Database::Reindexing::ReindexConcurrently, '#perform' do ...@@ -100,6 +101,27 @@ RSpec.describe Gitlab::Database::Reindexing::ReindexConcurrently, '#perform' do
end end
end end
context 'with normal index names' do
it_behaves_like 'dropping the dangling index'
end
context 'with index name at 63 character limit' do
let(:index_name) { 'a' * 63 }
before do
# Another unrelated index - don't drop
extra_index = index_name[0...55]
connection.execute("CREATE INDEX #{extra_index}_ccnew ON #{table_name} (#{column_name})")
end
it_behaves_like 'dropping the dangling index'
end
end
def iname(name, suffix = '')
"#{name[0...63 - suffix.size]}#{suffix}"
end
def expect_to_execute_in_order(*queries) def expect_to_execute_in_order(*queries)
# Indexes cannot be created CONCURRENTLY in a transaction. Since the tests are wrapped in transactions, # Indexes cannot be created CONCURRENTLY in a transaction. Since the tests are wrapped in transactions,
# verify the original call but pass through the non-concurrent form. # verify the original call but pass through the non-concurrent form.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment