2017-03-16 1 views
0

Je rencontre de sérieux problèmes de performances avec prefetch_related sur un modèle avec 5 m2m de champs et je préextrait aussi quelques champs m2m imbriqués.Django prefetch_related optimise la requête mais reste très lent

class TaskModelManager(models.Manager): 
    def get_queryset(self): 
     return super(TaskModelManager, self).get_queryset().exclude(internalStatus=2).prefetch_related("parent", "takes", "takes__flags", "assignedUser", "assignedUser__flags", "asset", "asset__flags", "status", "approvalWorkflow", "viewers", "requires", "linkedTasks", "activities") 


class Task(models.Model): 
    uuid = models.UUIDField(primary_key=True, default=genOptimUUID, editable=False) 
    internalStatus = models.IntegerField(default=0) 
    parent = models.ForeignKey("self", blank=True, null=True, related_name="childs") 
    name = models.CharField(max_length=45) 
    taskType = models.ForeignKey("TaskType", null=True) 
    priority = models.IntegerField() 
    startDate = models.DateTimeField() 
    endDate = models.DateTimeField() 
    status = models.ForeignKey("ProgressionStatus") 
    assignedUser = models.ForeignKey("Asset", related_name="tasksAssigned") 
    asset = models.ForeignKey("Asset", related_name="tasksSubject") 
    viewers = models.ManyToManyField("Asset", blank=True, related_name="followedTasks") 
    step = models.ForeignKey("Step", blank=True, null=True, related_name="tasks") 
    approvalWorkflow = models.ForeignKey("ApprovalWorkflow") 
    linkedTasks = models.ManyToManyField("self", symmetrical=False, blank=True, related_name="linkedTo") 
    requires = models.ManyToManyField("self", symmetrical=False, blank=True, related_name="depends") 

    objects = TaskModelManager() 

Le nombre de requête est très bien et le temps de requête de base de données est bien aussi, pour exemple si j'interroger 700 objets de mon modèle j'ai 35 requête et le temps de recherche moyen est de 100 ~ 200 ms, mais le temps de la demande totale est d'environ 8 secondes.

silk times

J'ai couru un certain profilage et il a fait remarquer que plus de 80% du temps passé était l'appel prefetch_related_objects.

profiling

J'utilise Django==1.8.5 et djangorestframework==3.4.6

Je suis ouvert à toute façon d'optimiser cela. Merci d'avance pour votre aide.


Modifier avec select_related:

J'ai essayé l'amélioration proposée par Alasdair

class TaskModelManager(models.Manager): 
    def get_queryset(self): 
     return super(TaskModelManager, self).get_queryset().exclude(internalStatus=2).select_related("parent", "status", "approvalWorkflow", "step").prefetch_related("takes", "takes__flags", "assignedUser", "assignedUser__flags", "asset", "asset__flags", "viewers", "requires", "linkedTasks", "activities") 

Le nouveau résultat est encore 8 secondes pour la demande avec 32 requêtes et 150ms de requête temps.


Edit:

Il semble qu'un ticket a été ouvert sur le suivi des problèmes Django il y a 4 ans et est encore ouvert: https: //code.djangoproject.com/ticket/20577

Répondre

1

Essayez d'utiliser select_related pour les clés étrangères telles que parent et ApprovalWorkflow au lieu de prefetch_related. Lorsque vous utilisez select_related, Django récupère les modèles en utilisant une jointure, contrairement à prefetch_related qui provoque une requête supplémentaire. Vous pourriez trouver que cela améliore les performances.

+0

Ok, j'ai essayé et le résultat n'est pas vraiment meilleur.J'ai 4 requêtes de moins mais le temps global est toujours de 8 secondes. Je pense que le problème est plus dans la logique de 'prefetch_related' au lieu des requêtes sql. –

0

Si la base de données est 150ms mais votre requête est de 8 secondes, ce n'est pas votre requête (en elle-même, au moins). Quelques problèmes possibles:

1) Votre code HTML ou votre modèle est trop complexe et vous passez trop de temps à générer la réponse. Ou considérer template caching.

2) Tous ces objets sont complexes et vous chargez trop de champs, donc si la requête est rapide, l'envoyer et traiter tous ces objets en Python est lent. Explorez using only(), defer() et values ​​() ou value_list() pour charger seulement ce dont vous avez besoin.

L'optimisation est difficile et nous aurions besoin de plus de détails pour vous donner une meilleure idée. Je suggère d'installer Django Debug Toolbar (application Django) ou Opbeat (utilitaire tiers), ils peuvent vous aider à détecter où votre temps est passé et vous pouvez ensuite optimiser en conséquence.