Django Migrations: Best Practices für große Datenbanken
Inhalt
Bei kleinen Datenbanken ist eine Migration eine Sache von Millisekunden. Bei Tabellen mit Millionen Zeilen und einer laufenden Produktion wird es schnell kritisch. Ich zeige dir die Patterns die ich in datenbankintensiven Projekten gelernt habe.
Wie Migrations funktionieren
# Migration erstellen
python manage.py makemigrations
# Migrations anzeigen
python manage.py showmigrations
# Bestimmte Migration
python manage.py migrate myapp 0005
# SQL einer Migration anzeigen (ohne auszuführen!)
python manage.py sqlmigrate myapp 0005
# Fake: Migration als ausgeführt markieren ohne SQL
python manage.py migrate --fake myapp 0005Migrations ohne Downtime
Große Schema-Änderungen brauchen eine mehrstufige Strategie:
# SCHRITT 1: Neues Feld als nullable hinzufügen (schnell, kein Lock)
class Migration(migrations.Migration):
operations = [
migrations.AddField(
model_name="order",
name="new_field",
field=models.CharField(max_length=100, null=True), # null=True!
),
]# SCHRITT 2: Daten befüllen (Datenmigration, batched)
from django.db import migrations
def fill_new_field(apps, schema_editor):
Order = apps.get_model("orders", "Order")
# In Batches verarbeiten, nicht alles auf einmal!
batch_size = 1000
qs = Order.objects.filter(new_field__isnull=True)
while True:
ids = list(qs.values_list("id", flat=True)[:batch_size])
if not ids:
break
Order.objects.filter(id__in=ids).update(
new_field=compute_value()
)
class Migration(migrations.Migration):
operations = [migrations.RunPython(fill_new_field)]# SCHRITT 3: NOT NULL Constraint hinzufügen
operations = [
migrations.AlterField(
model_name="order", name="new_field",
field=models.CharField(max_length=100), # null=True entfernt
),
]Datenmigrations
# Datenmigration erstellen
python manage.py makemigrations --empty myapp
# In der Migration:
from django.db import migrations
def migrate_forward(apps, schema_editor):
# apps.get_model statt direkten Import!
User = apps.get_model("auth", "User")
Profile = apps.get_model("myapp", "Profile")
for user in User.objects.filter(profile__isnull=True).iterator(chunk_size=500):
Profile.objects.create(user=user, display_name=user.username)
def migrate_backward(apps, schema_editor):
Profile = apps.get_model("myapp", "Profile")
Profile.objects.all().delete()
class Migration(migrations.Migration):
operations = [
migrations.RunPython(migrate_forward, migrate_backward),
]Migrations squashen
Viele Migrations verlangsamen den Start. Ab ~50 Migrations lohnt sich Squashing:
# Migrations 0001 bis 0050 zu einer zusammenfassen
python manage.py squashmigrations myapp 0001 0050
# Resultat: 0001_squashed_0050.pyRollback-Strategie
# Zurück zu einer bestimmten Migration
python manage.py migrate myapp 0004
# Komplett zurück
python manage.py migrate myapp zero
# Immer backward-Funktion implementieren!
def migrate_backward(apps, schema_editor):
# Diese Funktion MUSS den Zustand vor migrate_forward herstellen
passHäufige Fallstricke
- Nie Imports in Migrationen: Immer
apps.get_model()nutzen — direkte Model-Imports brechen wenn das Model später umstrukturiert wird unique_togetherentfernen vorindexeshinzufügen: Reihenfolge in Meta-Changes beachten- Keine Logik in Schema-Migrations: Datenmigration und Schema-Migration trennen
- Auf Produktions-DB testen:
sqlmigratenutzen und SQL auf Staging ausführen bevor Produktion
