Как решить проблему N+1 в Django для экспорта данных?

Я пытаюсь экспортировать данные в CSV и для этого мне нужно запросить тысячи экземпляров модели. Без каких-либо оптимизаций мой код может экспортировать ~700 экземпляров и связанные с ними статусы. При большем количестве сервер завершает работу с ошибкой 502 - Bad Gateway.

Добавление prefetch_related('statuses') улучшило производительность, но все равно происходит сбой при выборе более ~1000 экземпляров.

Статусы 200 и 300 могут отсутствовать или существовать несколько раз. В противном случае я мог бы отказаться от строки obj.statuses.filter(status=status_number).first() и просто добавить их по порядку, но я думаю, что здесь это необходимо.

Как я могу улучшить производительность этого фрагмента кода? Или я неправильно использую prefetch_related()?

# models.py
class MyExportModel(models.Model):
    id = models.PrimaryKey()
    name = models.CharField(max_length=255)


class StatusModel(models.Model):
    STATUS_CHOICES = [
        (100, 'created'),
        (200, 'paid'),
        (300, 'cancelled'),
        (400, 'billed'),
        (500, 'completed')
    ]

    export_model = models.ForeignKey('MyExportModel', related_name='statuses', on_delete=models.CASCADE)
    number = models.IntegerField(choices=STATUS_CHOICES)
    created = models.DateTimeField(auto_now_add=True)


# export_as_csv.py
def export_as_csv(writer, queryset):
    # Write headings into CSV
    writer.writerow(['id', 'name'] + [f'{number} - {name}' for (number, name) in StatusModel.STATUS_CHOICES])

    # Iterate over all selected models and their related statuses
    for obj in queryset.prefetch_related('statuses'):
        fields = [obj.id, obj.name]

        # Append date for statuses if known (problematic section)
        for (status_number, _) in StatusModel.STATUS_CHOICES:
            status = obj.statuses.filter(status=status_number).first()
            fields.append(status.created if status is not None else '')

        # Write row for instance
        writer.writerow(fields)
Вернуться на верх