Add add_column_safely and backfill_column_safely migration helpers#105
Add add_column_safely and backfill_column_safely migration helpers#105fatkodima wants to merge 1 commit intoankane:masterfrom
Conversation
e50e382 to
8bb6bd3
Compare
8bb6bd3 to
724bafa
Compare
| start_pk = result.first[primary_key] | ||
|
|
||
| loop do | ||
| finish_arel = table |
There was a problem hiding this comment.
Does this also need to add .where(table[column_name].not_eq(value)) to avoid reprocessing rows during a retry? Same question for the actual update a few lines down.
There was a problem hiding this comment.
I don't think so. This method updates rows in primary key order, so when restarting, the whole process will start from first (start_pk) unmatched row, which was last when update crashes, and move forward.
There was a problem hiding this comment.
That's true that the process will pickup where it left off before the restart but won't it unnecessarily update rows that were created after the column default was set but before the backfill process completes (i.e. rows were the DB filled in the column default on insert)? Might not be much of an issue in practice though.
There was a problem hiding this comment.
Might not be much of an issue in practice though.
Actually, this.
ankane
left a comment
There was a problem hiding this comment.
Hey @fatkodima, thanks for this and the other PRs that follow it. Will hold off on reviewing dependent ones until we get this merged.
Overall, looks great! I still need to dig into the Arel. I was hoping there'd to be a good way to dynamically create a model and use the existing code from the readme, but I'm not sure there's a good way to do this while reusing the existing connection.
lib/strong_migrations/util.rb
Outdated
| module StrongMigrations | ||
| module Util | ||
| def connection | ||
| ActiveRecord::Base.connection |
There was a problem hiding this comment.
Needs to use the migration connection rather than the ActiveRecord::Base connection.
test/migration_helpers_test.rb
Outdated
|
|
||
| class AddColumnSafely < TestMigration | ||
| def change | ||
| add_column_safely :users, :nice, :boolean, default: true, null: false |
There was a problem hiding this comment.
Let's use a different name for the new column to avoid needing to change the AddColumnDefaultSafe migration (keeping the changeset minimal)
| end | ||
| end | ||
|
|
||
| def backfill_column_safely(table_name, column_name, value, batch_size: 1000) |
There was a problem hiding this comment.
Let's remove batch size to keep things simple. Also, default should probably be 10,000 like with existing instructions.
| end | ||
|
|
||
| def add_column_safely(table_name, column_name, type, **options) | ||
| ensure_postgresql(__method__) |
There was a problem hiding this comment.
Think we can run the default add_column method when it's not Postgres.
| reversible do |dir| | ||
| dir.up do | ||
| transaction do | ||
| add_column(table_name, column_name, type, default: nil, **options) unless connection.column_exists?(table_name, column_name, type) |
There was a problem hiding this comment.
Don't think we should check for column_exists?, as the migration should fail if a user tries to add an existing column.
| change_column_default(table_name, column_name, default) | ||
| end | ||
|
|
||
| default_after_type_cast = connection.type_cast(default) |
There was a problem hiding this comment.
What's the reason for typecasting?
|
|
||
| start_arel = table | ||
| .project(table[primary_key]) | ||
| .where(table[column_name].not_eq(value)) |
There was a problem hiding this comment.
Backfilling should only affect NULL columns. Would be good to include a test case for this situation (can do after initial PR if that's easier)
lib/strong_migrations.rb
Outdated
| add_column_default: | ||
| "Adding a column with a non-null default causes the entire table to be rewritten. | ||
| Instead, add the column without a default value, then change the default. | ||
| Instead, add the column without a default value, backfill and then change the default. |
There was a problem hiding this comment.
The default gets changed before backfilling.
| end | ||
|
|
||
| safety_assured { execute(update_arel.to_sql) } | ||
|
|
There was a problem hiding this comment.
Need to throttle - sleep(0.01) (may need a global option for this later since more powerful databases may need less throttling, but let's deal with that later)
724bafa to
d41728c
Compare
|
@ankane Thanks for review! Updated with your suggestions. |
0cb518e to
63da732
Compare
|
Updated with master. |
63da732 to
bc50b5a
Compare
|
@ankane would you like making another round of reviews on this? |
|
Hey @fatkodima, sorry for the delay. Still thinking on #111. |
|
Decided helper methods are better as a separate gem for now - see #111 |
No description provided.