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.

Why a rename breaks deploysMigration runscolumn renamedOld pods still runquery old nameErrorscolumn missingRollout endsnew code worksDuring a rolling deploy, old and new code run together against one schema.
  1. If you only want a nicer Python name, keep the column: phone = models.CharField(db_column="phone_no", ...). No database change.
  2. 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.
  3. Never run RenameField on a busy table during a rolling deploy unless you accept errors or use a maintenance window.
  4. Rename models similarly with db_table to keep the existing table name.
  5. 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.

release by release1R1: add phone, dual writephone_no still read2Backfill phonebatched job3R2: read phonestill write both4R3: stop writing phone_noremove from model5R4: drop phone_nocontract doneEvery release is compatible with the one before itdjango-deployment.com
If you only need a new Python name, db_column skips all of this.

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.

what needs to change?Must the database column name change?no, Python name onlydb_columnone release, no SQLyes, busy tableExpand and contractfour releasesyes, can stop trafficRenameFieldmaintenance windowMost renames only need db_columndjango-deployment.com
Small internal tables with no rolling deploys can simply use RenameField.

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

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.

Learning path · step 6 of 14

Releases without downtime

Keep going