Как быстрее получить объекты подсчета на 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().

Вернуться на верх