Код-ревью Middle Миграции обновлено
Код-ревью миграции данных Django
Вопрос
Миграция заполняет новое поле slug у 3 млн статей. Найдите проблемы.
from django.db import migrations
from django.utils.text import slugify
from blog.models import Article
def fill(apps, schema_editor):
for article in Article.objects.all():
article.slug = slugify(article.title)
article.save()
class Migration(migrations.Migration):
dependencies = [('blog', '0012_article_slug')]
operations = [
migrations.RunPython(fill),
migrations.AlterField(
model_name='article',
name='slug',
field=models.SlugField(unique=True),
),
]
Коротко
Исправленный вариант делят на две миграции. Первая переносит данные: Вторая отдельно делает поле уникальным (а на большой таблице уникальный индекс лучше создать конкурентно).
Что должен найти ревьюер
- Ошибкастрока 3Импорт текущей модели вместо исторической: через полгода модель изменится, и миграция сломается; нужно
apps.get_model("blog", "Article") - Производительностьстрока 7Обход 3 млн объектов с кешированием QuerySet: память; нужен
.iterator()и обработка пачками (bulk_update) - Производительностьстрока 9
save()безupdate_fieldsпишет все поля и шлёт сигналы на каждую строку;bulk_update(objs, ["slug"])пачками - Стильстрока 15Нет обратной функции: миграцию нельзя откатить; добавить
migrations.RunPython.noop - Ошибкастрока 16Перенос данных и смена схемы в одной миграции и одной транзакции: долгая транзакция и блокировки на всё время переноса
- Ошибкастрока 19
unique=Trueупадёт на одинаковых заголовках (одинаковый slug), дубли нужно разрешить при заполнении; плюсmodelsне импортирован
Разбор
Исправленный вариант делят на две миграции. Первая переносит данные:
def fill(apps, schema_editor):
Article = apps.get_model('blog', 'Article')
batch = []
for a in Article.objects.filter(slug='').only('id', 'title').iterator(chunk_size=2000):
a.slug = f'{slugify(a.title)}-{a.id}'
batch.append(a)
if len(batch) == 2000:
Article.objects.bulk_update(batch, ['slug'])
batch.clear()
Article.objects.bulk_update(batch, ['slug'])
class Migration(migrations.Migration):
atomic = False
dependencies = [('blog', '0012_article_slug')]
operations = [migrations.RunPython(fill, migrations.RunPython.noop)]
Вторая отдельно делает поле уникальным (а на большой таблице уникальный индекс лучше создать конкурентно). Суффикс id снимает проблему одинаковых заголовков.
Ответьте сами, а не прочитайте
В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.
Предыдущий вопрос
Переименование колонки без простоя
Следующий вопрос
Как вы работаете с миграциями в команде