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

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

Instance Method Summary collapse

Methods included from PartitionHelpers

#partition?, #table_partitioned?

Instance Method Details

#async_index_creation_available?Boolean

Returns:

  • (Boolean)


171
172
173
# File 'lib/gitlab/database/async_indexes/migration_helpers.rb', line 171

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.

Note: The add_index_options is the same method Rails uses to generate the index creation statements. As such, we can pass index creation options to the method the same as we would standard index creation.

Example usage:

INITIAL_PIPELINE_INDEX = ‘tmp_index_vulnerability_occurrences_id_and_initial_pipline_id’ INITIAL_PIPELINE_COLUMNS = [:id, :initial_pipeline_id]

prepare_async_index TABLE_NAME, INITIAL_PIPELINE_COLUMNS, name: INITIAL_PIPELINE_INDEX, where: ‘initial_pipeline_id IS NULL’



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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/gitlab/database/async_indexes/migration_helpers.rb', line 51

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

  if table_partitioned?(table_name)
    raise ArgumentError, 'prepare_async_index can not be used on a partitioned ' \
      'table. Please use prepare_partitioned_async_index on the partitioned table.'
  end

  if partition?(table_name)
    raise ArgumentError, 'prepare_async_index can not be used on a child partition ' \
      'table. Please use prepare_partitioned_async_index on the partitioned table.'
  end

  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



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/gitlab/database/async_indexes/migration_helpers.rb', line 104

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. Except for partitioned tables where indexes cannot be dropped using this option. www.postgresql.org/docs/current/sql-dropindex.html

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



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/gitlab/database/async_indexes/migration_helpers.rb', line 142

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 = if table_partitioned?(table_name)
                 "DROP INDEX #{quote_column_name(index_name)}"
               else
                 "DROP INDEX CONCURRENTLY #{quote_column_name(index_name)}"
               end

  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



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

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



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

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