migration2025-03-20·9·143/348

Django 모델 마이그레이션 충돌 해결

How to diagnose and resolve Django migration conflicts when multiple developers modify models concurrently, including squashing and manual resolution.

Django 모델 마이그레이션 충돌 해결

Introduction

Django's migration system is one of its greatest strengths, but it becomes a source of frustration when multiple developers create migrations for the same app simultaneously. The dreaded Conflicting migrations detected error can halt your deployment pipeline and cause real anxiety when you are not sure how to resolve it safely.

I dealt with this exact problem on a freelance project in Lisbon where two developers were adding fields to the same Django model on different branches. When we merged to main, Django refused to run migrations because it could not determine the correct order.

Environment

Django 4.2.11
Python 3.12.3
PostgreSQL 15.4

Problem

When running python manage.py migrate, you see:

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, core, sessions
Running migrations:
  Applying core.0015_auto_20250315_1200...Traceback (most recent call last):
  ...
django.db.migrations.exceptions.InconsistentMigrationHistory:
Conflicting migrations detected; multiple leaf nodes in the migration graph.
To fix them, run "python manage.py makemigrations --merge"

Or:

django.db.migrations.exceptions.InconsistentMigrationHistory:
Conflicting migrations detected; multiple leaf nodes in the migration graph.
To fix them in your project, run:
  python manage.py makemigrations --merge

After running --merge:

$ python manage.py makemigrations --merge
Merging 'core.0015_add_price' and 'core.0015_add_volume'
  Branch 0015_add_price
    - Add field price to product
  Branch 0015_add_volume
    - Add field volume to product
Created new merge migration 0015_merge_0015_add_price_0015_add_volume.py

The merge migration looks like:

from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ('core', '0015_add_price'),
        ('core', '0015_add_volume'),
    ]

    operations = []

This seems fine, but there are deeper problems lurking.

Analysis

The merge migration approach works for simple cases but has several pitfalls.

Problem 1: Merge migrations can mask conflicts. If both branches added a field with the same name but different types, the merge migration will silently pick one and ignore the other.

Problem 2: Field ordering matters. If both migrations add fields to the same model, the column order in the database may not match what you expect.

Problem 3: Unique constraints can conflict. If both branches add a unique=True constraint to different fields, the merge migration handles it, but if they add constraints to the same field with different parameters, you have a real problem.

Problem 4: Manual editing is often required. The auto-generated merge migration is a starting point, not a final solution. You almost always need to review and edit it.

Check the current migration state:

python manage.py showmigrations core
 core
  [ ] 0001_initial
  [X] 0002_auto_20250101_1200
  [X] 0003_add_user_email
  [ ] 0015_add_price
  [ ] 0015_add_volume
  [ ] 0015_merge_0015_add_price_0015_add_volume

Notice that both 0015_add_price and 0015_add_volume are unapplied, but they both depend on 0003_add_user_email.

Solution

Step 1: Always create migrations from the latest main branch.

git checkout main
git pull
python manage.py makemigrations
git add .
git commit -m "Add migration for model changes"

Step 2: When conflicts exist, run the merge command.

python manage.py makemigrations --merge --name resolve_conflict

Step 3: Review the merge migration manually.

from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ('core', '0015_add_price'),
        ('core', '0015_add_volume'),
    ]

    operations = [
        migrations.AlterField(
            model_name='product',
            name='price',
            field=models.DecimalField(max_digits=10, decimal_places=2),
        ),
    ]

Do not rely on the auto-generated content. Open the file, read it, and make sure it does exactly what you expect.

Step 4: For complex conflicts, squash instead of merge.

# First, squash all migrations in the app
python manage.py squashmigrations core 0001 0016

# This creates a single migration file
# Review it carefully, then delete the old ones

Step 5: Test with a clean database.

# Drop and recreate the test database
dropdb test_core
python manage.py migrate --run-syncdb

# Or use Django's test database
python manage.py test core --keepdb

Step 6: For teams, establish a convention.

# In your team's workflow:
# 1. Always pull latest before making migrations
# 2. Name migrations descriptively: 0016_add_product_price_field
# 3. Include the app name in the dependency comment
# 4. Never edit someone else's migration file
# 5. Always run makemigrations on main before creating a branch

Lessons Learned

  • Merge migrations are a starting point, not a solution. Always review and potentially edit the auto-generated file.
  • Prevent conflicts by establishing team conventions. Agree on migration naming, ordering, and review processes.
  • Use squashmigrations for apps with many small migrations. It reduces complexity and prevents future conflicts.
  • Always test with a clean database after resolving migration conflicts. The migration graph can have hidden issues that only appear with fresh data.
  • Consider using Django's --name flag to give merge migrations descriptive names instead of the default auto-generated ones.

This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.