Module: Gitlab::Database::AsyncIndexes::MigrationHelpers

Included in:
MigrationHelpers
Defined in:
lib/gitlab/database/async_indexes/migration_helpers.rb

Instance Method Summary collapse

Instance Method Details

#async_index_creation_available?Boolean

Returns:

  • (Boolean)


142
143
144
# File 'lib/gitlab/database/async_indexes/migration_helpers.rb', line 142

def async_index_creation_available?
  table_exists?(:postgres_async_indexes)
end

#prepare_async_index(table_name, column_name, **options) ⇒ Object

Prepares an index for asynchronous creation.

Stores the index information in the postgres_async_indexes table to be created later. The index will be always be created CONCURRENTLY, so that option does not need to be given. If an existing asynchronous definition exists with the same name, the existing entry will be updated with the new definition.

If the requested index has already been created, it is not stored in the table for asynchronous creation.



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
# File 'lib/gitlab/database/async_indexes/migration_helpers.rb', line 38

def prepare_async_index(table_name, column_name, **options)
  Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas.require_ddl_mode!

  return unless async_index_creation_available?
  raise "Table #{table_name} does not exist" unless table_exists?(table_name)

  index_name = options[:name] || index_name(table_name, column_name)

  raise 'Specifying index name is mandatory - specify name: argument' unless index_name

  options = options.merge({ algorithm: :concurrently })

  if index_exists?(table_name, column_name, **options)
    Gitlab::AppLogger.warn(
      message: 'Index not prepared because it already exists',
      table_name: table_name,
      index_name: index_name)

    return
  end

  index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options)

  create_index = ActiveRecord::ConnectionAdapters::CreateIndexDefinition.new(index, algorithm, if_not_exists)
  schema_creation = ActiveRecord::ConnectionAdapters::PostgreSQL::SchemaCreation.new(ApplicationRecord.connection)
  definition = schema_creation.accept(create_index)

  async_index = PostgresAsyncIndex.find_or_create_by!(name: index_name) do |rec|
    rec.table_name = table_name
    rec.definition = definition
  end

  async_index.definition = definition
  async_index.save! # No-op if definition is not changed

  Gitlab::AppLogger.info(
    message: 'Prepared index for async creation',
    table_name: async_index.table_name,
    index_name: async_index.name)

  async_index
end

#prepare_async_index_from_sql(definition) ⇒ Object



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
# File 'lib/gitlab/database/async_indexes/migration_helpers.rb', line 81

def prepare_async_index_from_sql(definition)
  Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas.require_ddl_mode!

  return unless async_index_creation_available?

  table_name, index_name = extract_table_and_index_names_from_concurrent_index!(definition)

  if index_name_exists?(table_name, index_name)
    Gitlab::AppLogger.warn(
      message: 'Index not prepared because it already exists',
      table_name: table_name,
      index_name: index_name)

    return
  end

  async_index = Gitlab::Database::AsyncIndexes::PostgresAsyncIndex.find_or_create_by!(name: index_name) do |rec|
    rec.table_name = table_name
    rec.definition = definition.to_s.strip
  end

  Gitlab::AppLogger.info(
    message: 'Prepared index for async creation',
    table_name: async_index.table_name,
    index_name: async_index.name)

  async_index
end

#prepare_async_index_removal(table_name, column_name, options = {}) ⇒ Object

Prepares an index for asynchronous destruction.

Stores the index information in the postgres_async_indexes table to be removed later. The index will be always be removed CONCURRENTLY, so that option does not need to be given.

If the requested index has already been removed, it is not stored in the table for asynchronous destruction.



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/gitlab/database/async_indexes/migration_helpers.rb', line 117

def prepare_async_index_removal(table_name, column_name, options = {})
  index_name = options.fetch(:name)
  raise 'prepare_async_index_removal must get an index name defined' if index_name.blank?

  unless index_exists?(table_name, column_name, **options)
    Gitlab::AppLogger.warn "Index not removed because it does not exist (this may be due to an aborted migration or similar): table_name: #{table_name}, index_name: #{index_name}"
    return
  end

  definition = "DROP INDEX CONCURRENTLY #{quote_column_name(index_name)}"

  async_index = PostgresAsyncIndex.find_or_create_by!(name: index_name) do |rec|
    rec.table_name = table_name
    rec.definition = definition
  end

  Gitlab::AppLogger.info(
    message: 'Prepared index for async destruction',
    table_name: async_index.table_name,
    index_name: async_index.name
  )

  async_index
end

#unprepare_async_index(table_name, column_name, **options) ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
# File 'lib/gitlab/database/async_indexes/migration_helpers.rb', line 7

def unprepare_async_index(table_name, column_name, **options)
  Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas.require_ddl_mode!

  return unless async_index_creation_available?

  index_name = options[:name] || index_name(table_name, column_name)

  raise 'Specifying index name is mandatory - specify name: argument' unless index_name

  unprepare_async_index_by_name(table_name, index_name)
end

#unprepare_async_index_by_name(table_name, index_name, **options) ⇒ Object



19
20
21
22
23
24
25
26
27
# File 'lib/gitlab/database/async_indexes/migration_helpers.rb', line 19

def unprepare_async_index_by_name(table_name, index_name, **options)
  Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas.require_ddl_mode!

  return unless async_index_creation_available?

  PostgresAsyncIndex.find_by(name: index_name).try do |async_index|
    async_index.destroy!
  end
end