Статьи Автоматизация 8 мин

Оптимизация массовых действий для миллионов записей

person
Редакция Django-Admin.ru
Редакция • Опубликовано 18 октября 2024 г.
Автоматизация Bulk Actions Celery Производительность

Действия в Django Admin обманчиво просты. Выбрал строки, выбрал действие, нажал «Выполнить». Но за этой простотой скрывается ловушка: дефолтная реализация загружает все выбранные объекты в память и обрабатывает их последовательно в одном цикле запрос-ответ.

Проблема на масштабе

Когда queryset содержит 100 000+ строк, дефолтное поведение вызывает:

  1. Исчерпание памяти — Django загружает все объекты в Python
  2. Таймауты запросов — Nginx/Gunicorn убивает долгие запросы
  3. Блокировки БД — массовые обновления могут заблокировать таблицы

Чанкированная обработка

Первая оптимизация — обработка пакетами.

from django.contrib import admin

@admin.action(description="Архивировать выбранные")
def archive_items(modeladmin, request, queryset):
    CHUNK_SIZE = 500
    total = queryset.count()
    processed = 0

    while processed < total:
        chunk_ids = list(
            queryset.values_list('id', flat=True)[processed:processed + CHUNK_SIZE]
        )
        queryset.model.objects.filter(id__in=chunk_ids).update(
            status='archived',
            archived_at=timezone.now()
        )
        processed += CHUNK_SIZE

    modeladmin.message_user(request, f"Архивировано {total} записей.")

Делегирование в Celery

Для действительно больших датасетов действие должно только поставить задачу в очередь:

@admin.action(description="Экспорт в CSV (асинхронно)")
def export_csv_async(modeladmin, request, queryset):
    task = export_csv_task.delay(
        model=queryset.model.__name__,
        ids=list(queryset.values_list('id', flat=True)),
        user_email=request.user.email
    )
    modeladmin.message_user(
        request,
        f"Экспорт поставлен в очередь (задача {task.id}). Результат придёт на email."
    )

Ключевые выводы

  • Никогда не обрабатывайте более ~1000 строк синхронно в admin action
  • Используйте .update() вместо итерации с .save() на каждом объекте
  • Для экспорта и отчётов всегда используйте очередь задач

forum Обсуждение

Комментарии скоро будут доступны. Следите за обновлениями!