Как я могу улучшить этот запрос Django? - PullRequest
0 голосов
/ 16 июня 2020

У меня есть запрос Django, выполнение которого занимает несколько минут.

stat_type = 'Some String'
obj = xxx # some brand object

query = Q( Q(stat_type__icontains=stat_type) & Q(Q(brand=obj) | Q(organisation__in=obj.organisation_set.active())))

result = ViewStat.objects.filter(query).aggregate(one=Count('id', filter=Q(created__gte=timezone.now() - relativedelta(months=int(1)))), \
    three=Count('id', filter=Q(created__gte=timezone.now() - relativedelta(months=int(3)))), 
    twelve=Count('id', filter=Q(created__gte=timezone.now() - relativedelta(months=int(12)))), 
    all=Count('id', filter=Q(created__gte=timezone.now() - relativedelta(months=int(999)))))

Модели

class Brand(models.Model):
    ...

class Organisation(models.Model):
    brand = models.ForeignKey(Brand, on_delete=models.CASCADE)
    ...

class ViewStat(models.Model):
    stat_type = models.CharField(max_length=21)
    brand = models.ForeignKey(Brand, on_delete=models.SET_NULL, blank=True, null=True)
    organisation = models.ForeignKey(Organisation, on_delete=models.SET_NULL, blank=True, null=True)

У меня примерно 80 тысяч организаций, 700 брендов и 10 миллионов ViewStats.

Как повысить производительность запросов ?

1 Ответ

1 голос
/ 16 июня 2020

Трудно сделать улучшения без измерений для сравнения. Но вот несколько мыслей.

Сначала я немного изменил порядок кода, чтобы лучше его понять.

from django.db.models import Count, Q
from django.utils import timezone as tz
from .models import Brand, ViewStat

stat_type = 'Some String'
some_brand = Brand.objects.first()
active_org_id_set = set(
    some_brand.organisation_set.active().values_list('id', flat=True))

time_now = tz.now()
one_month_ago = time_now - relativedelta(months=int(1))
three_months_ago = time_now - relativedelta(months=int(3))
twelve_months_ago = time_now - relativedelta(months=int(12))

result = ViewStat.objects.select_related(None)\
    .filter(stat_type__icontains=stat_type)\
    .filter(
        Q(
            Q(brand_id=some_brand.pk)
            | Q(organisation_id__in=active_org_id_set)))\
    .aggregate(
        one=Count('id', filter=Q(created__gte=one_month_ago),
        three=Count('id', filter=Q(created__gte=three_months_ago)),
        twelve=Count('id', filter=Q(created__gte=twelve_months_ago)),
        all=Count('id')))

Всегда лучше использовать один вызов tz.now().

Похоже, что последний фильтр в агрегации (с использованием relativedelta(999)) можно было бы опустить.

Я предпочитаю использовать отдельные переменные для хранения данных фильтра, поэтому я создал active_org_id_set. Обратите внимание, что я собираю только PK (используя .values_list()), а не все объекты Organisation, поэтому потребляемая память намного меньше.

Затем я использую это active_org_id_set и вместо этого использую organisation_id__in из organisation__in, так что нет необходимости объединять таблицу Organisation с таблицей ViewStat.

Я также фильтрую brand_id вместо brand, чтобы избежать присоединения таблицы Brand к таблице ViewStat table.

Я явно использую .select_related(None), чтобы свести к минимуму объединенные таблицы. Возможно, в этом вызове нет необходимости, но у меня нет доступа к вашему плану выполнения базы данных.

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