strong_migrations 0.6.5 → 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +28 -0
- data/LICENSE.txt +1 -1
- data/README.md +189 -138
- data/lib/generators/strong_migrations/install_generator.rb +28 -0
- data/lib/generators/strong_migrations/templates/initializer.rb.tt +22 -0
- data/lib/strong_migrations.rb +43 -50
- data/lib/strong_migrations/checker.rb +139 -29
- data/lib/strong_migrations/railtie.rb +0 -4
- data/lib/strong_migrations/version.rb +1 -1
- data/lib/tasks/strong_migrations.rake +0 -6
- metadata +4 -3
- data/lib/strong_migrations/migration_helpers.rb +0 -117
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 947dda0ab2fa336611335b258b4eae34e7b60f38083e162fb6bf80d2237ce819
|
4
|
+
data.tar.gz: 6744411d6b2a33d5ee54b910ef60652fcb992cbb700a59a31da3eb48c2cb5322
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3e1b51f910a02835ab0612f8be9714dce1dd1024acc3745a4465588447e6e12d8a4bdb2903c66f9c5f8ccc67bb6877ec231ad5979b304aff0ee34e5e4bd1f0a2
|
7
|
+
data.tar.gz: e739903401b7787ad66a929ff1f67b0e0b4ff9366571d25ad0349f680bf013adad95be38a1fc71c4f4039174b4ef7ee30764a9786e6b8677597a0d2bd6434749
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,31 @@
|
|
1
|
+
## 0.7.1 (2020-07-27)
|
2
|
+
|
3
|
+
- Added `target_version` option to replace database-specific options
|
4
|
+
|
5
|
+
## 0.7.0 (2020-07-22)
|
6
|
+
|
7
|
+
- Added `check_down` option
|
8
|
+
- Added check for `change_column` with `null: false`
|
9
|
+
- Added check for `validate_foreign_key`
|
10
|
+
- Improved error messages
|
11
|
+
- Made auto analyze less verbose in Postgres
|
12
|
+
- Decreasing the length limit of a `varchar` column or adding a limit is not safe in Postgres
|
13
|
+
- Removed safety checks for `db` rake tasks (Rails 5+ handles this)
|
14
|
+
|
15
|
+
## 0.6.8 (2020-05-13)
|
16
|
+
|
17
|
+
- `change_column_null` on a column with a `NOT NULL` constraint is safe in Postgres 12+
|
18
|
+
|
19
|
+
## 0.6.7 (2020-05-13)
|
20
|
+
|
21
|
+
- Improved comments in initializer
|
22
|
+
- Fixed string timeouts for Postgres
|
23
|
+
|
24
|
+
## 0.6.6 (2020-05-08)
|
25
|
+
|
26
|
+
- Added warnings for missing and long lock timeouts
|
27
|
+
- Added install generator
|
28
|
+
|
1
29
|
## 0.6.5 (2020-05-06)
|
2
30
|
|
3
31
|
- Fixed deprecation warnings with Ruby 2.7
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -18,7 +18,42 @@ Add this line to your application’s Gemfile:
|
|
18
18
|
gem 'strong_migrations'
|
19
19
|
```
|
20
20
|
|
21
|
-
|
21
|
+
And run:
|
22
|
+
|
23
|
+
```sh
|
24
|
+
bundle install
|
25
|
+
rails generate strong_migrations:install
|
26
|
+
```
|
27
|
+
|
28
|
+
Strong Migrations sets a long statement timeout for migrations so you can set a [short statement timeout](#app-timeouts) for your application.
|
29
|
+
|
30
|
+
## How It Works
|
31
|
+
|
32
|
+
When you run a migration that’s potentially dangerous, you’ll see an error message like:
|
33
|
+
|
34
|
+
```txt
|
35
|
+
=== Dangerous operation detected #strong_migrations ===
|
36
|
+
|
37
|
+
Active Record caches attributes, which causes problems
|
38
|
+
when removing columns. Be sure to ignore the column:
|
39
|
+
|
40
|
+
class User < ApplicationRecord
|
41
|
+
self.ignored_columns = ["name"]
|
42
|
+
end
|
43
|
+
|
44
|
+
Deploy the code, then wrap this step in a safety_assured { ... } block.
|
45
|
+
|
46
|
+
class RemoveColumn < ActiveRecord::Migration[6.0]
|
47
|
+
def change
|
48
|
+
safety_assured { remove_column :users, :name }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
An operation is classified as dangerous if it either:
|
54
|
+
|
55
|
+
- Blocks reads or writes for more than a few seconds (after a lock is acquired)
|
56
|
+
- Has a good chance of causing application errors
|
22
57
|
|
23
58
|
## Checks
|
24
59
|
|
@@ -27,21 +62,19 @@ Potentially dangerous operations:
|
|
27
62
|
- [removing a column](#removing-a-column)
|
28
63
|
- [adding a column with a default value](#adding-a-column-with-a-default-value)
|
29
64
|
- [backfilling data](#backfilling-data)
|
30
|
-
- [changing the type of a column](#
|
31
|
-
- [renaming a column](#renaming-
|
65
|
+
- [changing the type of a column](#changing-the-type-of-a-column)
|
66
|
+
- [renaming a column](#renaming-a-column)
|
32
67
|
- [renaming a table](#renaming-a-table)
|
33
68
|
- [creating a table with the force option](#creating-a-table-with-the-force-option)
|
34
|
-
- [
|
69
|
+
- [setting NOT NULL on an existing column](#setting-not-null-on-an-existing-column)
|
35
70
|
- [executing SQL directly](#executing-SQL-directly)
|
36
71
|
|
37
72
|
Postgres-specific checks:
|
38
73
|
|
39
|
-
- [adding an index non-concurrently](#adding-an-index)
|
40
|
-
- [removing an index non-concurrently](#removing-an-index)
|
74
|
+
- [adding an index non-concurrently](#adding-an-index-non-concurrently)
|
41
75
|
- [adding a reference](#adding-a-reference)
|
42
76
|
- [adding a foreign key](#adding-a-foreign-key)
|
43
77
|
- [adding a json column](#adding-a-json-column)
|
44
|
-
- [setting NOT NULL on an existing column](#setting-not-null-on-an-existing-column)
|
45
78
|
|
46
79
|
Best practices:
|
47
80
|
|
@@ -53,7 +86,7 @@ You can also add [custom checks](#custom-checks) or [disable specific checks](#d
|
|
53
86
|
|
54
87
|
#### Bad
|
55
88
|
|
56
|
-
|
89
|
+
Active Record caches database columns at runtime, so if you drop a column, it can cause exceptions until your app reboots.
|
57
90
|
|
58
91
|
```ruby
|
59
92
|
class RemoveSomeColumnFromUsers < ActiveRecord::Migration[6.0]
|
@@ -65,7 +98,7 @@ end
|
|
65
98
|
|
66
99
|
#### Good
|
67
100
|
|
68
|
-
1. Tell
|
101
|
+
1. Tell Active Record to ignore the column from its cache
|
69
102
|
|
70
103
|
```ruby
|
71
104
|
class User < ApplicationRecord
|
@@ -88,11 +121,9 @@ end
|
|
88
121
|
|
89
122
|
### Adding a column with a default value
|
90
123
|
|
91
|
-
Note: This operation is safe in Postgres 11+, MySQL 8.0.12+, and MariaDB 10.3.2+.
|
92
|
-
|
93
124
|
#### Bad
|
94
125
|
|
95
|
-
|
126
|
+
In earlier versions of Postgres, MySQL, and MariaDB, adding a column with a default value to an existing table causes the entire table to be rewritten. During this time, reads and writes are blocked in Postgres, and writes are blocked in MySQL and MariaDB.
|
96
127
|
|
97
128
|
```ruby
|
98
129
|
class AddSomeColumnToUsers < ActiveRecord::Migration[6.0]
|
@@ -102,6 +133,8 @@ class AddSomeColumnToUsers < ActiveRecord::Migration[6.0]
|
|
102
133
|
end
|
103
134
|
```
|
104
135
|
|
136
|
+
In Postgres 11+, MySQL 8.0.12+, and MariaDB 10.3.2+, this no longer requires a table rewrite and is safe.
|
137
|
+
|
105
138
|
#### Good
|
106
139
|
|
107
140
|
Instead, add the column without a default value, then change the default.
|
@@ -125,7 +158,7 @@ See the next section for how to backfill.
|
|
125
158
|
|
126
159
|
#### Bad
|
127
160
|
|
128
|
-
|
161
|
+
Active Record creates a transaction around each migration, and backfilling in the same transaction that alters a table keeps the table locked for the [duration of the backfill](https://wework.github.io/data/2015/11/05/add-columns-with-default-values-to-large-tables-in-rails-postgres/).
|
129
162
|
|
130
163
|
```ruby
|
131
164
|
class AddSomeColumnToUsers < ActiveRecord::Migration[6.0]
|
@@ -155,19 +188,11 @@ class BackfillSomeColumn < ActiveRecord::Migration[6.0]
|
|
155
188
|
end
|
156
189
|
```
|
157
190
|
|
158
|
-
###
|
191
|
+
### Changing the type of a column
|
159
192
|
|
160
193
|
#### Bad
|
161
194
|
|
162
|
-
|
163
|
-
class RenameSomeColumn < ActiveRecord::Migration[6.0]
|
164
|
-
def change
|
165
|
-
rename_column :users, :some_column, :new_name
|
166
|
-
end
|
167
|
-
end
|
168
|
-
```
|
169
|
-
|
170
|
-
or
|
195
|
+
Changing the type of a column causes the entire table to be rewritten. During this time, reads and writes are blocked in Postgres, and writes are blocked in MySQL and MariaDB.
|
171
196
|
|
172
197
|
```ruby
|
173
198
|
class ChangeSomeColumnType < ActiveRecord::Migration[6.0]
|
@@ -177,17 +202,44 @@ class ChangeSomeColumnType < ActiveRecord::Migration[6.0]
|
|
177
202
|
end
|
178
203
|
```
|
179
204
|
|
180
|
-
A few changes are safe in Postgres:
|
205
|
+
A few changes don’t require a table rewrite (and are safe) in Postgres:
|
181
206
|
|
182
|
-
-
|
207
|
+
- Increasing the length limit of a `varchar` column (or removing the limit)
|
208
|
+
- Changing a `varchar` column to a `text` column
|
209
|
+
- Changing a `text` column to a `varchar` column with no length limit
|
183
210
|
- Increasing the precision of a `decimal` or `numeric` column
|
184
211
|
- Making a `decimal` or `numeric` column unconstrained
|
185
212
|
- Changing between `timestamp` and `timestamptz` columns when session time zone is UTC in Postgres 12+
|
186
213
|
|
187
214
|
And a few in MySQL and MariaDB:
|
188
215
|
|
189
|
-
- Increasing the length of a `varchar` column from under 255 up to 255
|
190
|
-
- Increasing the length of a `varchar` column over 255
|
216
|
+
- Increasing the length limit of a `varchar` column from under 255 up to 255
|
217
|
+
- Increasing the length limit of a `varchar` column from over 255 to the max limit
|
218
|
+
|
219
|
+
#### Good
|
220
|
+
|
221
|
+
A safer approach is to:
|
222
|
+
|
223
|
+
1. Create a new column
|
224
|
+
2. Write to both columns
|
225
|
+
3. Backfill data from the old column to the new column
|
226
|
+
4. Move reads from the old column to the new column
|
227
|
+
5. Stop writing to the old column
|
228
|
+
6. Drop the old column
|
229
|
+
|
230
|
+
### Renaming a column
|
231
|
+
|
232
|
+
#### Bad
|
233
|
+
|
234
|
+
Renaming a column that’s in use will cause errors in your application.
|
235
|
+
|
236
|
+
```ruby
|
237
|
+
class RenameSomeColumn < ActiveRecord::Migration[6.0]
|
238
|
+
def change
|
239
|
+
rename_column :users, :some_column, :new_name
|
240
|
+
end
|
241
|
+
end
|
242
|
+
```
|
191
243
|
|
192
244
|
#### Good
|
193
245
|
|
@@ -204,6 +256,8 @@ A safer approach is to:
|
|
204
256
|
|
205
257
|
#### Bad
|
206
258
|
|
259
|
+
Renaming a table that’s in use will cause errors in your application.
|
260
|
+
|
207
261
|
```ruby
|
208
262
|
class RenameUsersToCustomers < ActiveRecord::Migration[6.0]
|
209
263
|
def change
|
@@ -253,33 +307,57 @@ class CreateUsers < ActiveRecord::Migration[6.0]
|
|
253
307
|
end
|
254
308
|
```
|
255
309
|
|
256
|
-
|
310
|
+
If you intend to drop an existing table, run `drop_table` first.
|
311
|
+
|
312
|
+
### Setting NOT NULL on an existing column
|
257
313
|
|
258
314
|
#### Bad
|
259
315
|
|
260
|
-
|
316
|
+
Setting `NOT NULL` on an existing column blocks reads and writes while the every row is checked.
|
261
317
|
|
262
318
|
```ruby
|
263
|
-
class
|
319
|
+
class SetSomeColumnNotNull < ActiveRecord::Migration[6.0]
|
264
320
|
def change
|
265
|
-
change_column_null :users, :some_column, false
|
321
|
+
change_column_null :users, :some_column, false
|
266
322
|
end
|
267
323
|
end
|
268
324
|
```
|
269
325
|
|
270
|
-
#### Good
|
326
|
+
#### Good - Postgres
|
327
|
+
|
328
|
+
Instead, add a check constraint:
|
329
|
+
|
330
|
+
```ruby
|
331
|
+
class SetSomeColumnNotNull < ActiveRecord::Migration[6.0]
|
332
|
+
def change
|
333
|
+
safety_assured do
|
334
|
+
execute 'ALTER TABLE "users" ADD CONSTRAINT "users_some_column_null" CHECK ("some_column" IS NOT NULL) NOT VALID'
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
338
|
+
```
|
271
339
|
|
272
|
-
|
340
|
+
Then validate it in a separate migration. A `NOT NULL` check constraint is [functionally equivalent](https://medium.com/doctolib/adding-a-not-null-constraint-on-pg-faster-with-minimal-locking-38b2c00c4d1c) to setting `NOT NULL` on the column, but it won’t show up in `schema.rb`. In Postgres 12+, once the check constraint is validated, you can safely set `NOT NULL` on the column and drop the check constraint.
|
273
341
|
|
274
342
|
```ruby
|
275
|
-
class
|
343
|
+
class ValidateSomeColumnNotNull < ActiveRecord::Migration[6.0]
|
276
344
|
def change
|
345
|
+
safety_assured do
|
346
|
+
execute 'ALTER TABLE "users" VALIDATE CONSTRAINT "users_some_column_null"'
|
347
|
+
end
|
348
|
+
|
349
|
+
# in Postgres 12+, you can then safely set NOT NULL on the column
|
277
350
|
change_column_null :users, :some_column, false
|
351
|
+
safety_assured do
|
352
|
+
execute 'ALTER TABLE "users" DROP CONSTRAINT "users_some_column_null"'
|
353
|
+
end
|
278
354
|
end
|
279
355
|
end
|
280
356
|
```
|
281
357
|
|
282
|
-
|
358
|
+
#### Good - MySQL and MariaDB
|
359
|
+
|
360
|
+
[Let us know](https://github.com/ankane/strong_migrations/issues/new) if you have a safe way to do this.
|
283
361
|
|
284
362
|
### Executing SQL directly
|
285
363
|
|
@@ -293,11 +371,11 @@ class ExecuteSQL < ActiveRecord::Migration[6.0]
|
|
293
371
|
end
|
294
372
|
```
|
295
373
|
|
296
|
-
### Adding an index
|
374
|
+
### Adding an index non-concurrently
|
297
375
|
|
298
376
|
#### Bad
|
299
377
|
|
300
|
-
In Postgres, adding an index non-concurrently
|
378
|
+
In Postgres, adding an index non-concurrently blocks writes.
|
301
379
|
|
302
380
|
```ruby
|
303
381
|
class AddSomeIndexToUsers < ActiveRecord::Migration[6.0]
|
@@ -329,41 +407,11 @@ With [gindex](https://github.com/ankane/gindex), you can generate an index migra
|
|
329
407
|
rails g index table column
|
330
408
|
```
|
331
409
|
|
332
|
-
### Removing an index
|
333
|
-
|
334
|
-
Note: This check is [opt-in](#opt-in-checks).
|
335
|
-
|
336
|
-
#### Bad
|
337
|
-
|
338
|
-
In Postgres, removing an index non-concurrently locks the table for a brief period.
|
339
|
-
|
340
|
-
```ruby
|
341
|
-
class RemoveSomeIndexFromUsers < ActiveRecord::Migration[6.0]
|
342
|
-
def change
|
343
|
-
remove_index :users, :some_column
|
344
|
-
end
|
345
|
-
end
|
346
|
-
```
|
347
|
-
|
348
|
-
#### Good
|
349
|
-
|
350
|
-
Remove indexes concurrently.
|
351
|
-
|
352
|
-
```ruby
|
353
|
-
class RemoveSomeIndexFromUsers < ActiveRecord::Migration[6.0]
|
354
|
-
disable_ddl_transaction!
|
355
|
-
|
356
|
-
def change
|
357
|
-
remove_index :users, column: :some_column, algorithm: :concurrently
|
358
|
-
end
|
359
|
-
end
|
360
|
-
```
|
361
|
-
|
362
410
|
### Adding a reference
|
363
411
|
|
364
412
|
#### Bad
|
365
413
|
|
366
|
-
Rails adds an index non-concurrently to references by default, which
|
414
|
+
Rails adds an index non-concurrently to references by default, which blocks writes in Postgres.
|
367
415
|
|
368
416
|
```ruby
|
369
417
|
class AddReferenceToUsers < ActiveRecord::Migration[6.0]
|
@@ -391,7 +439,7 @@ end
|
|
391
439
|
|
392
440
|
#### Bad
|
393
441
|
|
394
|
-
In Postgres,
|
442
|
+
In Postgres, adding a foreign key blocks writes on both tables.
|
395
443
|
|
396
444
|
```ruby
|
397
445
|
class AddForeignKeyOnUsers < ActiveRecord::Migration[6.0]
|
@@ -413,7 +461,7 @@ end
|
|
413
461
|
|
414
462
|
#### Good
|
415
463
|
|
416
|
-
|
464
|
+
Add the foreign key without validating existing rows, then validate them in a separate migration.
|
417
465
|
|
418
466
|
For Rails 5.2+, use:
|
419
467
|
|
@@ -425,7 +473,7 @@ class AddForeignKeyOnUsers < ActiveRecord::Migration[6.0]
|
|
425
473
|
end
|
426
474
|
```
|
427
475
|
|
428
|
-
Then
|
476
|
+
Then:
|
429
477
|
|
430
478
|
```ruby
|
431
479
|
class ValidateForeignKeyOnUsers < ActiveRecord::Migration[6.0]
|
@@ -447,7 +495,7 @@ class AddForeignKeyOnUsers < ActiveRecord::Migration[5.1]
|
|
447
495
|
end
|
448
496
|
```
|
449
497
|
|
450
|
-
Then
|
498
|
+
Then:
|
451
499
|
|
452
500
|
```ruby
|
453
501
|
class ValidateForeignKeyOnUsers < ActiveRecord::Migration[5.1]
|
@@ -463,7 +511,7 @@ end
|
|
463
511
|
|
464
512
|
#### Bad
|
465
513
|
|
466
|
-
In Postgres, there’s no equality operator for the `json` column type, which can cause errors for existing `SELECT DISTINCT` queries.
|
514
|
+
In Postgres, there’s no equality operator for the `json` column type, which can cause errors for existing `SELECT DISTINCT` queries in your application.
|
467
515
|
|
468
516
|
```ruby
|
469
517
|
class AddPropertiesToUsers < ActiveRecord::Migration[6.0]
|
@@ -485,48 +533,6 @@ class AddPropertiesToUsers < ActiveRecord::Migration[6.0]
|
|
485
533
|
end
|
486
534
|
```
|
487
535
|
|
488
|
-
### Setting NOT NULL on an existing column
|
489
|
-
|
490
|
-
#### Bad
|
491
|
-
|
492
|
-
In Postgres, setting `NOT NULL` on an existing column requires an `AccessExclusiveLock`, which is expensive on large tables.
|
493
|
-
|
494
|
-
```ruby
|
495
|
-
class SetSomeColumnNotNull < ActiveRecord::Migration[6.0]
|
496
|
-
def change
|
497
|
-
change_column_null :users, :some_column, false
|
498
|
-
end
|
499
|
-
end
|
500
|
-
```
|
501
|
-
|
502
|
-
#### Good
|
503
|
-
|
504
|
-
Instead, add a constraint:
|
505
|
-
|
506
|
-
```ruby
|
507
|
-
class SetSomeColumnNotNull < ActiveRecord::Migration[6.0]
|
508
|
-
def change
|
509
|
-
safety_assured do
|
510
|
-
execute 'ALTER TABLE "users" ADD CONSTRAINT "users_some_column_null" CHECK ("some_column" IS NOT NULL) NOT VALID'
|
511
|
-
end
|
512
|
-
end
|
513
|
-
end
|
514
|
-
```
|
515
|
-
|
516
|
-
Then validate it in a separate migration.
|
517
|
-
|
518
|
-
```ruby
|
519
|
-
class ValidateSomeColumnNotNull < ActiveRecord::Migration[6.0]
|
520
|
-
def change
|
521
|
-
safety_assured do
|
522
|
-
execute 'ALTER TABLE "users" VALIDATE CONSTRAINT "users_some_column_null"'
|
523
|
-
end
|
524
|
-
end
|
525
|
-
end
|
526
|
-
```
|
527
|
-
|
528
|
-
Note: This is not 100% the same as `NOT NULL` column constraint. Here’s a [good explanation](https://medium.com/doctolib/adding-a-not-null-constraint-on-pg-faster-with-minimal-locking-38b2c00c4d1c).
|
529
|
-
|
530
536
|
### Keeping non-unique indexes to three columns or less
|
531
537
|
|
532
538
|
#### Bad
|
@@ -587,16 +593,12 @@ Note: Since `remove_column` always requires a `safety_assured` block, it’s not
|
|
587
593
|
|
588
594
|
## Opt-in Checks
|
589
595
|
|
590
|
-
|
591
|
-
|
592
|
-
```ruby
|
593
|
-
StrongMigrations.enable_check(:remove_index)
|
594
|
-
```
|
596
|
+
### Removing an index non-concurrently
|
595
597
|
|
596
|
-
|
598
|
+
Postgres supports removing indexes concurrently, but removing them non-concurrently shouldn’t be an issue for most applications. You can enable this check with:
|
597
599
|
|
598
600
|
```ruby
|
599
|
-
StrongMigrations.enable_check(:remove_index
|
601
|
+
StrongMigrations.enable_check(:remove_index)
|
600
602
|
```
|
601
603
|
|
602
604
|
## Disable Checks
|
@@ -609,6 +611,14 @@ StrongMigrations.disable_check(:add_index)
|
|
609
611
|
|
610
612
|
Check the [source code](https://github.com/ankane/strong_migrations/blob/master/lib/strong_migrations.rb) for the list of keys.
|
611
613
|
|
614
|
+
## Down Migrations / Rollbacks
|
615
|
+
|
616
|
+
By default, checks are disabled when migrating down. Enable them with:
|
617
|
+
|
618
|
+
```ruby
|
619
|
+
StrongMigrations.check_down = true
|
620
|
+
```
|
621
|
+
|
612
622
|
## Custom Messages
|
613
623
|
|
614
624
|
To customize specific messages, create an initializer with:
|
@@ -619,26 +629,69 @@ StrongMigrations.error_messages[:add_column_default] = "Your custom instructions
|
|
619
629
|
|
620
630
|
Check the [source code](https://github.com/ankane/strong_migrations/blob/master/lib/strong_migrations.rb) for the list of keys.
|
621
631
|
|
622
|
-
## Timeouts
|
632
|
+
## Migration Timeouts
|
623
633
|
|
624
|
-
It’s
|
634
|
+
It’s extremely important to set a short lock timeout for migrations. This way, if a migration can’t acquire a lock in a timely manner, other statements won’t be stuck behind it. We also recommend setting a long statement timeout so migrations can run for a while.
|
625
635
|
|
626
636
|
Create `config/initializers/strong_migrations.rb` with:
|
627
637
|
|
628
638
|
```ruby
|
629
|
-
StrongMigrations.statement_timeout = 1.hour
|
630
639
|
StrongMigrations.lock_timeout = 10.seconds
|
640
|
+
StrongMigrations.statement_timeout = 1.hour
|
631
641
|
```
|
632
642
|
|
633
643
|
Or set the timeouts directly on the database user that runs migrations. For Postgres, use:
|
634
644
|
|
635
645
|
```sql
|
636
|
-
ALTER ROLE myuser SET statement_timeout = '1h';
|
637
646
|
ALTER ROLE myuser SET lock_timeout = '10s';
|
647
|
+
ALTER ROLE myuser SET statement_timeout = '1h';
|
638
648
|
```
|
639
649
|
|
640
650
|
Note: If you use PgBouncer in transaction mode, you must set timeouts on the database user.
|
641
651
|
|
652
|
+
## App Timeouts
|
653
|
+
|
654
|
+
We recommend adding timeouts to `config/database.yml` to prevent connections from hanging and individual queries from taking up too many resources in controllers, jobs, the Rails console, and other places.
|
655
|
+
|
656
|
+
For Postgres:
|
657
|
+
|
658
|
+
```yml
|
659
|
+
production:
|
660
|
+
connect_timeout: 5
|
661
|
+
variables:
|
662
|
+
statement_timeout: 15s
|
663
|
+
lock_timeout: 10s
|
664
|
+
```
|
665
|
+
|
666
|
+
Note: If you use PgBouncer in transaction mode, you must set the statement and lock timeouts on the database user as shown above.
|
667
|
+
|
668
|
+
For MySQL:
|
669
|
+
|
670
|
+
```yml
|
671
|
+
production:
|
672
|
+
connect_timeout: 5
|
673
|
+
read_timeout: 5
|
674
|
+
write_timeout: 5
|
675
|
+
variables:
|
676
|
+
max_execution_time: 15000 # ms
|
677
|
+
lock_wait_timeout: 10 # sec
|
678
|
+
|
679
|
+
```
|
680
|
+
|
681
|
+
For MariaDB:
|
682
|
+
|
683
|
+
```yml
|
684
|
+
production:
|
685
|
+
connect_timeout: 5
|
686
|
+
read_timeout: 5
|
687
|
+
write_timeout: 5
|
688
|
+
variables:
|
689
|
+
max_statement_time: 15 # sec
|
690
|
+
lock_wait_timeout: 10 # sec
|
691
|
+
```
|
692
|
+
|
693
|
+
For HTTP connections, Redis, and other services, check out [this guide](https://github.com/ankane/the-ultimate-guide-to-ruby-timeouts).
|
694
|
+
|
642
695
|
## Existing Migrations
|
643
696
|
|
644
697
|
To mark migrations as safe that were created before installing this gem, create an initializer with:
|
@@ -651,14 +704,14 @@ Use the version from your latest migration.
|
|
651
704
|
|
652
705
|
## Target Version
|
653
706
|
|
654
|
-
If your development database version is different from production, you can specify the production version so the right checks
|
707
|
+
If your development database version is different from production, you can specify the production version so the right checks run in development.
|
655
708
|
|
656
709
|
```ruby
|
657
|
-
StrongMigrations.
|
658
|
-
StrongMigrations.target_mysql_version = "8.0.12"
|
659
|
-
StrongMigrations.target_mariadb_version = "10.3.2"
|
710
|
+
StrongMigrations.target_version = 10 # or "8.0.12", "10.3.2", etc
|
660
711
|
```
|
661
712
|
|
713
|
+
The major version works well for Postgres, while the full version is recommended for MySQL and MariaDB.
|
714
|
+
|
662
715
|
For safety, this option only affects development and test environments. In other environments, the actual server version is always used.
|
663
716
|
|
664
717
|
## Analyze Tables
|
@@ -686,22 +739,20 @@ Columns can flip order in `db/schema.rb` when you have multiple developers. One
|
|
686
739
|
task "db:schema:dump": "strong_migrations:alphabetize_columns"
|
687
740
|
```
|
688
741
|
|
689
|
-
## Dangerous Tasks
|
690
|
-
|
691
|
-
For safety, dangerous database tasks are disabled in production - `db:drop`, `db:reset`, `db:schema:load`, and `db:structure:load`. To get around this, use:
|
692
|
-
|
693
|
-
```sh
|
694
|
-
SAFETY_ASSURED=1 rails db:drop
|
695
|
-
```
|
696
|
-
|
697
742
|
## Permissions
|
698
743
|
|
699
744
|
We recommend using a [separate database user](https://ankane.org/postgres-users) for migrations when possible so you don’t need to grant your app user permission to alter tables.
|
700
745
|
|
746
|
+
## Smaller Projects
|
747
|
+
|
748
|
+
You probably don’t need this gem for smaller projects, as operations that are unsafe at scale can be perfectly safe on smaller, low-traffic tables.
|
749
|
+
|
701
750
|
## Additional Reading
|
702
751
|
|
703
752
|
- [Rails Migrations with No Downtime](https://pedro.herokuapp.com/past/2011/7/13/rails_migrations_with_no_downtime/)
|
704
753
|
- [PostgreSQL at Scale: Database Schema Changes Without Downtime](https://medium.com/braintree-product-technology/postgresql-at-scale-database-schema-changes-without-downtime-20d3749ed680)
|
754
|
+
- [An Overview of DDL Algorithms in MySQL](https://mydbops.wordpress.com/2020/03/04/an-overview-of-ddl-algorithms-in-mysql-covers-mysql-8/)
|
755
|
+
- [MariaDB InnoDB Online DDL Overview](https://mariadb.com/kb/en/innodb-online-ddl-overview/)
|
705
756
|
|
706
757
|
## Credits
|
707
758
|
|