Как быстрее получить объекты подсчета на Django?
Моя цель - оптимизировать получение количества объектов, которые есть в моей модели Django.
У меня есть две модели:
- Пользователи
- Перспективы
Это отношения "один-ко-многим". Один Пользователь может создать много Перспектив. Одна перспектива может быть создана только одним пользователем.
Я пытаюсь получить Перспективы, созданные пользователем за последние 24 часа.
Модель перспектив имеет примерно 7 миллионов строк в моей базе данных PostgreSQL. Пользователей только 2000.
Мой текущий код занимает слишком много времени, чтобы получить желаемые результаты.
Я пытался использовать filter()
и count()
:
import datetime
# get the date but 24 hours earlier
date_example = datetime.datetime.now() - datetime.timedelta(days = 1)
# Filter Prospects that are created by user_id_example
# and filter Prospects that got a date greater than date_example (so equal or sooner)
today_prospects = Prospect.objects.filter(user_id = 'user_id_example', create_date__gte = date_example)
# get the count of prospects that got created in the past 24 hours by user_id_example
# this is the problematic call that takes too long to process
count_total_today_prospects = today_prospects.count()
Я работаю, но это занимает слишком много времени (5 минут). Потому что он проверяет всю базу данных, а не просто проверяет, как я думал: только перспективы, которые были созданы за последние 24 часа пользователем.
Я также пробовал использовать annotate
, но это так же медленно, потому что в конечном итоге делает то же самое, что и обычный .count()
:
today_prospects.annotate(Count('id'))
Как я могу получить подсчет более оптимизированным способом?
Предполагая, что у вас его еще нет, я предлагаю добавить индекс, включающий поля пользователя и даты (убедитесь, что они расположены в таком порядке: сначала пользователь, а затем дата, потому что для пользователя вы ищете точное совпадение, а для даты у вас есть только отправная точка). Это должно ускорить выполнение запроса.
Например:
class Prospect(models.Model):
...
class Meta:
...
indexes = [
models.Index(fields=['user', 'create_date']),
]
...
Это должно создать новый файл миграции (выполните makemigrations
и migrate
), где он добавляет индекс в базу данных.
После этого ваш тот же код должен работать немного быстрее:
count_total_today_prospects = Prospect.objects\
.filter(user_id='user_id_example', create_date__gte=date_example)\
.count()
Документация Django:
Вызов count() выполняет SELECT COUNT(*) за кадром, поэтому всегда следует использовать count(), а не загружать все записи в объекты Python и вызывать len() для результата (если только вам не нужно загрузить объекты в память, в этом случае len() будет быстрее). Обратите внимание, что если вам нужно количество элементов в наборе QuerySet и вы также извлекаете из него экземпляры модели (например, путем итерации), вероятно, эффективнее будет использовать len(queryset), который не вызовет дополнительного запроса к базе данных, как это сделал бы count(). Если набор queryset уже полностью получен, count() будет использовать эту длину, а не выполнять дополнительный запрос к базе данных.
.
Посмотрите на эту ссылку: https://docs.djangoproject.com/en/3.2/ref/models/querysets/#count.
Попробуйте использовать len().