Module: Gitlab::Database::PartitioningMigrationHelpers::IndexHelpers
- Includes:
- MigrationHelpers, MigrationHelpers::Swapping, SchemaHelpers
- Included in:
- Gitlab::Database::PartitioningMigrationHelpers
- Defined in:
- lib/gitlab/database/partitioning_migration_helpers/index_helpers.rb
Constant Summary collapse
- DuplicatedIndexesError =
Class.new(StandardError)
- ERROR_SCOPE =
'index'
Constants included from MigrationHelpers
MigrationHelpers::DEFAULT_TIMESTAMP_COLUMNS, MigrationHelpers::ENFORCE_INITIALIZE_ALL_INT_IDS_FROM_MILESTONE, MigrationHelpers::INTEGER_IDS_YET_TO_INITIALIZED_TO_BIGINT_FILE_PATH, MigrationHelpers::PENDING_INT_IDS_ERROR_MSG, MigrationHelpers::TABLE_INT_IDS_YAML_FILE_COMMENT
Constants included from DynamicModelHelpers
DynamicModelHelpers::BATCH_SIZE
Constants included from Migrations::RedisHelpers
Migrations::RedisHelpers::SCAN_START_CURSOR
Constants included from Migrations::SidekiqHelpers
Migrations::SidekiqHelpers::DEFAULT_MAX_ATTEMPTS, Migrations::SidekiqHelpers::DEFAULT_TIMES_IN_A_ROW
Constants included from Migrations::ConstraintsHelpers
Migrations::ConstraintsHelpers::MAX_IDENTIFIER_NAME_LENGTH
Constants included from Migrations::BatchedBackgroundMigrationHelpers
Migrations::BatchedBackgroundMigrationHelpers::BATCH_CLASS_NAME, Migrations::BatchedBackgroundMigrationHelpers::BATCH_MIN_DELAY, Migrations::BatchedBackgroundMigrationHelpers::BATCH_MIN_VALUE, Migrations::BatchedBackgroundMigrationHelpers::BATCH_SIZE, Migrations::BatchedBackgroundMigrationHelpers::EARLY_FINALIZATION_ERROR, Migrations::BatchedBackgroundMigrationHelpers::ENFORCE_EARLY_FINALIZATION_FROM_VERSION, Migrations::BatchedBackgroundMigrationHelpers::MIGRATION_NOT_FOUND_MESSAGE, Migrations::BatchedBackgroundMigrationHelpers::MINIMUM_PAUSE_MS, Migrations::BatchedBackgroundMigrationHelpers::NonExistentMigrationError, Migrations::BatchedBackgroundMigrationHelpers::SUB_BATCH_SIZE
Instance Method Summary collapse
-
#add_concurrent_partitioned_index(table_name, column_names, options = {}) ⇒ Object
Concurrently creates a new index on a partitioned table.
-
#find_duplicate_indexes(table_name, schema_name: connection.current_schema) ⇒ Object
Finds duplicate indexes for a given schema and table.
-
#indexes_by_definition_for_table(table_name, schema_name: connection.current_schema) ⇒ Object
Retrieves a hash of index names for a given table and schema, by index definition.
-
#prepare_partitioned_async_index(table_name, column_name, **options) ⇒ Object
Prepare async index creation for partitions Once the partition indexes are created, we can then use
#add_concurrent_partitioned_indexbelow to synchronously create the partitioned index. -
#remove_concurrent_partitioned_index_by_name(table_name, index_name) ⇒ Object
Safely removes an existing index from a partitioned table.
-
#rename_indexes_for_table(table_name, new_index_names, schema_name: connection.current_schema) ⇒ Object
Renames indexes for a given table and schema, mapping by index definition, to a hash of new index names.
-
#rename_partitioned_index(table_name, old_index_name, new_index_name) ⇒ Object
Rename the index for partitioned table and its partitions.
-
#swap_partitioned_indexes(table_name, old_index_name, new_index_name) ⇒ Object
Swap the index names for partitioned table and its partitions.
- #unprepare_partitioned_async_index(table_name, column_name, **options) ⇒ Object
- #unprepare_partitioned_async_index_by_name(table_name, index_name) ⇒ Object
Methods included from MigrationHelpers::Swapping
#swap_columns, #swap_columns_default, #swap_foreign_keys, #swap_indexes
Methods included from MigrationHelpers::V2
#cleanup_concurrent_column_rename, #create_table, #rename_column_concurrently, #rename_index_with_schema, #truncate_tables!, #undo_cleanup_concurrent_column_rename, #undo_rename_column_concurrently, #with_lock_retries
Methods included from MigrationHelpers
#add_concurrent_index, #add_primary_key_using_index, #add_sequence, #add_timestamps_with_timezone, #backfill_conversion_of_integer_to_bigint, #backfill_iids, #change_column_type_concurrently, #check_trigger_permissions!, #cleanup_concurrent_column_rename, #cleanup_concurrent_column_type_change, #cleanup_conversion_of_integer_to_bigint, #column_for, #convert_to_bigint_column, #copy_foreign_keys, #copy_indexes, #create_or_update_plan_limit, #define_batchable_model, #drop_sequence, #each_batch, #each_batch_range, #ensure_backfill_conversion_of_integer_to_bigint_is_finished, #false_value, #foreign_keys_for, #index_exists_by_name?, #index_invalid?, #indexes_for, #initialize_conversion_of_integer_to_bigint, #install_rename_triggers, #install_sharding_key_assignment_trigger, #lock_tables, #remove_column_default, #remove_concurrent_index, #remove_concurrent_index_by_name, #remove_rename_triggers, #remove_sharding_key_assignment_trigger, #remove_timestamps, #rename_column_concurrently, #rename_trigger_name, #replace_sql, #restore_conversion_of_integer_to_bigint, #revert_backfill_conversion_of_integer_to_bigint, #revert_initialize_conversion_of_integer_to_bigint, #swap_primary_key, #table_integer_ids, #true_value, #undo_change_column_type_concurrently, #undo_cleanup_concurrent_column_rename, #undo_cleanup_concurrent_column_type_change, #undo_rename_column_concurrently, #update_column_in_batches
Methods included from Gitlab::Database::PartitionHelpers
#partition?, #table_partitioned?
Methods included from MigrationHelpers::WraparoundVacuumHelpers
#check_if_wraparound_in_progress
Methods included from AsyncConstraints::MigrationHelpers
#prepare_async_check_constraint_validation, #prepare_async_foreign_key_validation, #prepare_partitioned_async_check_constraint_validation, #prepare_partitioned_async_foreign_key_validation, #unprepare_async_check_constraint_validation, #unprepare_async_foreign_key_validation, #unprepare_partitioned_async_check_constraint_validation, #unprepare_partitioned_async_foreign_key_validation
Methods included from AsyncIndexes::MigrationHelpers
#async_index_creation_available?, #prepare_async_index, #prepare_async_index_from_sql, #prepare_async_index_removal, #unprepare_async_index, #unprepare_async_index_by_name
Methods included from RenameTableHelpers
#finalize_table_rename, #rename_table_safely, #undo_finalize_table_rename, #undo_rename_table_safely
Methods included from MigrationHelpers::FeatureFlagMigratorHelpers
#down_migrate_to_jsonb_setting, #down_migrate_to_setting, #up_migrate_to_jsonb_setting, #up_migrate_to_setting
Methods included from DynamicModelHelpers
define_batchable_model, #each_batch, #each_batch_range
Methods included from Migrations::ForeignKeyHelpers
#add_concurrent_foreign_key, #concurrent_foreign_key_name, #foreign_key_exists?, #remove_foreign_key_if_exists, #remove_foreign_key_without_error, #validate_foreign_key
Methods included from Migrations::LockRetriesHelpers
Methods included from Migrations::TimeoutHelpers
Methods included from Migrations::RedisHelpers
Methods included from Migrations::SidekiqHelpers
#migrate_across_instance, #migrate_within_instance, #sidekiq_queue_migrate, #sidekiq_remove_jobs
Methods included from Migrations::ExtensionHelpers
#create_extension, #drop_extension
Methods included from Migrations::ConstraintsHelpers
#add_check_constraint, #add_multi_column_not_null_constraint, #add_not_null_constraint, #add_text_limit, #check_constraint_exists?, check_constraint_exists?, #check_constraint_name, #check_not_null_constraint_exists?, #check_text_limit_exists?, #copy_check_constraints, #drop_constraint, #remove_check_constraint, #remove_multi_column_not_null_constraint, #remove_not_null_constraint, #remove_text_limit, #rename_constraint, #switch_constraint_names, #text_limit_name, #validate_check_constraint, #validate_check_constraint_name!, #validate_multi_column_not_null_constraint, #validate_not_null_constraint, #validate_text_limit
Methods included from Migrations::BatchedBackgroundMigrationHelpers
#delete_batched_background_migration, #ensure_batched_background_migration_is_finished, #finalize_batched_background_migration, #gitlab_schema_from_context, #queue_batched_background_migration
Methods included from Migrations::ReestablishedConnectionStack
#with_restored_connection_stack
Methods included from SchemaHelpers
#assert_not_in_transaction_block, #create_comment, #create_trigger, #create_trigger_function, #drop_function, #drop_trigger, #find_all_id_columns_sql, #function_exists?, #object_name, #reset_all_trigger_functions, #reset_trigger_function, #tmp_table_name, #trigger_exists?
Instance Method Details
#add_concurrent_partitioned_index(table_name, column_names, options = {}) ⇒ Object
Concurrently creates a new index on a partitioned table. In concept this works similarly to
add_concurrent_index, and won't block reads or writes on the table while the index is being built.
A special helper is required for partitioning because Postgres does not support concurrently building indexes on partitioned tables. This helper concurrently adds the same index to each partition, and creates the final index on the parent table once all of the partitions are indexed. This is the recommended safe way to add indexes to partitioned tables.
Example:
add_concurrent_partitioned_index :users, :some_column
See Rails' add_index for more info on the available arguments.
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
# File 'lib/gitlab/database/partitioning_migration_helpers/index_helpers.rb', line 28 def add_concurrent_partitioned_index(table_name, column_names, = {}) assert_not_in_transaction_block(scope: ERROR_SCOPE) raise ArgumentError, 'A name is required for indexes added to partitioned tables' unless [:name] partitioned_table = find_partitioned_table(table_name) if index_name_exists?(table_name, [:name]) Gitlab::AppLogger.warn "Index not created because it already exists (this may be due to an aborted " \ "migration or similar): table_name: #{table_name}, index_name: #{options[:name]}" return end partitioned_table.postgres_partitions.order(:name).each do |partition| partition_index_name = generated_index_name(partition.identifier, [:name]) = .merge(name: partition_index_name, allow_partition: true) add_concurrent_index(partition.identifier, column_names, ) end with_lock_retries do add_index(table_name, column_names, **) end end |
#find_duplicate_indexes(table_name, schema_name: connection.current_schema) ⇒ Object
Finds duplicate indexes for a given schema and table. This finds indexes where the index definition is identical but the names are different. Returns an array of arrays containing duplicate index name pairs.
Example:
find_duplicate_indexes('table_name_goes_here')
137 138 139 140 141 142 |
# File 'lib/gitlab/database/partitioning_migration_helpers/index_helpers.rb', line 137 def find_duplicate_indexes(table_name, schema_name: connection.current_schema) find_indexes(table_name, schema_name: schema_name) .group_by { |r| r['index_id'] } .select { |_, v| v.size > 1 } .map { |_, indexes| indexes.map { |index| index['index_name'] } } end |
#indexes_by_definition_for_table(table_name, schema_name: connection.current_schema) ⇒ Object
Retrieves a hash of index names for a given table and schema, by index definition.
Example:
indexes_by_definition_for_table('table_name_goes_here')
Returns:
{
"CREATE _ btree (created_at)" => "index_on_created_at"
}
156 157 158 159 160 161 162 163 164 165 |
# File 'lib/gitlab/database/partitioning_migration_helpers/index_helpers.rb', line 156 def indexes_by_definition_for_table(table_name, schema_name: connection.current_schema) duplicate_indexes = find_duplicate_indexes(table_name, schema_name: schema_name) unless duplicate_indexes.empty? raise DuplicatedIndexesError, "#{table_name} has duplicate indexes: #{duplicate_indexes}" end find_indexes(table_name, schema_name: schema_name) .each_with_object({}) { |row, hash| hash[row['index_id']] = row['index_name'] } end |
#prepare_partitioned_async_index(table_name, column_name, **options) ⇒ Object
Prepare async index creation for partitions
Once the partition indexes are created, we can then use #add_concurrent_partitioned_index below
to synchronously create the partitioned index
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 |
# File 'lib/gitlab/database/partitioning_migration_helpers/index_helpers.rb', line 183 def prepare_partitioned_async_index(table_name, column_name, **) Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas.require_ddl_mode! return unless async_index_creation_available? partitioned_table = find_partitioned_table(table_name) if index_exists?(table_name, column_name, **) return Gitlab::AppLogger.warn( message: 'Partitioned index not prepared because it already exists', table_name: table_name, column_name: column_name, index_name: [:name] ) end [:name] ||= index_name(table_name, column_name) partitioned_table .postgres_partitions .order(:name) .each do |partition| partition_index_name = generated_index_name(partition.identifier, [:name]) prepare_async_index(partition.identifier, column_name, **.merge(name: partition_index_name)) end end |
#remove_concurrent_partitioned_index_by_name(table_name, index_name) ⇒ Object
Safely removes an existing index from a partitioned table. The method name is a bit inaccurate as it does not drop the index concurrently, but it's named as such to maintain consistency with other similar helpers, and indicate that this should be safe to use in a production environment.
In current versions of Postgres it's impossible to drop an index concurrently, or drop an index from an individual partition that exists across the entire partitioned table. As a result this helper drops the index from the parent table, which automatically cascades to all partitions. While this does require an exclusive lock, dropping an index is a fast operation that won't block the table for a significant period of time.
Example:
remove_concurrent_partitioned_index_by_name :users, 'index_name_goes_here'
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/gitlab/database/partitioning_migration_helpers/index_helpers.rb', line 66 def remove_concurrent_partitioned_index_by_name(table_name, index_name) assert_not_in_transaction_block(scope: ERROR_SCOPE) find_partitioned_table(table_name) unless index_name_exists?(table_name, index_name) 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 with_lock_retries do remove_index(table_name, name: index_name) end end |
#rename_indexes_for_table(table_name, new_index_names, schema_name: connection.current_schema) ⇒ Object
Renames indexes for a given table and schema, mapping by index definition, to a hash of new index names.
Example:
index_names = indexes_by_definition_for_table('source_table_name_goes_here')
drop_table('source_table_name_goes_here')
rename_indexes_for_table('destination_table_name_goes_here', index_names)
175 176 177 178 |
# File 'lib/gitlab/database/partitioning_migration_helpers/index_helpers.rb', line 175 def rename_indexes_for_table(table_name, new_index_names, schema_name: connection.current_schema) current_index_names = indexes_by_definition_for_table(table_name, schema_name: schema_name) rename_indexes(current_index_names, new_index_names, schema_name: schema_name) end |
#rename_partitioned_index(table_name, old_index_name, new_index_name) ⇒ Object
Rename the index for partitioned table and its partitions.
The new_index_name will be the new name of the partitioned index.
The new name of the partition indexes will be generated by the partition table name and new_index_name
and look like e.g. index_000925dbd7.
Example:
rename_partitioned_index :users, 'existing_partitioned_index_name', 'new_index_name'
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/gitlab/database/partitioning_migration_helpers/index_helpers.rb', line 91 def rename_partitioned_index(table_name, old_index_name, new_index_name) partitioned_table = find_partitioned_table(table_name) old_index_json = index_json(find_index(partitioned_table.name) { |i| i.name == old_index_name.to_s }) partitioned_table.postgres_partitions.order(:name).each do |partition| old_partition_index_name = find_index(partition.identifier) { |i| index_json(i) == old_index_json }.name new_partition_index_name = generated_index_name(partition.identifier, new_index_name) rename_index_with_schema( partition.identifier, old_partition_index_name, new_partition_index_name, schema: partition.schema ) end rename_index_with_schema(partitioned_table.name, old_index_name, new_index_name) end |
#swap_partitioned_indexes(table_name, old_index_name, new_index_name) ⇒ Object
Swap the index names for partitioned table and its partitions.
Example:
swap_partitioned_indexes :users, 'existing_partitioned_index_name_1', 'existing_partitioned_index_name_2'
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/gitlab/database/partitioning_migration_helpers/index_helpers.rb', line 112 def swap_partitioned_indexes(table_name, old_index_name, new_index_name) partitioned_table = find_partitioned_table(table_name) old_index_json = index_json(find_index(partitioned_table.name) { |i| i.name == old_index_name.to_s }) new_index_json = index_json(find_index(partitioned_table.name) { |i| i.name == new_index_name.to_s }) partitioned_table.postgres_partitions.order(:name).each do |partition| old_partition_index_name = find_index(partition.identifier) { |i| index_json(i) == old_index_json }.name new_partition_index_name = find_index(partition.identifier) { |i| index_json(i) == new_index_json }.name swap_indexes( partition.identifier, old_partition_index_name, new_partition_index_name, schema: partition.schema ) end swap_indexes(partitioned_table.name, old_index_name, new_index_name) end |
#unprepare_partitioned_async_index(table_name, column_name, **options) ⇒ Object
209 210 211 212 |
# File 'lib/gitlab/database/partitioning_migration_helpers/index_helpers.rb', line 209 def unprepare_partitioned_async_index(table_name, column_name, **) partitioned_index_name = [:name] || index_name(table_name, column_name) unprepare_partitioned_async_index_by_name(table_name, partitioned_index_name) end |
#unprepare_partitioned_async_index_by_name(table_name, index_name) ⇒ Object
214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
# File 'lib/gitlab/database/partitioning_migration_helpers/index_helpers.rb', line 214 def unprepare_partitioned_async_index_by_name(table_name, index_name) raise ArgumentError, 'Partitioned index name is required' if index_name.blank? Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas.require_ddl_mode! return unless async_index_creation_available? find_partitioned_table(table_name) .postgres_partitions .order(:name) .each do |partition| partition_index_name = generated_index_name(partition.identifier, index_name) unprepare_async_index_by_name(partition.identifier, partition_index_name) end end |