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()