1

I have problem with my endpoint performance which returns around 40 items and response takes around 17 seconds.

I have model:

class GameTask(models.Model):
    name= models.CharField()
    description = RichTextUploadingField()
    ...

and another model like that:

class TaskLevel(models.Model):
    master_task = models.ForeignKey(GameTask, related_name="sub_levels", on_delete-models.CASCADE)
    sub_tasks = models.ManyToManyField(GameTask, related_name="master_levels")
    ...

So basicly I can have "normal" tasks, but when I create TaskLevel object I can add master_task as a task which gonna fasten other tasks added to sub_tasks field.

My serializers look like:

class TaskBaseSerializer(serializers.ModelSerializer):
    fields created by serializers.SerializerMethodField()
    ...

class TaskLevelSerializer(serializers.ModelSerializer):
    sub_tasks = serializers.SerializerMethodField()

    class Meta:
        model = TaskLevel

    def get_sub_tasks(self, obj: TaskLevel):
        sub_tasks = get_sub_tasks(level=obj, user=self.context["request"].user) # method from other module
        return TaskBaseSerializer(sub_tasks, many=True, context=self.context).data

class TaskSerializer(TaskBaseSerializer):
    levels_config = serializers.SerializerMethodField()

    class Meta:
        model = GameTask

    def get_levels_config(self, obj: GameTask):
        if is_mastertask(obj):
            return dict(
                sub_levels=TaskLevelSerializer(
                    obj.sub_levels.all().order_by("number"), many=True, context=self.context
                ).data,
                progress=get_progress(
                    master_task=obj, user=self.context["request"].user
                ),
            )
        return None

When I tried to measure time it turned out that get_levels_config method takes around 0.25 seconds for one multilevel-task (which contain 7 subtasks). Is there any way to improve this performance? If any more detailed methods are needed I will add them

1 Answer 1

2

Your code might be suffering from N+1 problem. TaskSerializer.get_levels_config() performs database queries from obj.sub_levels.all().order_by("number").

What happens when serializing multiple instances like:

TaskSerializer(tasks, many=True)

each instance calls .get_levels_config()

You can use prefetch_related & selected_related(more explanation here).

You will have to manually check for prefetched objects since you are using SerializerMethodField. There's also the functions get_progress & get_sub_tasks which I assume does another query.


Here are some examples that can be used around your code:

Prefetching:

GameTask.objects.prefetch_related("sub_levels", "master_levels")

# Accessing deeper level
GameTask.objects.prefetch_related(
    "sub_levels",
    "master_levels",
    "sub_levels__sub_tasks",
).select_related(
    "master_levels__master_task",
)

Checking prefetch:

def get_sub_tasks(self, obj: TaskLevel):
    if hasattr(obj, "_prefetched_objects_cache") and obj._prefetched_objects_cache.get("sub_tasks", None):
        return obj._prefetched_objects_cache
    return obj.sub_tasks.all()
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.