Django Rest Framework: оптимизация производительности сериализаторов nester

У меня проблема с производительностью моей конечной точки, которая возвращает около 40 элементов и ответ занимает около 17 секунд.

У меня есть модель:

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

и еще одна подобная модель:

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")
    ...

По сути, у меня могут быть "обычные" задачи, но когда я создаю объект TaskLevel, я могу добавить master_task как задачу, которая будет прикреплять другие задачи, добавленные в поле sub_tasks.

Мои сериализаторы выглядят следующим образом:

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

Когда я попытался измерить время, оказалось, что метод get_levels_config занимает около 0.25 секунд для одной многоуровневой задачи (которая содержит 7 подзадач). Есть ли способ улучшить эту производительность? Если потребуются более подробные методы, я добавлю их

Ваш код может страдать от проблемы N+1. TaskSerializer.get_levels_config() выполняет запросы к базе данных из obj.sub_levels.all().order_by("number").

Что происходит при сериализации нескольких экземпляров, например:

TaskSerializer(tasks, many=True)

каждый экземпляр вызывает .get_levels_config()

Вы можете использовать prefetch_related & selected_related (более подробное объяснение здесь).

Вам придется вручную проверять наличие префетишированных объектов, поскольку вы используете SerializerMethodField. Есть также функции get_progress & get_sub_tasks, которые, как я предполагаю, выполняют другой запрос.


Вот несколько примеров, которые можно использовать в вашем коде:

Префетчинг:

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",
)

Проверка предварительной выборки:

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()
Вернуться на верх