Migrations
For the purposes of this page, a "migration" is a change to a Postgres database schema.
Rails scans a database's schema when ActiveRecord first connects, and it keeps its knowledge cached.
This means that schema changes (like adding or removing columns) need to be paired with an app reboot.
Not-particularly-recommended path
This path is probably most suitable if you're doing deep, backwards-incompatible changes to the database schema.
Write standard Rails migrations for your pull request.
Merge it.
Release your code.
Important: until you reach and complete step #5, you'll have new code running with an old database schema. Plan ahead for this part, and mitigate user impact however you can.
Run
bin/rake db:migrate
in the deployment environment.Restart the app. Wait for success.
Recommended path
This path is probably most suitable if you're doing things that can be done idempotently and in a way that won't break old application code.
Write migrations for your pull request, using idempotent raw SQL.
Merge the pull request containing the migrations.
Before releasing the code, run the migrations manually in the target environment using
cb psql $CLUSTER_ID --role application
. Verify that your db changes are working properly, and that the db continues to be healthy.⚠️ That
--role application
flag is important! The app itself connects using this role, and (for continuity/consistency/predictability) it needs to have ownership over the objects it uses. So, when you're manually running migrations, it's important to use the same role as the app itself -- i.e.application
.
Release your code changes, which gives you an app restart for free. Wait for success.
Run
bin/rake db:migrate
in the deployment environment. This is a semi-redundant step: you already manually ran the changes in step 3. Doing it this time won't fail, because you wrote idempotent migrations. Doing it this time also updates the private Rails bookkeeping table calledschema_migrations
, declaring at the Rails level that the database schema is up to date.You can use
fly console -a $FLY_APP_NAME -s
to open up a console on an already-running machine. (You can also open a console using a different build/image! For that, see Unusual consoles.)
Idempotence
Idempotent code is code that has side effects, but only creates those side effects once -- even if it's run more than once.
This is useful in database land! Idempotent migration code can be run more than once without errors. It's like saying "hey, make this change, but only if it wasn't already made".
Rails has some conveniences for this -- look for if_exists:
and if_not_exists:
in https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html.
However, it can be useful to stick to raw SQL, for easy pasting into psql.
For functions, use CREATE OR REPLACE FUNCTION
. Functions are stateless (unlike tables and indexes!), so it's okay to have the function created ahead of time by a human, and then recreated during the actual Rails migration execution. This still counts as idempotent behavior, because the function's existence and behavior remain consistent even when the migration SQL is re-run.
Example migrations
Last updated