Django PG Zero Downtime Migrations
Django postgresql backend that applies migrations with respect to database locks, enabling zero downtime during schema changes. It modifies the standard Django PostgreSQL backend to minimize locks during schema and `RunSQL` operations, helping to ensure continuous application availability during deployments. The library is actively maintained, with frequent releases to support new Django, Python, and PostgreSQL versions, and is currently at version 0.19.
Common errors
-
django.db.utils.ProgrammingError: column "new_field" contains null values
cause A `NOT NULL` field was added without a `db_default` (Django 5.0+) or sufficient handling for existing rows, causing old application code (running during a rolling deployment) to fail when creating/updating records without providing a value for the new field.fixWhen adding a `NOT NULL` column: (1) Add the column as nullable. (2) Deploy code that handles the new nullable field and populates it for existing data (e.g., via a `RunPython` operation). (3) Deploy a subsequent migration to set the column as `NOT NULL`. For Django 5.0+, use `db_default` when adding `NOT NULL` fields to ensure a database-level default. For older Django, consider `ZERO_DOWNTIME_MIGRATIONS_KEEP_DEFAULT = True` or the multi-step approach. -
django.db.utils.OperationalError: canceling statement due to statement timeout
cause A migration operation acquired a lock on a table or took too long to execute, exceeding PostgreSQL's configured `statement_timeout` or `lock_timeout` settings. This can happen with complex schema changes or large tables.fixIncrease `ZERO_DOWNTIME_MIGRATIONS_LOCK_TIMEOUT` and/or `ZERO_DOWNTIME_MIGRATIONS_STATEMENT_TIMEOUT` in your Django `settings.py` for operations that are genuinely long but safe. Alternatively, analyze the specific migration step and break it down into smaller, inherently safer operations (e.g., creating indexes concurrently, adding nullable columns before setting `NOT NULL`). -
django.db.utils.ProgrammingError: relation "old_table_name" does not exist
cause Renaming a model or field in a single migration step causes a conflict with old application code that is still running during a rolling deployment and references the old name.fixImplement a multi-step rename strategy. First, add the new field/model (deploy code to use the new while tolerating the old). Second, backfill data if necessary. Third, deploy code that solely uses the new field/model. Finally, remove the old field/model in a separate migration. The library's `SeparateDatabaseAndState` operation can assist with certain renaming scenarios using views. -
ModuleNotFoundError: No module named 'django_pg_zero_downtime_migrations'
cause The library is not installed or the `ENGINE` path in `settings.py` is incorrect.fixEnsure the library is installed with `pip install django-pg-zero-downtime-migrations`. Verify the `ENGINE` path in `settings.py` is exactly `'django_pg_zero_downtime_migrations.backends.postgres'`.
Warnings
- breaking This library frequently drops support for older Django, Python, and PostgreSQL versions. For example, version 0.17 dropped Django 3.2, 4.0, 4.1 and Python 3.6, 3.7. Version 0.14 dropped PostgreSQL 11 and the setting `ZERO_DOWNTIME_MIGRATIONS_USE_NOT_NULL`.
- deprecated The `migrate_isnotnull_check_constraints` command was marked deprecated in 0.14 and completely dropped in 0.17.
- gotcha The backend does not use transactions for schema migrations (except for `RunPython` operations) to avoid deadlocks for complex migrations. If a schema migration fails mid-operation, the database state might be inconsistent, requiring manual intervention.
- gotcha Adding a `NOT NULL` column with a code-level default (e.g., `default=...` in a Django model field) can still cause downtime issues in older Django versions. PostgreSQL will attempt to fill existing rows, acquiring a table lock. While the library makes `ADD COLUMN DEFAULT NOT NULL` safe for Django 5.0+ with `db_default` and offers `ZERO_DOWNTIME_MIGRATIONS_KEEP_DEFAULT` for older Django versions, manual steps are safer if not properly configured.
Install
-
pip install django-pg-zero-downtime-migrations
Imports
- DATABASES['default']['ENGINE']
'ENGINE': 'django.db.backends.postgresql'
DATABASES = { 'default': { 'ENGINE': 'django_pg_zero_downtime_migrations.backends.postgres', ... } }
Quickstart
import os
DATABASES = {
'default': {
'ENGINE': 'django_pg_zero_downtime_migrations.backends.postgres',
'NAME': os.environ.get('DB_NAME', 'your_database_name'),
'USER': os.environ.get('DB_USER', 'your_user'),
'PASSWORD': os.environ.get('DB_PASSWORD', 'your_password'),
'HOST': os.environ.get('DB_HOST', 'localhost'),
'PORT': os.environ.get('DB_PORT', '5432'),
}
}
# Recommended settings for zero-downtime operations
ZERO_DOWNTIME_MIGRATIONS_LOCK_TIMEOUT = '2s'
ZERO_DOWNTIME_MIGRATIONS_STATEMENT_TIMEOUT = '2s'
ZERO_DOWNTIME_MIGRATIONS_FLEXIBLE_STATEMENT_TIMEOUT = True
ZERO_DOWNTIME_MIGRATIONS_RAISE_FOR_UNSAFE = True
# For Django < 5.0 when adding columns with code defaults
# ZERO_DOWNTIME_MIGRATIONS_KEEP_DEFAULT = True