README.md in pg_ha_migrations-1.7.0 vs README.md in pg_ha_migrations-1.8.0

- old
+ new

@@ -66,18 +66,23 @@ When `unsafe_*` migration methods support checks of this type you can bypass the checks by passing an `:allow_dependent_objects` key in the method's `options` hash containing an array of dependent object types you'd like to allow. Until 2.0 none of these checks will run by default, but you can opt-in by setting `config.check_for_dependent_objects = true` [in your configuration initializer](#configuration). Similarly we believe the `force: true` option to ActiveRecord's `create_table` method is always unsafe, and therefore we disallow it even when calling `unsafe_create_table`. This option won't be enabled by default until 2.0, but you can opt-in by setting `config.allow_force_create_table = false` [in your configuration initializer](#configuration). -[Running multiple DDL statements inside a transaction acquires exclusive locks on all of the modified objects](https://medium.com/paypal-tech/postgresql-at-scale-database-schema-changes-without-downtime-20d3749ed680#cc22). For that reason, this gem [disables DDL transactions](./lib/pg_ha_migrations.rb:8) by default. You can change this by resetting `ActiveRecord::Migration.disable_ddl_transaction` in your application. +[Running multiple DDL statements inside a transaction acquires exclusive locks on all of the modified objects](https://medium.com/paypal-tech/postgresql-at-scale-database-schema-changes-without-downtime-20d3749ed680#cc22). For that reason, this gem [disables DDL transactions](./lib/pg_ha_migrations/hacks/disable_ddl_transaction.rb) by default. You can change this by resetting `ActiveRecord::Migration.disable_ddl_transaction` in your application. The following functionality is currently unsupported: - Rollbacks - Generators - schema.rb +Compatibility notes: + +- While some features may work with other versions, this gem is currently tested against PostgreSQL 11+ and Partman 4.x +- There is a [bug](https://github.com/rails/rails/pull/41490) in early versions of Rails 6.1 when using `algorithm: :concurrently`. To add / remove indexes concurrently, please upgrade to at least Rails 6.1.4. + #### safe\_create\_table Safely creates a new table. ```ruby @@ -164,10 +169,18 @@ ```ruby unsafe_make_column_not_nullable :table, :column ``` +#### safe\_add\_index\_on\_empty\_table + +Safely add an index on a table with zero rows. This will raise an error if the table contains data. + +```ruby +safe_add_index_on_empty_table :table, :column +``` + #### safe\_add\_concurrent\_index Add an index concurrently. ```ruby @@ -186,10 +199,45 @@ ```ruby safe_remove_concurrent_index :table, :name => :index_name ``` +#### safe\_add\_concurrent\_partitioned\_index + +Add an index to a natively partitioned table concurrently, as described in the [table partitioning docs](https://www.postgresql.org/docs/current/ddl-partitioning.html): + +> To avoid long lock times, it is possible to use `CREATE INDEX ON ONLY` the partitioned table; such an index is marked invalid, and the partitions do not get the index applied automatically. +> The indexes on partitions can be created individually using `CONCURRENTLY`, and then attached to the index on the parent using `ALTER INDEX .. ATTACH PARTITION`. +> Once indexes for all partitions are attached to the parent index, the parent index is marked valid automatically. + +```ruby +# Assuming this table has partitions child1 and child2, the following indexes will be created: +# - index_partitioned_table_on_column +# - index_child1_on_column (attached to index_partitioned_table_on_column) +# - index_child2_on_column (attached to index_partitioned_table_on_column) +safe_add_concurrent_partitioned_index :partitioned_table, :column +``` + +Add a composite index using the `hash` index type with custom name for the parent index when the parent table contains sub-partitions. + +```ruby +# Assuming this table has partitions child1 and child2, and child1 has sub-partitions sub1 and sub2, +# the following indexes will be created: +# - custom_name_idx +# - index_child1_on_column1_column2 (attached to custom_name_idx) +# - index_sub1_on_column1_column2 (attached to index_child1_on_column1_column2) +# - index_sub2_on_column1_column2 (attached to index_child1_on_column1_column2) +# - index_child2_on_column1_column2 (attached to custom_name_idx) +safe_add_concurrent_partitioned_index :partitioned_table, [:column1, :column2], name: "custom_name_idx", using: :hash +``` + +Note: + +This method runs multiple DDL statements non-transactionally. +Creating or attaching an index on a child table could fail. +In such cases an exception will be raised, and an `INVALID` index will be left on the parent table. + #### safe\_add\_unvalidated\_check\_constraint Safely add a `CHECK` constraint. The constraint will not be immediately validated on existing rows to avoid a full table scan while holding an exclusive lock. After adding the constraint, you'll need to use `safe_validate_check_constraint` to validate existing rows. ```ruby @@ -385,18 +433,31 @@ ### Utilities #### safely\_acquire\_lock\_for\_table -Safely acquire a lock for a table. +Safely acquire an access exclusive lock for a table. ```ruby safely_acquire_lock_for_table(:table) do ... end ``` +Safely acquire a lock for a table in a different mode. + +```ruby +safely_acquire_lock_for_table(:table, mode: :share) do + ... +end +``` + +Note: + +We enforce that only one table (or a table and its partitions) can be locked at a time. +Attempting to acquire a nested lock on a different table will result in an error. + #### adjust\_lock\_timeout Adjust lock timeout. ```ruby @@ -419,9 +480,25 @@ Set maintenance work mem. ```ruby safe_set_maintenance_work_mem_gb 1 +``` + +#### ensure\_small\_table! + +Ensure a table on disk is below the default threshold (10 megabytes). +This will raise an error if the table is too large. + +```ruby +ensure_small_table! :table +``` + +Ensure a table on disk is below a custom threshold and is empty. +This will raise an error if the table is too large and/or contains data. + +```ruby +ensure_small_table! :table, empty: true, threshold: 100.megabytes ``` ### Configuration The gem can be configured in an initializer.