Skip to content

Commit e5f8092

Browse files
authored
Prefetch build and project on version list (#11616)
This optimizes the version listing view and removes redundant queries. - Requires readthedocs/ext-theme#493 - Fixes readthedocs/ext-theme#463
1 parent fd82aeb commit e5f8092

File tree

3 files changed

+46
-2
lines changed

3 files changed

+46
-2
lines changed

readthedocs/builds/models.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,9 @@ class Meta:
207207
unique_together = [("project", "slug")]
208208
ordering = ["-verbose_name"]
209209

210+
# Property used for prefetching version related fields
211+
LATEST_BUILD_CACHE = "_latest_build"
212+
210213
def __str__(self):
211214
return self.verbose_name
212215

@@ -291,6 +294,19 @@ def vcs_url(self):
291294

292295
@property
293296
def last_build(self):
297+
# TODO deprecated in favor of `latest_build`, which matches naming on
298+
# the Project model
299+
return self.latest_build
300+
301+
@property
302+
def latest_build(self):
303+
# Check if there is `_latest_build` prefetch in the Queryset.
304+
# Used for database optimization.
305+
if hasattr(self, self.LATEST_BUILD_CACHE):
306+
if latest_build := getattr(self, self.LATEST_BUILD_CACHE):
307+
return latest_build[0]
308+
return None
309+
294310
return self.builds.order_by("-date").first()
295311

296312
@property

readthedocs/builds/querysets.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
import structlog
55
from django.db import models
6-
from django.db.models import Q
6+
from django.db.models import OuterRef, Prefetch, Q, Subquery
77
from django.utils import timezone
88

99
from readthedocs.builds.constants import (
@@ -141,6 +141,30 @@ def for_reindex(self):
141141
.distinct()
142142
)
143143

144+
def prefetch_subquery(self):
145+
"""
146+
Prefetch related objects via subquery for each version.
147+
148+
.. note::
149+
150+
This should come after any filtering.
151+
"""
152+
from readthedocs.builds.models import Build
153+
154+
# Prefetch the latest build for each project.
155+
subquery_builds = Subquery(
156+
Build.internal.filter(version=OuterRef("version_id"))
157+
.order_by("-date")
158+
.values_list("id", flat=True)[:1]
159+
)
160+
prefetch_builds = Prefetch(
161+
"builds",
162+
Build.internal.filter(pk__in=subquery_builds),
163+
to_attr=self.model.LATEST_BUILD_CACHE,
164+
)
165+
166+
return self.prefetch_related(prefetch_builds)
167+
144168

145169
class VersionQuerySet(SettingsOverrideObject):
146170
_default_class = VersionQuerySetBase

readthedocs/projects/views/public.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,11 @@ def get_context_data(self, **kwargs):
123123
queryset=versions,
124124
project=project,
125125
)
126-
versions = self.get_filtered_queryset()
126+
versions = (
127+
self.get_filtered_queryset()
128+
.prefetch_related("project")
129+
.prefetch_subquery()
130+
)
127131
context["versions"] = versions
128132

129133
protocol = "http"

0 commit comments

Comments
 (0)