{"id":13879420,"url":"https://github.com/braintree/pg_ha_migrations","last_synced_at":"2025-06-25T10:40:30.238Z","repository":{"id":38980680,"uuid":"153339643","full_name":"braintree/pg_ha_migrations","owner":"braintree","description":"Enforces DDL/migration safety in Ruby on Rails project with an emphasis on explicitly choosing trade-offs and avoiding unnecessary magic.","archived":false,"fork":false,"pushed_at":"2024-07-25T16:48:04.000Z","size":293,"stargazers_count":210,"open_issues_count":2,"forks_count":23,"subscribers_count":9,"default_branch":"master","last_synced_at":"2024-08-07T08:12:36.884Z","etag":null,"topics":["hacktoberfest"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/braintree.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-10-16T19:06:53.000Z","updated_at":"2024-07-19T20:59:30.000Z","dependencies_parsed_at":"2023-09-27T21:06:48.897Z","dependency_job_id":"e72ac18d-9c36-4027-b093-aa1061bcbf56","html_url":"https://github.com/braintree/pg_ha_migrations","commit_stats":null,"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/braintree%2Fpg_ha_migrations","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/braintree%2Fpg_ha_migrations/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/braintree%2Fpg_ha_migrations/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/braintree%2Fpg_ha_migrations/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/braintree","download_url":"https://codeload.github.com/braintree/pg_ha_migrations/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":226143895,"owners_count":17580245,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["hacktoberfest"],"created_at":"2024-08-06T08:02:20.411Z","updated_at":"2025-06-25T10:40:30.220Z","avatar_url":"https://github.com/braintree.png","language":"Ruby","funding_links":[],"categories":["Gems","Ruby"],"sub_categories":["Database Structure and Schema Management"],"readme":"# PgHaMigrations\n\n[![Build Status](https://github.com/braintree/pg_ha_migrations/actions/workflows/ci.yml/badge.svg)](https://github.com/braintree/pg_ha_migrations/actions/workflows/ci.yml?query=branch%3Amaster+)\n\nWe've documented our learned best practices for applying schema changes without downtime in the post [PostgreSQL at Scale: Database Schema Changes Without Downtime](https://medium.com/paypal-tech/postgresql-at-scale-database-schema-changes-without-downtime-20d3749ed680) on the [PayPal Technology Blog](https://medium.com/paypal-tech). Many of the approaches we take and choices we've made are explained in much greater depth there than in this README.\n\nInternally we apply those best practices to our Rails applications through this gem which updates ActiveRecord migrations to clearly delineate safe and unsafe DDL as well as provide safe alternatives where possible.\n\nSome projects attempt to hide complexity by having code determine the intent and magically do the right series of operations. But we (and by extension this gem) take the approach that it's better to understand exactly what the database is doing so that (particularly long running) operations are not a surprise during your deploy cycle.\n\nProvided functionality:\n- [Migrations](#migrations)\n- [Utilities](#utilities)\n- [Rake Tasks](#rake-tasks)\n\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'pg_ha_migrations'\n```\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install pg_ha_migrations\n\n## Migration Safety\n\nThere are two major classes of concerns we try to handle in the API:\n\n- Database safety (e.g., long-held locks)\n- Application safety (e.g., dropping columns the app uses)\n\n### Migration Method Renaming\n\nWe rename migration methods with prefixes to explicitly denote their safety level:\n\n- `safe_*`: These methods check for both application and database safety concerns, prefer concurrent operations where available, set low lock timeouts where appropriate, and decompose operations into multiple safe steps.\n- `unsafe_*`: Using these methods is a signal that the DDL operation is not necessarily safe for a running application. They include basic safety features like safe lock acquisition and dependent object checking, but otherwise dispatch directly to the native ActiveRecord migration method.\n- `raw_*`: These methods are a direct dispatch to the native ActiveRecord migration method.\n\nCalling the original migration methods without a prefix will raise an error.\n\nThe API is designed to be explicit yet remain flexible. There may be situations where invoking the `unsafe_*` method is preferred (or the only option available for definitionally unsafe operations).\n\nWhile `unsafe_*` methods were historically (before 2.0) pure wrappers for invoking the native ActiveRecord migration method, there is a class of problems that we can't handle easily without breaking that design rule a bit. For example, dropping a column is unsafe from an application perspective, so we make the application safety concerns explicit by using an `unsafe_` prefix. Using `unsafe_remove_column` calls out the need to audit the application to confirm the migration won't break the application. Because there are no safe alternatives we don't define a `safe_remove_column` analogue. However there are still conditions we'd like to assert before dropping a column. For example, dropping an unused column that's used in one or more indexes may be safe from an application perspective, but the cascading drop of the index won't use a `CONCURRENT` operation to drop the dependent indexes and is therefore unsafe from a database perspective.\n\nFor `unsafe_*` migration methods which 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. These checks will run by default, but you can opt-out by setting `config.check_for_dependent_objects = false` [in your configuration initializer](#configuration).\n\n### Disallowed Migration Methods\n\nWe disallow the use of `unsafe_change_table`, as the equivalent operation can be composed with explicit `safe_*` / `unsafe_*` methods. If you _must_ use `change_table`, it is still available as `raw_change_table`.\n\n### Migration Method Arguments\n\nWe believe the `force: true` option to ActiveRecord's `create_table` method is always unsafe because it's not possible to denote exactly how the current state will change. Therefore we disallow using `force: true` even when calling `unsafe_create_table`. This option is enabled by default, but you can opt-out by setting `config.allow_force_create_table = true` [in your configuration initializer](#configuration).\n\n### Rollback\n\nBecause we require that [\"Rollback strategies do not involve reverting the database schema to its previous version\"](https://medium.com/paypal-tech/postgresql-at-scale-database-schema-changes-without-downtime-20d3749ed680#360a), PgHaMigrations does not support ActiveRecord's automatic migration rollback capability.\n\nInstead we write all of our migrations with only an `def up` method like:\n\n```\ndef up\n  safe_add_column :table, :column\nend\n```\n\nand never use `def change`. We believe that this is the only safe approach in production environments. For development environments we iterate by recreating the database from scratch every time we make a change.\n\n### Transactional DDL\n\nIndividual DDL statements in PostgreSQL are transactional by default (as are all Postgres statements). Concurrent index creation and removal are two exceptions: these utility commands manage their own transaction state (and each uses multiple transactions to achieve the desired concurrency).\n\nWe [disable ActiveRecord's DDL transactions](./lib/pg_ha_migrations/hacks/disable_ddl_transaction.rb) (which wrap the entire migration file in a transaction) by default for the following reasons:\n\n* [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).\n* Acquired locks are held until the end of the transaction.\n* Multiple locks creates the possibility of deadlocks.\n* Increased exposure to long waits:\n  * Each newly acquired lock has its own timeout applied (so total lock time is additive).\n  * [Safe lock acquisition](#safely_acquire_lock_for_table) (which is used in each migration method where locks will be acquired) can issue multiple lock attempts on lock timeouts (with sleep delays between attempts).\n\nBecause of the above issues attempting to re-enable transaction migrations forfeits many of the safety guarantees this library provides and may even break certain functionally. If you'd like to experiment with it anyway you can re-enable transactional migrations by adding `self.disable_ddl_transaction = false` to your migration class definition.\n\n## Usage\n\n### Unsupported ActiveRecord Features\n\nThe following functionality is currently unsupported:\n\n- [Rollback methods in migrations](#rollback)\n- Generators\n- schema.rb\n\n### Compatibility Notes\n\n- While some features may work with other versions, this gem is currently tested against PostgreSQL 13+ and Partman 4.x\n\n### Migration Methods\n\n#### safe\\_create\\_table\n\nSafely creates a new table.\n\n```ruby\nsafe_create_table :table do |t|\n  t.type :column\nend\n```\n\n#### safe\\_create\\_enum\\_type\n\nSafely create a new enum without values.\n\n```ruby\nsafe_create_enum_type :enum\n```\nOr, safely create the enum with values.\n```ruby\nsafe_create_enum_type :enum, [\"value1\", \"value2\"]\n```\n\n#### safe\\_add\\_enum\\_value\n\nSafely add a new enum value.\n\n```ruby\nsafe_add_enum_value :enum, \"value\"\n```\n\n#### unsafe\\_rename\\_enum\\_value\n\nUnsafely change the value of an enum type entry.\n\n```ruby\nunsafe_rename_enum_value(:enum, \"old_value\", \"new_value\")\n```\n\n\u003e **Note:** Changing an enum value does not issue any long-running scans or acquire locks on usages of the enum type. Therefore multiple queries within a transaction concurrent with the change may see both the old and new values. To highlight these potential pitfalls no `safe_rename_enum_value` equivalent exists. Before modifying an enum type entry you should verify that no concurrently executing queries will attempt to write the old value and that read queries understand the new value.\n\n#### safe\\_add\\_column\n\nSafely add a column.\n\n```ruby\nsafe_add_column :table, :column, :type\n```\n\n#### unsafe\\_add\\_column\n\nUnsafely add a column, but do so with a lock that is safely acquired.\n\n```ruby\nunsafe_add_column :table, :column, :type\n```\n\n#### safe\\_change\\_column\\_default\n\nSafely change the default value for a column.\n\n```ruby\n# Constant value:\nsafe_change_column_default :table, :column, \"value\"\nsafe_change_column_default :table, :column, DateTime.new(...)\n# Functional expression evaluated at row insert time:\nsafe_change_column_default :table, :column, -\u003e { \"NOW()\" }\n# Functional expression evaluated at migration time:\nsafe_change_column_default :table, :column, -\u003e { \"'NOW()'\" }\n```\n\n\u003e **Note:** On Postgres 11+ adding a column with a constant default value does not rewrite or scan the table (under a lock or otherwise). In that case a migration adding a column with a default should do so in a single operation rather than the two-step `safe_add_column` followed by `safe_change_column_default`. We enforce this best practice with the error `PgHaMigrations::BestPracticeError`, but if your prefer otherwise (or are running in a mixed Postgres version environment), you may opt out by setting `config.prefer_single_step_column_addition_with_default = false` [in your configuration initializer](#configuration).\n\n#### safe\\_make\\_column\\_nullable\n\nSafely make the column nullable.\n\n```ruby\nsafe_make_column_nullable :table, :column\n```\n#### safe\\_make\\_column\\_not\\_nullable\n\nSafely make the column not nullable. This method uses a `CHECK column IS NOT NULL` constraint to validate no values are null before altering the column. If such a constraint exists already, it is re-used, if it does not, a temporary constraint is added. Whether or not the constraint already existed, the constraint will be validated, if necessary, and removed after the column is marked `NOT NULL`.\n\n```ruby\nsafe_make_column_not_nullable :table, :column\n```\n\n\u003e **Note:**\n\u003e - This method may perform a full table scan to validate that no NULL values exist in the column. While no exclusive lock is held for this scan, on large tables the scan may take a long time.\n\u003e - The method runs multiple DDL statements non-transactionally. Validating the constraint can fail. In such cases an INVALID constraint will be left on the table. Calling `safe_make_column_not_nullable` again is safe.\n\nIf you want to avoid a full table scan and have already added and validated a suitable CHECK constraint, consider using [`safe_make_column_not_nullable_from_check_constraint`](#safe_make_column_not_nullable_from_check_constraint) instead.\n\n#### unsafe\\_make\\_column\\_not\\_nullable\n\nUnsafely make a column not nullable.\n\n```ruby\nunsafe_make_column_not_nullable :table, :column\n```\n\n#### safe\\_make\\_column\\_not\\_nullable\\_from\\_check\\_constraint\n\nVariant of `safe_make_column_not_nullable` that safely makes a column NOT NULL using an existing validated CHECK constraint that enforces non-null values for the column. This method is expected to always be fast because it avoids a full table scan.\n\n```ruby\nsafe_make_column_not_nullable_from_check_constraint :table, :column, constraint_name: :constraint_name\n```\n\n- `constraint_name` (required): The name of a validated CHECK constraint that enforces `column IS NOT NULL`.\n- `drop_constraint:` (optional, default: true): Whether to drop the constraint after making the column NOT NULL.\n\nYou should use [`safe_make_column_not_nullable`](#safe_make_column_not_nullable) when neither a CHECK constraint or a NOT NULL constraint exists already. You should use this method when you already have an equivalent CHECK constraint on the table.\n\nThis method will raise an error if the constraint does not exist, is not validated, or does not strictly enforce non-null values for the column.\n\n\u003e **Note:** We do not attempt to catch all possible proofs of `column IS NOT NULL` by means of an existing constraint; only a constraint with the exact definition `column IS NOT NULL` will be recognized.\n\n#### safe\\_add\\_index\\_on\\_empty\\_table\n\nSafely add an index on a table with zero rows. This will raise an error if the table contains data.\n\n```ruby\nsafe_add_index_on_empty_table :table, :column\n```\n\n#### safe\\_add\\_concurrent\\_index\n\nAdd an index concurrently.\n\n```ruby\nsafe_add_concurrent_index :table, :column\n```\n\nAdd a composite btree index.\n\n```ruby\nsafe_add_concurrent_index :table, [:column1, :column2], name: \"index_name\", using: :btree\n```\n\n#### safe\\_remove\\_concurrent\\_index\n\nSafely remove an index. Migrations that contain this statement must also include `disable_ddl_transaction!`.\n\n```ruby\nsafe_remove_concurrent_index :table, :name =\u003e :index_name\n```\n\n#### safe\\_add\\_concurrent\\_partitioned\\_index\n\nAdd an index to a natively partitioned table concurrently, as described in the [table partitioning docs](https://www.postgresql.org/docs/current/ddl-partitioning.html):\n\n\u003e 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.\n\u003e The indexes on partitions can be created individually using `CONCURRENTLY`, and then attached to the index on the parent using `ALTER INDEX .. ATTACH PARTITION`.\n\u003e Once indexes for all partitions are attached to the parent index, the parent index is marked valid automatically.\n\n```ruby\n# Assuming this table has partitions child1 and child2, the following indexes will be created:\n#   - index_partitioned_table_on_column\n#   - index_child1_on_column (attached to index_partitioned_table_on_column)\n#   - index_child2_on_column (attached to index_partitioned_table_on_column)\nsafe_add_concurrent_partitioned_index :partitioned_table, :column\n```\n\nAdd a composite index using the `hash` index type with custom name for the parent index when the parent table contains sub-partitions.\n\n```ruby\n# Assuming this table has partitions child1 and child2, and child1 has sub-partitions sub1 and sub2,\n# the following indexes will be created:\n#   - custom_name_idx\n#   - index_child1_on_column1_column2 (attached to custom_name_idx)\n#   - index_sub1_on_column1_column2 (attached to index_child1_on_column1_column2)\n#   - index_sub2_on_column1_column2 (attached to index_child1_on_column1_column2)\n#   - index_child2_on_column1_column2 (attached to custom_name_idx)\nsafe_add_concurrent_partitioned_index :partitioned_table, [:column1, :column2], name: \"custom_name_idx\", using: :hash\n```\n\n\u003e **Note:**\n\u003e This method runs multiple DDL statements non-transactionally.\n\u003e Creating or attaching an index on a child table could fail.\n\u003e In such cases an exception will be raised, and an `INVALID` index will be left on the parent table.\n\n#### safe\\_add\\_unvalidated\\_check\\_constraint\n\nSafely 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.\n\n```ruby\nsafe_add_unvalidated_check_constraint :table, \"column LIKE 'example%'\", name: :constraint_table_on_column_like_example\n```\n\n#### safe\\_validate\\_check\\_constraint\n\nSafely validate (without acquiring an exclusive lock) existing rows for a newly added but as-yet unvalidated `CHECK` constraint.\n\n```ruby\nsafe_validate_check_constraint :table, name: :constraint_table_on_column_like_example\n```\n\n#### safe\\_rename\\_constraint\n\nSafely rename any (not just `CHECK`) constraint.\n\n```ruby\nsafe_rename_constraint :table, from: :constraint_table_on_column_like_typo, to: :constraint_table_on_column_like_example\n```\n\n#### unsafe\\_remove\\_constraint\n\nDrop any (not just `CHECK`) constraint.\n\n```ruby\nunsafe_remove_constraint :table, name: :constraint_table_on_column_like_example\n```\n\n#### safe\\_create\\_partitioned\\_table\n\nSafely create a new partitioned table using [declaritive partitioning](https://www.postgresql.org/docs/current/ddl-partitioning.html#DDL-PARTITIONING-DECLARATIVE).\n\n```ruby\n# list partitioned table using single column as partition key\nsafe_create_partitioned_table :table, type: :list, partition_key: :example_column do |t|\n  t.text :example_column, null: false\nend\n\n# range partitioned table using multiple columns as partition key\nsafe_create_partitioned_table :table, type: :range, partition_key: [:example_column_a, :example_column_b] do |t|\n  t.integer :example_column_a, null: false\n  t.integer :example_column_b, null: false\nend\n\n# hash partitioned table using expression as partition key\nsafe_create_partitioned_table :table, :type: :hash, partition_key: -\u003e{ \"(example_column::date)\" } do |t|\n  t.datetime :example_column, null: false\nend\n```\n\nThe identifier column type is `bigserial` by default. This can be overridden, as you would in `safe_create_table`, by setting the `id` argument:\n\n```ruby\nsafe_create_partitioned_table :table, id: :serial, type: :range, partition_key: :example_column do |t|\n  t.date :example_column, null: false\nend\n```\n\nIn PostgreSQL 11+, primary key constraints are supported on partitioned tables given the partition key is included. On supported versions, the primary key is inferred by default (see [available options](#available-options)). This functionality can be overridden by setting the `infer_primary_key` argument.\n\n```ruby\n# primary key will be (id, example_column)\nsafe_create_partitioned_table :table, type: :range, partition_key: :example_column do |t|\n  t.date :example_column, null: false\nend\n\n# primary key will not be created\nsafe_create_partitioned_table :table, type: :range, partition_key: :example_column, infer_primary_key: false do |t|\n  t.date :example_column, null: false\nend\n```\n\n#### safe\\_partman\\_create\\_parent\n\nSafely configure a partitioned table to be managed by [pg\\_partman](https://github.com/pgpartman/pg_partman).\n\nThis method calls the [create\\_parent](https://github.com/pgpartman/pg_partman/blob/master/doc/pg_partman.md#creation-functions) partman function with some reasonable defaults and a subset of user-defined overrides.\n\nThe first (and only) positional argument maps to `p_parent_table` in the `create_parent` function.\n\nThe rest are keyword args with the following mappings:\n\n- `partition_key` -\u003e `p_control`. Required: `true`\n- `interval` -\u003e `p_interval`. Required: `true`\n- `template_table` -\u003e `p_template_table`. Required: `false`. Partman will create a template table if not defined.\n- `premake` -\u003e `p_premake`. Required: `false`. Partman defaults to `4`.\n- `start_partition` -\u003e `p_start_partition`. Required: `false`. Partman defaults to the current timestamp.\n\n\u003e **Note:** We have chosen to require PostgreSQL 11+ and hardcode `p_type` to `native` for simplicity, as previous PostgreSQL versions are end-of-life.\n\nAdditionally, this method allows you to configure a subset of attributes on the record stored in the [part\\_config](https://github.com/pgpartman/pg_partman/blob/master/doc/pg_partman.md#tables) table.\nThese options are delegated to the `unsafe_partman_update_config` method to update the record:\n\n- `infinite_time_partitions`. Partman defaults this to `false` but we default to `true`\n- `inherit_privileges`. Partman defaults this to `false` but we default to `true`\n- `retention`. Partman defaults this to `null`\n- `retention_keep_table`. Partman defaults this to `true`\n\nWith only the required args:\n\n```ruby\nsafe_create_partitioned_table :table, type: :range, partition_key: :created_at do |t|\n  t.timestamps null: false\nend\n\nsafe_partman_create_parent :table, partition_key: :created_at, interval: \"weekly\"\n```\n\nWith custom overrides:\n\n```ruby\nsafe_create_partitioned_table :table, type: :range, partition_key: :created_at do |t|\n  t.timestamps null: false\n  t.text :some_column\nend\n\n# Partman will reference the template table to create unique indexes on child tables\nsafe_create_table :table_template, id: false do |t|\n  t.text :some_column, index: {unique: true}\nend\n\nsafe_partman_create_parent :table,\n  partition_key: :created_at,\n  interval: \"weekly\",\n  template_table: :table_template,\n  premake: 10,\n  start_partition: Time.current + 1.month,\n  infinite_time_partitions: false,\n  inherit_privileges: false,\n  retention: \"60 days\",\n  retention_keep_table: false\n```\n\n#### safe\\_partman\\_update\\_config\n\nThere are some partitioning options that cannot be set in the call to `create_parent` and are only available in the `part_config` table.\nAs mentioned previously, you can specify these args in the call to `safe_partman_create_parent` which will be delegated to this method.\nCalling this method directly will be useful if you need to modify your partitioned table after the fact.\n\nAllowed keyword args:\n\n- `infinite_time_partitions`\n- `inherit_privileges`\n- `premake`\n- `retention`\n- `retention_keep_table`\n\n\u003e **Note:** If `inherit_privileges` will change then `safe_partman_reapply_privileges` will be automatically called to ensure permissions are propagated to existing child partitions.\n\n```ruby\nsafe_partman_update_config :table,\n  infinite_time_partitions: false,\n  inherit_privileges: false,\n  premake: 10\n```\n\n#### unsafe\\_partman\\_update\\_config\n\nWe have chosen to flag the use of `retention` and `retention_keep_table` as an unsafe operation.\nWhile we recognize that these options are useful, changing these values fits in the same category as `drop_table` and `rename_table`, and is therefore unsafe from an application perspective.\nIf you wish to change these options, you must use this method.\n\n```ruby\nunsafe_partman_update_config :table,\n  retention: \"60 days\",\n  retention_keep_table: false\n```\n\n#### safe\\_partman\\_reapply\\_privileges\n\nIf your partitioned table is configured with `inherit_privileges` set to `true`, use this method after granting new roles / privileges on the parent table to ensure permissions are propagated to existing child partitions.\n\n```ruby\nsafe_partman_reapply_privileges :table\n```\n\n### Utilities\n\n#### safely\\_acquire\\_lock\\_for\\_table\n\nAcquires a lock (in `ACCESS EXCLUSIVE` mode by default) on a table using the following algorithm:\n\n1. Verify that no long-running queries are using the table.\n    - If long-running queries are currently using the table, sleep `PgHaMigrations::LOCK_TIMEOUT_SECONDS` and check again.\n2. If no long-running queries are currently using the table, optimistically attempt to lock the table (with a timeout of `PgHaMigrations::LOCK_TIMEOUT_SECONDS`).\n    - If the lock is not acquired, sleep `PgHaMigrations::LOCK_FAILURE_RETRY_DELAY_MULTLIPLIER * PgHaMigrations::LOCK_TIMEOUT_SECONDS`, and start again at step 1.\n3. If the lock is acquired, proceed to run the given block.\n\n```ruby\nsafely_acquire_lock_for_table(:table) do\n  ...\nend\n```\n\nSafely acquire a lock on a table in `SHARE` mode.\n\n```ruby\nsafely_acquire_lock_for_table(:table, mode: :share) do\n  ...\nend\n```\n\nSafely acquire a lock on multiple tables in `EXCLUSIVE` mode.\n\n```ruby\nsafely_acquire_lock_for_table(:table_a, :table_b, mode: :exclusive) do\n  ...\nend\n```\n\n\u003e **Note:** We enforce that only one set of tables can be locked at a time.\n\u003e Attempting to acquire a nested lock on a different set of tables will result in an error.\n\n#### adjust\\_lock\\_timeout\n\nAdjust lock timeout.\n\n```ruby\nadjust_lock_timeout(seconds) do\n  ...\nend\n```\n\n#### adjust\\_statement\\_timeout\n\nAdjust statement timeout.\n\n```ruby\nadjust_statement_timeout(seconds) do\n  ...\nend\n```\n\n#### safe\\_set\\_maintenance\\_work\\_mem\\_gb\n\nSet maintenance work mem.\n\n```ruby\nsafe_set_maintenance_work_mem_gb 1\n```\n\n#### ensure\\_small\\_table!\n\nEnsure a table on disk is below the default threshold (10 megabytes).\nThis will raise an error if the table is too large.\n\n```ruby\nensure_small_table! :table\n```\n\nEnsure a table on disk is below a custom threshold and is empty.\nThis will raise an error if the table is too large and/or contains data.\n\n```ruby\nensure_small_table! :table, empty: true, threshold: 100.megabytes\n```\n\n### Configuration\n\nThe gem can be configured in an initializer.\n\n```ruby\nPgHaMigrations.configure do |config|\n  # ...\nend\n```\n\n#### Available options\n\n- `disable_default_migration_methods`: If true, the default implementations of DDL changes in `ActiveRecord::Migration` and the PostgreSQL adapter will be overridden by implementations that raise a `PgHaMigrations::UnsafeMigrationError`. Default: `true`\n- `check_for_dependent_objects`: If true, some `unsafe_*` migration methods will raise a `PgHaMigrations::UnsafeMigrationError` if any dependent objects exist. Default: `true`\n- `prefer_single_step_column_addition_with_default`: If true, raise an error when adding a column and separately setting a constant default value for that column in the same migration. Default: `true`\n- `allow_force_create_table`: If false, the `force: true` option to ActiveRecord's `create_table` method is disallowed. Default: `false`\n- `infer_primary_key_on_partitioned_tables`: If true, the primary key for partitioned tables will be inferred on PostgreSQL 11+ databases (identifier column + partition key columns). Default: `true`\n\n### Rake Tasks\n\nUse this to check for blocking transactions before migrating.\n\n    $ bundle exec rake pg_ha_migrations:check_blocking_database_transactions\n\nThis rake task expects that you already have a connection open to your database. We suggest that you add another rake task to open the connection and then add that as a prerequisite for `pg_ha_migrations:check_blocking_database_transactions`.\n\n```ruby\nnamespace :db do\n  desc \"Establish a database connection\"\n  task :establish_connection do\n    ActiveRecord::Base.establish_connection\n  end\nend\n\nRake::Task[\"pg_ha_migrations:check_blocking_database_transactions\"].enhance [\"db:establish_connection\"]\n```\n\n\n## Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies and start a postgres docker container. Then, run `bundle exec rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. This project uses Appraisal to test against multiple versions of ActiveRecord; you can run the tests against all supported version with `bundle exec appraisal rspec`.\n\n\u003e **Warning**: If you rebuild the Docker container _without_ using `docker-compose build` (or the `--build` flag), it will not respect the `PGVERSION` environment variable that you've set if image layers from a different version exist. The Dockerfile uses a build-time argument that's only evaluated during the initial build. To change the Postgres version, you should explicitly provide the build argument: `docker-compose build --build-arg PGVERSION=15`. **Using `bin/setup` handles this for you.**\n\n\u003e **Warning**: The Postgres Dockerfile automatically creates an anonymous volume for the data directory. When changing the specified `PGVERSION` environment variable this volume must be reset using `--renew-anon-volumes` or booting Postgres will fail.  **Using `bin/setup` handles this for you.**\n\nRunning tests will automatically create a test database in the locally running Postgres server. You can find the connection parameters in `spec/spec_helper.rb`, but setting the environment variables `PGHOST`, `PGPORT`, `PGUSER`, and `PGPASSWORD` will override the defaults.\n\nTo install this gem onto your local machine, run `bundle exec rake install`.\n\nTo release a new version, update the version number in `version.rb`, commit the change, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).\n\n\u003e **Note:** If while releasing the gem you get the error ``Your rubygems.org credentials aren't set. Run `gem push` to set them.`` you can more simply run `gem signin`.\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/braintreeps/pg_ha_migrations. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).\n\n## Code of Conduct\n\nEveryone interacting in the PgHaMigrations project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/braintreeps/pg_ha_migrations/blob/master/CODE_OF_CONDUCT.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbraintree%2Fpg_ha_migrations","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbraintree%2Fpg_ha_migrations","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbraintree%2Fpg_ha_migrations/lists"}