[layerindex-web][PATCH 3/5] layerindex/views.py: fix DuplicatesView timeout

Tim Orling <[email protected]>
Newsgroups org.yoctoproject.lists.yocto-patches
Message-ID <[email protected]>
Two performance problems caused the duplicates page to take over 115
seconds to respond at production data volumes, eventually causing clients
to disconnect (SIGPIPE / broken pipe):

1. Python list comprehension IN clause: each get_recipes/get_classes/
   get_incfiles method evaluated its 'dupes' queryset in Python with a
   list comprehension to build a filter:

     filter(pn__in=[item['pn'] for item in dupes])

   This loaded all matching values into memory and emitted a potentially
   huge SQL IN (...) literal. Replace with a Django ORM subquery by
   passing the queryset directly:

     filter(pn__in=dupes)   # dupes is a .values('pn') queryset

   Django translates this into a SQL subquery (WHERE pn IN (SELECT pn
   FROM ... GROUP BY pn HAVING COUNT(...) > 1)), which the database can
   optimise without a round-trip through Python.

2. N+1 queries: the template renders recipe.layerbranch.layer.name,
   class.layerbranch.layer.name and incfile.layerbranch.layer.name for
   every row. Without select_related these trigger one extra query per
   row. Add select_related('layerbranch__layer') to each queryset so the
   JOIN is done once up front.

[YOCTO #16175]

AI-Generated: Claude Cowork Sonnet 4.6
Signed-off-by: Tim Orling <[email protected]>
---
 layerindex/views.py | 18 ++++++++++++------
 1 file changed, 12 insertions(+), 6 deletions(-)

diff --git a/layerindex/views.py b/layerindex/views.py
index 23bafd0..742e5d5 100644
--- a/layerindex/views.py
+++ b/layerindex/views.py
@@ -656,24 +656,30 @@ class DuplicatesView(TemplateView):
         init_qs = Recipe.objects.filter(layerbranch__branch__name=self.kwargs['branch'])
         if layer_ids:
             init_qs = init_qs.filter(layerbranch__layer__in=layer_ids)
-        dupes = init_qs.values('pn').annotate(Count('layerbranch', distinct=True)).filter(layerbranch__count__gt=1)
-        qs = init_qs.all().filter(pn__in=[item['pn'] for item in dupes]).order_by('pn', 'layerbranch__layer', '-pv')
+        # Use a subquery instead of a Python list comprehension so that Django
+        # emits a single SQL subquery rather than loading all duplicate pn values
+        # into memory and building a potentially huge IN (...) clause.
+        # See: https://bugzilla.yoctoproject.org/show_bug.cgi?id=16175
+        dupes = init_qs.values('pn').annotate(Count('layerbranch', distinct=True)).filter(layerbranch__count__gt=1).values('pn')
+        qs = init_qs.filter(pn__in=dupes).select_related('layerbranch__layer').order_by('pn', 'layerbranch__layer', '-pv')
         return recipes_preferred_count(qs)
 
     def get_classes(self, layer_ids):
         init_qs = BBClass.objects.filter(layerbranch__branch__name=self.kwargs['branch'])
         if layer_ids:
             init_qs = init_qs.filter(layerbranch__layer__in=layer_ids)
-        dupes = init_qs.values('name').annotate(Count('layerbranch', distinct=True)).filter(layerbranch__count__gt=1)
-        qs = init_qs.all().filter(name__in=[item['name'] for item in dupes]).order_by('name', 'layerbranch__layer')
+        # Use a subquery instead of a Python list comprehension (see bug #16175)
+        dupes = init_qs.values('name').annotate(Count('layerbranch', distinct=True)).filter(layerbranch__count__gt=1).values('name')
+        qs = init_qs.filter(name__in=dupes).select_related('layerbranch__layer').order_by('name', 'layerbranch__layer')
         return qs
 
     def get_incfiles(self, layer_ids):
         init_qs = IncFile.objects.filter(layerbranch__branch__name=self.kwargs['branch'])
         if layer_ids:
             init_qs = init_qs.filter(layerbranch__layer__in=layer_ids)
-        dupes = init_qs.values('path').annotate(Count('layerbranch', distinct=True)).filter(layerbranch__count__gt=1)
-        qs = init_qs.all().filter(path__in=[item['path'] for item in dupes]).order_by('path', 'layerbranch__layer')
+        # Use a subquery instead of a Python list comprehension (see bug #16175)
+        dupes = init_qs.values('path').annotate(Count('layerbranch', distinct=True)).filter(layerbranch__count__gt=1).values('path')
+        qs = init_qs.filter(path__in=dupes).select_related('layerbranch__layer').order_by('path', 'layerbranch__layer')
         return qs
 
     def get_context_data(self, **kwargs):
-- 
2.54.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.