Skip to content

Add change_column_safely migration helper#107

Closed
fatkodima wants to merge 3 commits intoankane:masterfrom
fatkodima:change_column_safely
Closed

Add change_column_safely migration helper#107
fatkodima wants to merge 3 commits intoankane:masterfrom
fatkodima:change_column_safely

Conversation

@fatkodima
Copy link
Contributor

This pr depends on (and consists of) changes from previous 2 prs, so should be merged after them.

Copy link
Contributor

@jturkel jturkel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've rolled similar infrastructure into our internal application development kit so it's so awesome to see this get pulled into strong_migrations! I'm not a maintainer on this gem but I added some comments on things we're doing slightly differently in our homegrown infrastructure.

reversible do |dir|
dir.up do
transaction do
add_column(table_name, column_name, type, default: nil, **options)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this only add the column if it doesn't already exist to properly handle retries since the entire transaction isn't wrapped in a transaction? We've hit issues with longer running migrations so we generally try to make them idempotent.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jturkel Thanks for your suggestions. Implemented them in #105

change_column_default(table_name, column_name, default)
end

default_after_type_cast = connection.type_cast(default)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How hard would it be to support default expression e.g. uuid_generate_v4()? We've needed to do that pretty often as we've migrated away from bigint identifiers (at least for what we externally expose).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not that hard, but it definitely will introduce some ugly code changes in backfill_column_safely method. Let's wait for more demand and implement it in separate PR, if needed.


table = Arel::Table.new(table_name)
count_arel = table.project(Arel.star.count.as("count"))
total = connection.exec_query(count_arel.to_sql).first["count"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've seen count(*) on big tables take a long time. Should we avoid this count query and just handle the start_arel returning a null result?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented in #105


primary_key = connection.primary_key(table_name)

start_arel = table
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following up on my previous comment about restartability of migrations, should we skip rows that already have a non-null value for column_name? It's not strictly required but can be a really helpful for long running migrations that get restarted due to a network blip, Rails process dying, etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented in #105


finish_result = connection.exec_query(finish_arel.to_sql).first

update_arel = Arel::UpdateManager.new
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our app sometimes updates multiple rows which makes migrations like this deadlock prone. We always force locks to be acquired in primary key order to avoid this. Not sure how common that practice is and whether or not it's worth folding into the gem to avoid deadlocks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't get it. Can you provide examples and elaborate a little more?

@ankane ankane added the waiting label May 12, 2020
@ankane
Copy link
Owner

ankane commented May 12, 2020

Decided helper methods are better as a separate gem for now - see #111

@ankane ankane closed this May 12, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants