Skip to main content

django-fastmig

Experimental. fastmig is a drop-in add-on for Django (tested with 4.2, 5.2 and 6.0) that makes migrate on large projects several times faster without changing the SQL that gets executed.

pip install django-fastmig
INSTALLED_APPS = ["fastmig", *INSTALLED_APPS]   # or: import fastmig; fastmig.install()

FASTMIG_DISABLE=1 turns it off. Pure Python, no settings, no changes to your migrations, models or database.

Results

Fresh database; every DDL statement the schema editor executes is logged and compared between the two runs.

project models / migrations backend stock fastmig speedup DDL
"Project A" — real in-house inventory/ERP app (taggit, modeltranslation, auditlog, pgtrigger, 49 data migrations) 222 / 493 PostgreSQL 15 154–178 s 17–22 s 8–9.5× identical (22 557 statements)
Project A, pytest --create-db, one app PostgreSQL 15 170 s 23 s
generated, 400 models 400 / 818 SQLite 313–364 s 57 s 5.5–6.4× identical (11 262)
generated, 120 models 120 / 223 SQLite / PostgreSQL 17–20 s 4–5 s 3.5–4.6× identical
generated corner cases (MTI, proxies, O2O pks, to_field, explicit through, renames, pk type changes) 53 / 59 SQLite / PostgreSQL 1.5 / 3.0 s 0.7 / 2.2 s 2× / 1.4× identical

What remains is mostly the database executing DDL and the class renders Django still needs (including a full re-render before every RunPython, same as stock). Scripts and generators are in benchmarks/.

Why migrate is slow

The schema editor works on rendered model classes. After every operation Django rebuilds ("fakes") classes with type(...) — expensive pure-Python work (ModelBase.__new__, field cloning via deconstruct(), contribute_to_class, M2M through models). For any relational change stock Django rebuilds the model's entire connected component; on a connected schema that is O(models) class builds per operation. The 120-model project above rebuilds 37 080 classes over 223 migrations; fastmig rebuilds 1 163. See the forum thread Very slow migrations with large numbers of tables.

How fastmig differs from the "shallow reload" patch in that thread

That patch (SHALLOW_RELOAD_MIGRATION) reloads only the mutated model. It gets the speed, but models that point at the re-rendered one keep pointing at the old class object — reverse relations and FK targets can land on the wrong class, which is what Django's maintainers warned about. It usually works because schema editors mostly read table/column names, which are the same on the stale class.

fastmig:

  • re-renders the mutated model plus what genuinely needs a new class: subclasses/proxies, targets of added/removed/altered relations, and — when a referenced field, pk or relation changed — direct predecessors, propagating through pk-type dependencies (MTI, O2O primary keys, to_field);
  • re-links every other model's forward pointers to the new class through Django's own do_related_class(), clearing cached properties and following auto-created through tables;
  • preserves the pre-change class that RenameModel / AlterField on a pk read from from_state (stock only gets it by accident of traversal order);
  • unregisters stale auto-created through classes;
  • re-renders instead of re-linking for third-party relation fields it doesn't know (e.g. taggit's TaggableManager);
  • keeps Django's from_state contract (#24225, #24573).

The plan is computed from ModelState objects, never from rendered classes. Django's own migrations + schema suites (1 033 tests) pass with fastmig installed on SQLite and PostgreSQL, plus 1 435 tests from related labels.

What it patches

fastmig.install() (run by the AppConfig) monkey-patches django.db.migrations.state.ProjectState: reload_model/reload_models (planner + re-link), add_field/remove_field/alter_field/add_model/ rename_model/remove_model (hints about what changed), and clone (cheaper, equivalent). uninstall() restores everything. fastmig.stats counts reloads, renders and re-linked fields.

Caveats

  • Validated on SQLite and PostgreSQL only; not on MySQL/Oracle.
  • Third-party operations that mutate a model's ModelState and call state.reload_model() for it (pgtrigger, django.contrib.postgres) work unchanged. Untested: an operation that mutates model X but only worked because stock's component-wide re-render rebuilt X as a side effect of reloading another model. I know of no package doing this.
  • Some operations emit several independent DDL statements (e.g. altering the FK column in each table referencing a changed pk). Django orders them by model-registry order, which is hash-seed dependent — stock Django itself emits them in varying order between runs. The benchmark scripts treat a pure reordering of otherwise identical statements as a match.
  • If anything looks off: FASTMIG_DISABLE=1, and benchmarks/bench_real.py gives you a full stock-vs-fastmig DDL diff for your project.

Development

pip install -e . django
python -m unittest discover -s tests -v                       # unit + SQL parity tests
python benchmarks/gen_project.py benchmarks/demo --apps 8 --models 15 --rounds 30
python benchmarks/bench.py benchmarks/demo [--pg dbname]      # stock vs fastmig, DDL diff
python benchmarks/bench_real.py <project> <python> <settings> <pg_db> [runs]
DJANGO_SRC=/path/to/django python benchmarks/run_django_tests.py --settings test_sqlite migrations schema

BSD 3-Clause license.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_fastmig-0.1.0.tar.gz (16.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_fastmig-0.1.0-py3-none-any.whl (14.3 kB view details)

Uploaded Python 3

File details

Details for the file django_fastmig-0.1.0.tar.gz.

File metadata

  • Download URL: django_fastmig-0.1.0.tar.gz
  • Upload date:
  • Size: 16.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.3

File hashes

Hashes for django_fastmig-0.1.0.tar.gz
Algorithm Hash digest
SHA256 c4e573d2129883bb60092caabe11c4597dbcca58e150a13a4344eabd4e3b040d
MD5 d28b00a6df3fe89241ec62f402a39b09
BLAKE2b-256 5d68c849c336625026dbca98bb1df65daba073e6890b6fcdc54b8a9f7300f032

See more details on using hashes here.

File details

Details for the file django_fastmig-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: django_fastmig-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 14.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.3

File hashes

Hashes for django_fastmig-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7e4527388d91b27be2d55fbc42374abb1cb8615d632d4e3433b29659d0efe30c
MD5 a5eb064dadb17364ce2ec7bffabcf249
BLAKE2b-256 9e856e7c52fcb0632116f749c5092d4d1ed3f7c1c4098b51fe3b0b91c1dca4ec

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page