У меня возникла проблема, когда серверу 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 для отображения сайта.
Вы пытаетесь отобразить шаблон, содержащий огромные объемы данных.
В этом случае я думаю, вам следует рассмотреть возможность разделения отправляемых вами данных на шаблон и записи как на два разных типа запроса. Это означает, что вы отправляете шаблон, а затем ваш alpine.js загружает записи одну за другой. (Редактировать: я имею в виду пакеты из 50 или около того, пожалуйста, не делайте запрос на каждую запись...)
Для этого вы можете использовать либо готовый Приложение REST Framework для django, либо развернуть собственное решение.
Конечно, таким образом пользователь не увидит никаких данных после загрузки шаблона. Вместо этого им придется ждать второго запроса. Усовершенствованное решение для этого — отображать только первые 100 записей или около того, которые увидит пользователь, и использовать ваш новый REST API для остальных данных. Вы можете использовать плагин отложенной загрузки alpine.js Пересекать, это также поможет вашей производительности в целом!
Я не совсем уверен, что вы имеете в виду. Например, записи перемешиваются при каждом запросе страницы? Или только когда пользователь нажимает какую-то кнопку? В любом случае решение остается тем же или, по крайней мере, похожим. Вы можете: Отправить список идентификаторов записей вместе с шаблоном, заставить интерфейс перетасовать их, а затем отправить запрос на увеличение для перетасованных разделов.
На первый взгляд, ваша основная проблема, скорее всего, заключается в том, что вы дважды обращаетесь к базе данных для каждого экземпляра 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 записей, и как-то ограничить это. Но пока это то, что мне было нужно.
Для этого вы должны использовать нумерацию страниц, которая ограничит размер ответа. Конечно, требуется немного навыков JS, чтобы получать данные, КОГДА вам нужно и сколько вам нужно. Количество запросов не имеет значения, если вы возвращаете много данных (и я должен сказать, что 4-5 секунд для конечной точки в большинстве случаев неприемлемы). В любом случае, рад, что смог помочь.
Это звучит как хорошее решение, но что, если записи нужно перетасовать? То есть они не должны отображаться в том же порядке? Единственный способ, который я вижу, - это получить всю информацию перед загрузкой страницы и перетасовать ее во внешнем интерфейсе. Но в этом случае не будет ли стоимость больше?