Если вы разработчик и выбрали для своего проекта Django Framework, вы уже знаете насколько это отличное решение. Проекту уже почти 20 лет. Кроме того, что сам проект хорошо документирован, в сети много статей и подробной документации по разным областям применения. Найти готовое решение, пример скрипта или ответ на свой вопрос довольно легко. Фреймворк с понятными парадигмами, разными «батарейками» в комплекте, а главное надежный. На нём можно строить почти любые по сложности сайты — от простых до SaaS решений.

Сегодня я хочу дать пару важных советов для тех, кому ещё предстоит делать большие миграции на своем Django проекте. Поверьте, вам они очень пригодятся, чтобы ваш сайт не пребывал в даунтайме несколько часов, а вы всё это время не находились в сложном эмоциональном состоянии.

Если тема миграций на Django для вас совсем новая, то на хабре есть хорошие подробные статьи которые раскрывают много деталей: раз, два. Советы в этой же статье выстраданы автором лично, применимы к PostgreSQL 14 и Django 5.2.7, но на самом деле они подходят для всех версий фреймворка.

Совет 1: перед миграцией обязательно проверяйте свободное место на диске

Для жирных таблиц размером гигабайты и десятки гигабайт обязательно нужно убедиться, что у вас достаточно свободного места. К примеру, для таблицы размером 10 Гб нужно столько же свободного места на диске, а лучше чуть больше. Лично я придерживаюсь правила держать диск на 60% свободным и больше.

А если вы подумали, что у вас очень простая ��играция. К примеру, которая меняет тип даных с Integer на Decimal и вам не требуется такое изобилие свободного места. Посмотрите на этот пример:

# Generated by Django 5.2.7 on 2026-02-22 09:57
from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ("pages", "0006_alter_sigmaapi_report"),
    ]

    operations = [
        migrations.AlterField(
            model_name="report",
            name="сumulative_layout_shift ",
            field=models.DecimalField(
                blank=True,
                decimal_places=4,
                default=0,
                max_digits=5,
                null=True,
                verbose_name="CLS",
            ),
        ),
    ]

Казалось бы, пример как пример. Довольно тривиальный по замыслу. В нём всего один ALTER на таблицу:

BEGIN;
ALTER TABLE "report" ALTER COLUMN "сumulative_layout_shift" TYPE numeric(5, 4) USING "original_page_cls"::numeric(5, 4);
COMMIT;

Но подвох в том, что в этом случае PostgreSQL переписывает таблицу целиком. Кроме этого запускается процесс перестраивания индексов, которые также съедят пространство на диске. И если вам в процессе не хватит диска, вам потребуется дополнительное время на поиск срочных решений, когда миграция рухнет с эксепшеном. Согласитесь, все же лучше проверить заранее наличие нужного свободного места и при необходимости до выполнения миграции добавить его.

Вы можете проверить размер вашей таблицы через python manage.py dbshell :

SELECT datname, pg_size_pretty(pg_database_size(datname)) AS size
FROM pg_database
ORDER BY pg_database_size(datname) DESC;

проверить свободное место на диске можно с помощью команды:

df -h

Совет 2: будьте аккуратны с ORM при миграциях

Иногда при миграциях нам требуется стереть или перезаписать данные в существующих полях таблицы. И если вы не сталкивались с такого рода сюрпризами, то скорее всего сделаете это в обычной Django парадигме:

# Generated by Django 5.2.7 on 2026-02-22 08:52
import time
from django.db import migrations


def change_data(apps, schema_editor):
    Report = apps.get_model('pages', 'Report')
    for obj in Report.objects.filter(is_active=True):
        obj.сumulative_layout_shift = 0.05
        obj.save()


class Migration(migrations.Migration):

    dependencies = [
        ("pages", "0006_alter_sigmaapi_report"),
    ]

    operations = [
        migrations.RunPython(change_data),
    ]

В этом коде две коварных ошибки. Обе они очень быстро сжирают оперативную память, а затем упираются в различные блокировки, что опять приводит ваш сервер к многочасовому даунтайму в случае больших гигабайтных таблиц. С таблицами поменьше будет тоже не очень приятно.

Вот пример более производительный, но не лучший:

# Generated by Django 5.2.7 on 2026-02-22 08:52
import time
from django.db import migrations


def change_data(apps, schema_editor):
    Report = apps.get_model('pages', 'Report')
    for obj in Report.objects.filter(is_active=True).iterator():
        obj.сumulative_layout_shift = 0.05
        obj.save(update_fields=['сumulative_layout_shift'])


class Migration(migrations.Migration):

    dependencies = [
        ("pages", "0006_alter_sigmaapi_report"),
    ]

    operations = [
        migrations.RunPython(change_data),
    ]

В этом примере мы используем .iterator() и update_fields= Первый помогает избежать быстрого перерасхода оперативной памяти, чтобы не парализовать наш сервер, а второй ускоряет операции обновления.

Но если у вас десятки и сотни тысяч записей в таблице, то лучше не делайте так! В этом случае я бы совсем отказаться от использования obj.save() А вместо него использовал метод .update()

Почему Report.objects.filter(is_active=True).iterator() все же не лучшее решение? Дело в том, что хоть мы и экономим память при использовании итератора, за кулисами Django для каждого объекта будет выполнять запрос SELECT. Таким образом, если у нас 10000 объектов и сама запись тяжелая, скажем 1 Мб+, то эта миграция запустит 10000 раз SELECT и, как следствие, мы сильно потеряем время на дисковых операциях и транспортировке данных. 

Вот наиболее производительный пример для этого кейса. В нем совсем нет SELECT-ов, а только один UPDATE.

# Generated by Django 5.2.7 on 2026-02-22 08:52
import time
from django.db import migrations


def change_data(apps, schema_editor):
    Report = apps.get_model('pages', 'Report')
    Report.objects.filter(is_active=True).update(сumulative_layout_shift=0.05)


class Migration(migrations.Migration):

    dependencies = [
        ("pages", "0006_alter_sigmaapi_report"),
    ]

    operations = [
        migrations.RunPython(change_data),
    ]

Подведем итоги

  1. Перед миграциями проверяйте доступное свободное пространство на диске. Убедитесь, что на диске свободного места в 2 раза больше, от занимаемого объема.

  2. В коде с миграциями старайтесь избегать обход по объектам и метод .save(). Вместо этого используйте .update()Если вам по каким-то причинам не избежать этого, используйте .iterator() и update_fields=

Иначе вы рискуете попасть в неприятную ситуацию, в которой оказывался я — вместо ожидаемых 10 минут обновлений, просидеть 8 часов за терминалом с даунтаймом своего проекта.


К слову о своих проектах. В этом году я начал развивать свой новый проект — https://sigmaapi.com/ru/ Если у вас есть среди знакомых SEO специалисты, отправьте им ссылку на него. Буду вам очень признателен. Это поможет мне быстрее собрать правильную обратную связь от них и развивать продукт в правильном направлении.