Django конвертирует объекты модели в словарь в больших объемах, что приводит к тайм-ауту сервера

У меня возникла проблема, когда серверу Django требуется вечность, чтобы вернуть ответ. При работе с пушкой в ​​Heroku я получаю тайм-аут, поэтому не могу получить ответ. Если я запускаю локально, это занимает некоторое время, но через некоторое время он правильно показывает сайт.

В основном у меня есть две модели:

class Entry(models.Model):
    #Some stuff charFields, foreignKey, TextField and ManyToMany fields
    #Eg of m2m field:
    tags = models.ManyToManyField(Tag, blank=True)

    def getTags(self):
        ret = []
        for tag in self.tags.all():
            ret.append(getattr(tag, "name"))
        return ret

    def convertToDict(self):
        #for the many to many field I run the getter
        return {'id': self.id, 'tags' : self.getTags(), ... all the other fields ... }

class EntryState(models.Model):
    entry = models.ForeignKey(Entry, on_delete=models.CASCADE)
    def convertToDict(self):
        temp = self.entry.convertToDict()
        temp['field1'] = self.field1
        temp['field2'] = self.field1 + self.field3
        return temp

Тогда у меня есть представление:

def myView(request):
    entries = list(EntryState.objects.filter(field1 < 10))

    dictData = {'entries' : []}
    for entry in entries:
        dictData['entries'].append(entry.convertToDict())

    context = {'dictData': dictData}
    return render(request, 'my_template.html', context)

Как это было сделано, dictData содержит информацию обо всех записях, которые должны отображаться на сайте. Затем эта переменная загружается в javascript, который решает, как отображать информацию.

Представление застревает в цикле for, когда есть много записей с field1 < 10.

Мне просто интересно, что можно сделать, чтобы улучшить этот подход. С +600 записями это уже занимает целую вечность, достаточно, чтобы gunicorn истекло время ожидания.

Если это уместно, я использую Django 4.0.3 и Python3. Данные используются во внешнем интерфейсе Alpine.js для отображения сайта.

Анализ настроения постов в Twitter с помощью Python, Tweepy и Flair
Анализ настроения постов в Twitter с помощью Python, Tweepy и Flair
Анализ настроения текстовых сообщений может быть настолько сложным или простым, насколько вы его сделаете. Как и в любом ML-проекте, вы можете выбрать...
7 лайфхаков для начинающих Python-программистов
7 лайфхаков для начинающих Python-программистов
В этой статье мы расскажем о хитростях и советах по Python, которые должны быть известны разработчику Python.
Установка Apache Cassandra на Mac OS
Установка Apache Cassandra на Mac OS
Это краткое руководство по установке Apache Cassandra.
Сертификатная программа "Кванты Python": Бэктестер ансамблевых методов на основе ООП
Сертификатная программа "Кванты Python": Бэктестер ансамблевых методов на основе ООП
В одном из недавних постов я рассказал о том, как я использую навыки количественных исследований, которые я совершенствую в рамках программы TPQ...
Создание персонального файлового хранилища
Создание персонального файлового хранилища
Вы когда-нибудь хотели поделиться с кем-то файлом, но он содержал конфиденциальную информацию? Многие думают, что электронная почта безопасна, но это...
Создание приборной панели для анализа данных на GCP - часть I
Создание приборной панели для анализа данных на GCP - часть I
Недавно я столкнулся с интересной бизнес-задачей - визуализацией сбоев в цепочке поставок лекарств, которую могут просматривать врачи и...
1
0
26
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы пытаетесь отобразить шаблон, содержащий огромные объемы данных.

В этом случае я думаю, вам следует рассмотреть возможность разделения отправляемых вами данных на шаблон и записи как на два разных типа запроса. Это означает, что вы отправляете шаблон, а затем ваш alpine.js загружает записи одну за другой. (Редактировать: я имею в виду пакеты из 50 или около того, пожалуйста, не делайте запрос на каждую запись...)

Для этого вы можете использовать либо готовый Приложение REST Framework для django, либо развернуть собственное решение.

Конечно, таким образом пользователь не увидит никаких данных после загрузки шаблона. Вместо этого им придется ждать второго запроса. Усовершенствованное решение для этого — отображать только первые 100 записей или около того, которые увидит пользователь, и использовать ваш новый REST API для остальных данных. Вы можете использовать плагин отложенной загрузки alpine.js Пересекать, это также поможет вашей производительности в целом!

Это звучит как хорошее решение, но что, если записи нужно перетасовать? То есть они не должны отображаться в том же порядке? Единственный способ, который я вижу, - это получить всю информацию перед загрузкой страницы и перетасовать ее во внешнем интерфейсе. Но в этом случае не будет ли стоимость больше?

lpares12 17.05.2022 11:23

Я не совсем уверен, что вы имеете в виду. Например, записи перемешиваются при каждом запросе страницы? Или только когда пользователь нажимает какую-то кнопку? В любом случае решение остается тем же или, по крайней мере, похожим. Вы можете: Отправить список идентификаторов записей вместе с шаблоном, заставить интерфейс перетасовать их, а затем отправить запрос на увеличение для перетасованных разделов.

Shellsort 17.05.2022 11:57
Ответ принят как подходящий

На первый взгляд, ваша основная проблема, скорее всего, заключается в том, что вы дважды обращаетесь к базе данных для каждого экземпляра EntryState.

convertToDict метод использует FK entry, и для каждой записи вы также получаете M2M tags. Решение заключается в оптимизации запроса.

Во-первых, давайте определим проблему. Когда вы поместите этот код перед концом представления, вы увидите, сколько раз вы обращаетесь к базе данных в консоли.

def myView(request):
    ....
    from django.db import connection; qq=connection.queries; print(f'QUERY: {len(qq)}')
    return render(request, 'my_template.html', context)

Теперь попробуйте улучшить запрос в представлении.

query = EntryState.objects.filter(field1 < 10)  # Assuming this is a valid query that uses something like `field1__lt=10`

Вы можете использовать select_related для входа FK, чтобы он был получен в рамках этого одного запроса (сохраняет N попаданий в БД)

query = EntryState.objects.filter(field1__lt=10).select_related('entry')

Также вы можете добавить предварительную выборку, связанную с получением tags для ВСЕХ записей за один клик.

query = EntryState.objects.filter(field1__lt=10).select_related('entry').prefetch_related('entry__tags_set')

После каждого изменения вы можете увидеть, сколько раз вы обращались к базе данных, чтобы увидеть, как устраняется проблема. Вы можете снова проверить количество обращений к БД, чтобы убедиться, что оно оптимизировано.

Для получения дополнительной информации, пожалуйста, прочитайте об оптимизации запросов, select_related и prefetch_related.

Вы также можете использовать некоторые приложения для отслеживания эффективности ваших представлений: джанго-шелк, панель инструментов django-debug.

Источник: https://docs.djangoproject.com/en/4.0/ref/models/querysets/#select-related

Это идеально и сокращает весь вызов буквально до 4-5 секунд (с запросов 2016 года до 8). Конечно, мне все равно нужно быть осторожным, если было 2000000 записей, и как-то ограничить это. Но пока это то, что мне было нужно.

lpares12 17.05.2022 17:44

Для этого вы должны использовать нумерацию страниц, которая ограничит размер ответа. Конечно, требуется немного навыков JS, чтобы получать данные, КОГДА вам нужно и сколько вам нужно. Количество запросов не имеет значения, если вы возвращаете много данных (и я должен сказать, что 4-5 секунд для конечной точки в большинстве случаев неприемлемы). В любом случае, рад, что смог помочь.

Çağatay Barın 17.05.2022 18:09

Другие вопросы по теме