Hi all,
I’m curious to know how the community are tackling backwards compatible migrations to enable stuff like blue-green deployments.
From my understanding the workflow for the common operations of adding, deleting and renaming columns is something like this:
Adding a columns
Adding a column can be made in one release if the column is nullable or have a default value with the caveat that it has to be a database default and not a Django ORM-default. This can be solved by overriding the original migration and using RunSQL
:
RunSQL(
‘ALTER TABLE foo ADD COLUMN quantity int DEFAULT 1 NOT NULL’,
‘ALTER TABLE foo DROP COLUMN quantity’,
state_operations=[
migrations.AddField(
model_name=‘foo’,
name=‘quantity’,
field=models.IntegerField(default=1, null=False),
),
],
)
I’m not sure if SeparateDatabaseAndState
could / should be used instead of RunSQL
.
Adding NOT NULL
columns where a database default isn’t applicable and a data migration is needed can be made in one release if made in three steps in the same migration. First add the column as nullable, run the data migration and then set the column as NOT NULL
. This also requires the migration to be run in one transaction with atomic = True
(not supported by all backends). Otherwise it should be done in two releases where the last release sets the column as NOT NULL
.
Deleting columns
Deleting a column is made in two releases:
- Make the field nullable and set it as
editable=False
and remove all usage of it while keeping the field in the model. - Delete the field.
Renaming columns
Renaming a column is made in three releases:
- Add the field with the new name as nullable or with a database default as described in “Adding columns”. Update any usage of the old field to also write value to the new field.
- Run a data migration to copy values to the new field if not set. Make the old field nullable and set it as
editable=False
and remove all usage of it while keeping the field in the model. The new field can now be set asNOT NULL
. - Delete the old field.
Dropping tables
Dropping a table is made in two releases:
- Remove all usage of the table to be removed.
- Delete the table.
Note that I’m assuming that the database is PostgreSQL >= 11 where database defaults can be added for new columns without rewriting the table.
Am I missing something or do you folks have any recommendations for making these processes as smooth as possible? One part that feels a bit sketchy is keeping a field that is pending deletion on the model. It would be quite easy to miss a place in the code where that field still is used.