Repository: readthedocs/readthedocs.org

Issue:
Profile page performance issue
While working on #5464, I discovered a performance issue on the [profile page](https://readthedocs.org/profiles/davidfischer/).

There appears to be a couple duplicated queries in the profile screen. One is to get maintainers for a project and another is to get the latest build. This results in O(n) SQL queries where n is the number of projects displayed. It should be possible to get this data using an annotation.

Parts of this look similar to #5467 and #5465.

There is also one query per project to generate the "view docs" link but that will probably involve refactoring the resolver. You might want to hold off on that as that is not a *good first issue*. See #3712.


<img width="1157" alt="Screen Shot 2019-03-15 at 11 24 33 AM" src="https://user-images.githubusercontent.com/185043/54453941-96b76f00-4715-11e9-8b9a-fb0433242023.png">



Discussion:
@davidfischer should these issues be worked on after merging #5464 or should i install debug toolbar locally and try to resolve them?
#5464 will help you work on them but it isn't necessary to fix them.

Resolving patch:
diff --git a/readthedocs/core/templatetags/privacy_tags.py b/readthedocs/core/templatetags/privacy_tags.py
--- a/readthedocs/core/templatetags/privacy_tags.py
+++ b/readthedocs/core/templatetags/privacy_tags.py
@@ -3,7 +3,9 @@
 """Template tags to query projects by privacy."""
 
 from django import template
+from django.db.models import Exists, OuterRef, Subquery, Prefetch
 
+from readthedocs.builds.models import Build
 from readthedocs.core.permissions import AdminPermission
 from readthedocs.projects.models import Project
 
@@ -18,9 +20,23 @@ def is_admin(user, project):
 
 @register.simple_tag(takes_context=True)
 def get_public_projects(context, user):
+    # Creates a Subquery object which returns the latest builds 'id'.
+    # Used for optimization purpose.
+    subquery = Subquery(
+        Build.objects.filter(
+            project=OuterRef('project_id')).values_list('id', flat=True)[:1]
+    )
+    # Filters the latest builds of projects.
+    latest_build = Prefetch('builds', Build.objects.filter(
+        pk__in=subquery), to_attr='_latest_build'
+    )
+    # 'Exists()' checks if the project has any good builds.
     projects = Project.objects.for_user_and_viewer(
         user=user,
         viewer=context['request'].user,
+    ).prefetch_related('users', latest_build).annotate(
+        _good_build=Exists(
+            Build.objects.filter(success=True, project=OuterRef('pk')))
     )
     context['public_projects'] = projects
     return ''
diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py
--- a/readthedocs/projects/models.py
+++ b/readthedocs/projects/models.py
@@ -732,6 +732,10 @@ def is_imported(self):
 
     @property
     def has_good_build(self):
+        # Check if there is `_good_build` annotation in the Queryset.
+        # Used for Database optimization.
+        if hasattr(self, '_good_build'):
+            return self._good_build
         return self.builds.filter(success=True).exists()
 
     @property
@@ -855,6 +859,13 @@ def get_latest_build(self, finished=True):
 
         :param finished: Return only builds that are in a finished state
         """
+        # Check if there is `_latest_build` attribute in the Queryset.
+        # Used for Database optimization.
+        if hasattr(self, '_latest_build'):
+            if self._latest_build:
+                return self._latest_build[0]
+            return None
+
         kwargs = {'type': 'html'}
         if finished:
             kwargs['state'] = 'finished'


Test patch: