Предотвратить повторяющийся запрос в SerializerMethodField s - PullRequest
0 голосов
/ 07 марта 2019

Итак, мой сериализатор называется так:

result_serializer = TaskInfoSerializer(tasks, many=True)

И сериализатор:

class TaskInfoSerializer(serializers.ModelSerializer):
    done_jobs_count = serializers.SerializerMethodField()
    total_jobs_count = serializers.SerializerMethodField()
    task_status = serializers.SerializerMethodField()

    class Meta:
        model = Task
        fields = ('task_id', 'task_name', 'done_jobs_count', 'total_jobs_count', 'task_status')

    def get_done_jobs_count(self, obj):
        qs  = Job.objects.filter(task__task_id=obj.task_id, done_flag=1)
        condition = False
        # Some complicate logic to determine condition that I can't reveal due to business
        result = qs.count() if condition else 0
        # this function take around 3 seconds
        return result

    def get_total_jobs_count(self, obj):
        qs = Job.objects.filter(task__task_id=obj.task_id)
        # this query take around 3-5 seconds
        return qs.count()

    def get_task_status(self, obj):
        done_count    = self.get_done_jobs_count(obj)
        total_count    = self.get_total_jobs_count(obj)
        if done_count >= total_count:
            return 'done'
        else:
            return 'not yet'

Когда вызывается функция get_task_status, она вызывает другие 2 функции и снова выполняет эти 2 дорогостоящих запроса. Есть ли лучший способ предотвратить это? И я действительно не знаю порядок вызова этих функций, основан ли он на порядке, объявленном в полях Meta? Или выше этого?

Edit: Логика в get_done_jobs_count немного усложняется, и я не могу сделать это в одном запросе при получении задачи

Редактировать 2: Я просто перенести все эти функции в модель и использовать cached_property https://docs.djangoproject.com/en/2.1/ref/utils/#module-django.utils.functional Но возникает другой вопрос: надежно ли это число? Я не очень разбираюсь в кеше django, является ли cached_property существующим только для этого экземпляра (только до тех пор, пока API не получит список задач, возвращающих ответ) или он будет существовать какое-то время?

Ответы [ 3 ]

1 голос
/ 11 марта 2019

Я просто попробовал cached_property, и это решило проблему.

Модель:

from django.utils.functional import cached_property
from django.db import models

class Task(models.Model):
    task_id = models.AutoField(primary_key=True)
    task_name = models.CharField(default='')

    @cached_property
    def done_jobs_count(self):
        qs  = self.jobs.filter(done_flag=1)
        condition = False
        # Some complicate logic to determine condition that I can't reveal due to business
        result = qs.count() if condition else 0
        # this function take around 3 seconds
        return result

    @cached_property
    def total_jobs_count(self):
        qs = Job.objects.filter(task__task_id=obj.task_id)
        # this query take around 3-5 seconds
        return qs.count()

    @property
    def task_status(self):
        done_count    = self.done_jobs_count
        total_count    = self.total_jobs_count
        if done_count >= total_count:
            return 'done'
        else:
            return 'not yet'

Serializer:

class TaskInfoSerializer(serializers.ModelSerializer):

    class Meta:
        model = Task
        fields = ('task_id', 'task_name', 'done_jobs_count', 'total_jobs_count', 'task_status')
0 голосов
/ 07 марта 2019

Вы можете аннотировать эти значения, чтобы избежать дополнительных запросов. Таким образом, набор запросов, передаваемый сериализатору, будет выглядеть примерно так (он может измениться в зависимости от используемой версии Django и имени соответствующего запроса для заданий):

tasks = tasks.annotate(
    done_jobs=Count('jobs', filter=Q(done_flag=1)),
    total_jobs=Count('jobs'),
)
result_serializer = TaskInfoSerializer(tasks, many=True)

Тогда метод сериализатора будет выглядеть так:

def get_task_status(self, obj):
    if obj.done_jobs >= obj.total_jobs:
        return 'done'
    else:
        return 'not yet'

Редактировать : cached_property не поможет вам, если вам придется вызывать метод для каждого экземпляра задачи (что выглядит так). Проблема не столько в расчете, сколько в том, нужно ли вам обращаться к базе данных для каждой отдельной задачи. Вы должны сосредоточиться на получении всей информации, необходимой для расчета в одном запросе. Если это невозможно или слишком сложно, возможно, подумайте об изменении структуры данных (моделей), чтобы облегчить это.

0 голосов
/ 07 марта 2019

Использование iterator () и подсчет Iterator могут решить вашу проблему.

job_iter = Job.objects.filter(task__task_id=obj.task_id).iterator()
count = len(list(job_iter))
return count

Вы можете использовать select_related () и prefetch_related () для Retrieve all сразу, если они вам понадобятся,

Примечание : если вы используете iterator () для выполнения запроса, prefetch_related () вызовы будут игнорироваться

Возможно, вы захотите просмотреть документацию для оптимизации

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