Rename a Django Model Field Without Downtime
Rename a Django model field or column without breaking running code during rolling deploys: use db_column to rename in Python only, or expand and contract with a new column, dual writes, backfill, and removal across several releases.
Problem statement
You rename Customer.phone_no to Customer.phone and makemigrations generates a RenameField. During a rolling deploy, the migration renames the column while old application instances are still running and querying phone_no, so they fail with column "phone_no" does not exist until the rollout finishes. The same happens to Celery workers on the old release. Renaming without downtime means never making the database incompatible with code that is still running.
- If you only want a nicer Python name, keep the column:
phone = models.CharField(db_column="phone_no", ...). No database change. - To really rename the column, expand and contract over several releases: add the new column, write both, backfill, switch reads, stop writing the old one, drop it.
- Never run
RenameFieldon a busy table during a rolling deploy unless you accept errors or use a maintenance window. - Rename models similarly with
db_tableto keep the existing table name. - Check every consumer of the column: raw SQL, reports, other services, analytics.
Step-by-step solution
1. Rename only in Python with db_column
class Customer(models.Model):
phone = models.CharField(max_length=32, db_column="phone_no")
Generate a migration; Django produces a RenameField plus an AlterField that, combined, change nothing in the database (check with sqlmigrate, which should show no SQL or only a no-op). Old code reading phone_no and new code reading phone both work, because the column name is unchanged. This covers most renames, which are about code readability.
2. Expand: add the new column and write both
When the column name itself must change (shared database, external consumers, naming standards), release 1 adds the new column:
class Customer(models.Model):
phone_no = models.CharField(max_length=32)
phone = models.CharField(max_length=32, null=True)
def save(self, *args, **kwargs):
self.phone = self.phone_no if self.phone is None else self.phone
super().save(*args, **kwargs)
Write to both columns wherever the field is set (forms, serializers, update() calls). A database trigger can keep them in sync too, which also covers writes from outside Django.
3. Backfill and switch reads
Fill the new column for existing rows in batches (UPDATE customers_customer SET phone = phone_no WHERE phone IS NULL AND id BETWEEN ...). Then release 2 reads from phone, makes it non-nullable once verified, and continues writing phone_no so that release 1 instances (still running during the rollout) see correct data.
4. Contract: stop writing and drop
Release 3 removes phone_no from the model while keeping the column in the database. Django would generate a RemoveField; to keep the column during this release, use SeparateDatabaseAndState:
migrations.SeparateDatabaseAndState(
state_operations=[migrations.RemoveField("customer", "phone_no")],
database_operations=[],
)
Release 4, after release 3 is fully rolled out, drops the column with RunSQL("ALTER TABLE customers_customer DROP COLUMN phone_no"). See Remove a Django Field Without Downtime.
5. Renaming models and tables
RenameModel renames the table too. To rename only the Python class, set db_table = "customers_client" (the old table name) on the renamed model. Foreign key columns (client_id) keep their names unless you rename those fields as well, which follows the same patterns.
Explanation
A rolling deploy runs old and new versions side by side, both against the same database. Any schema change must therefore work with both versions. Renaming a column is an atomic, incompatible change for whichever version uses the other name. Mapping a new Python name onto the old column avoids the change entirely; expand and contract replaces one incompatible step with several compatible ones, at the cost of more releases and temporary duplicate data.
Edge cases / notes
- Indexes and constraints. Recreate indexes and constraints on the new column before switching reads, concurrently on large tables.
- Serializers and APIs. API field names are a separate contract; keep the old API name or version the API.
- Celery tasks in queues. Tasks queued by old code may carry the old field name in arguments; handle both during the transition.
Next steps
- Finish the contract step with Remove a Django Field Without Downtime.
- See the bigger picture in Zero-Downtime Django Deployment.
- Review migrations with the Django Migration Review Checklist.
FAQ
Is RenameField safe in production?
The SQL is fast, but old code still running during a rolling deploy breaks until the rollout completes.
How do I rename a field without changing the database?
Rename it in Python and set db_column to the existing column name.
How many releases does a real column rename take?
Typically three or four: add and dual-write, switch reads, stop writing, drop the old column.
Does this apply to renaming models?
Yes. Use db_table to keep the old table name, or expand and contract if the table name must change.