README.md in strong_migrations-0.1.9 vs README.md in strong_migrations-0.2.0
- old
+ new
@@ -2,11 +2,11 @@
Catch unsafe migrations at dev time
:tangerine: Battle-tested at [Instacart](https://www.instacart.com/opensource)
-[![Build Status](https://travis-ci.org/ankane/strong_migrations.svg)](https://travis-ci.org/ankane/strong_migrations)
+[![Build Status](https://travis-ci.org/ankane/strong_migrations.svg?branch=master)](https://travis-ci.org/ankane/strong_migrations)
## Installation
Add this line to your application’s Gemfile:
@@ -14,16 +14,17 @@
gem 'strong_migrations'
```
## Dangerous Operations
+The following operations can cause downtime or errors:
+
- adding a column with a non-null default value to an existing table
- changing the type of a column
- renaming a table
- renaming a column
- removing a column
-- executing arbitrary SQL
- adding an index non-concurrently (Postgres only)
- adding a `json` column to an existing table (Postgres only)
For more info, check out:
@@ -37,33 +38,33 @@
## The Zero Downtime Way
### Adding a column with a default value
1. Add the column without a default value
-2. Commit the transaction
-3. Backfill the column
-4. Add the default value
+2. Add the default value
+3. Commit the transaction - **extremely important if you are backfilling in the migration**
+4. Backfill the column
```ruby
class AddSomeColumnToUsers < ActiveRecord::Migration
def up
# 1
add_column :users, :some_column, :text
# 2
+ change_column_default :users, :some_column, "default_value"
+
+ # 3
commit_db_transaction
- # 3.a (Rails 5+)
+ # 4.a (Rails 5+)
User.in_batches.update_all some_column: "default_value"
- # 3.b (Rails < 5)
+ # 4.b (Rails < 5)
User.find_in_batches do |users|
User.where(id: users.map(&:id)).update_all some_column: "default_value"
end
-
- # 4
- change_column_default :users, :some_column, "default_value"
end
def down
remove_column :users, :some_column
end
@@ -92,28 +93,41 @@
5. Stop writing to the old table
6. Drop the old table
### Removing a column
-Tell ActiveRecord to ignore the column from its cache.
+ActiveRecord caches database columns at runtime, so if you drop a column, it can cause exceptions until your app reboots. To prevent this:
-```ruby
-# For Rails 5+
-class User < ActiveRecord::Base
- self.ignored_columns = %w(some_column)
-end
+1. Tell ActiveRecord to ignore the column from its cache
-# For Rails < 5
-class User < ActiveRecord::Base
- def self.columns
- super.reject { |c| c.name == "some_column" }
+ ```ruby
+ # For Rails 5+
+ class User < ApplicationRecord
+ self.ignored_columns = %w(some_column)
end
-end
-```
-Once it’s deployed, create a migration to remove the column.
+ # For Rails < 5
+ class User < ActiveRecord::Base
+ def self.columns
+ super.reject { |c| c.name == "some_column" }
+ end
+ end
+ ```
+2. Deploy code
+3. Write a migration to remove the column (wrap in `safety_assured` block)
+
+ ```ruby
+ class RemoveSomeColumnFromUsers < ActiveRecord::Migration
+ def change
+ safety_assured { remove_column :users, :some_column }
+ end
+ end
+ ```
+
+4. Deploy and run migration
+
### Adding an index (Postgres)
Add indexes concurrently.
```ruby
@@ -151,10 +165,12 @@
```ruby
StrongMigrations.start_after = 20170101000000
```
+Use the version from your latest migration.
+
## Dangerous Tasks
For safety, dangerous rake tasks are disabled in production - `db:drop`, `db:reset`, `db:schema:load`, and `db:structure:load`. To get around this, use:
```sh
@@ -176,16 +192,36 @@
```ruby
task "db:schema:dump": "strong_migrations:alphabetize_columns"
```
+## Custom Error Messages
+
+To customize specific error messages, create an initializer with:
+
+```ruby
+StrongMigrations.error_messages[:add_column_default] = "Your custom instructions"
+```
+
+Check the source code for the list of keys.
+
## Analyze Tables (Postgres)
Analyze tables automatically (to update planner statistics) after an index is added. Create an initializer with:
```ruby
StrongMigrations.auto_analyze = true
```
+
+## Lock Timeout (Postgres)
+
+It’s a good idea to set a lock timeout for the database user that runs migrations. This way, if migrations can’t acquire a lock in a timely manner, other statements won’t be stuck behind it.
+
+```sql
+ALTER ROLE myuser SET lock_timeout = '10s';
+```
+
+There’s also [a gem](https://github.com/gocardless/activerecord-safer_migrations) you can use for this.
## Credits
Thanks to Bob Remeika and David Waller for the [original code](https://github.com/foobarfighter/safe-migrations).