Merge ~cjwatson/launchpad:optimize-product-portlet-packages into launchpad:master

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: 1fcdf85900d4be9c46c0f34be66ab11fafd62856
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:optimize-product-portlet-packages
Merge into: launchpad:master
Diff against target: 85 lines (+38/-4)
2 files modified
lib/lp/registry/browser/product.py (+11/-4)
lib/lp/registry/browser/tests/test_product.py (+27/-0)
Reviewer Review Type Date Requested Status
Jürgen Gmach Approve
Review via email: mp+435829@code.launchpad.net

Commit message

Avoid O(n) queries in ProductPackagesPortletView.sourcepackages

Description of the change

While QAing commit 98b4e22ea22f7c0df982523b8bd92778eca39cfd, I was still seeing timeouts, and investigated. A significant amount of the remaining time spent rendering Product:+index is because `ProductPackagesPortletView.sourcepackages` is doing one query to evaluate `sp.currentrelease` for each package corresponding to the project in different distribution series.

The `SourcePackage.currentrelease` property ends up calling `DistroSeriesSet.getCurrentSourceReleases`, which is a bulk operation that can be given lots of packages at once, so call it directly in order to make only one query rather than O(n).

To post a comment you must log in.
Revision history for this message
Jürgen Gmach (jugmac00) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/registry/browser/product.py b/lib/lp/registry/browser/product.py
2index 21f6b80..f90e27a 100644
3--- a/lib/lp/registry/browser/product.py
4+++ b/lib/lp/registry/browser/product.py
5@@ -38,7 +38,7 @@ __all__ = [
6 "ProjectAddStepTwo",
7 ]
8
9-
10+from collections import defaultdict
11 from operator import attrgetter
12 from typing import Type
13 from urllib.parse import urlunsplit
14@@ -140,6 +140,7 @@ from lp.registry.browser.pillar import (
15 PillarViewMixin,
16 )
17 from lp.registry.enums import VCSType
18+from lp.registry.interfaces.distroseries import IDistroSeriesSet
19 from lp.registry.interfaces.ociproject import (
20 OCI_PROJECT_ALLOW_CREATE,
21 IOCIProjectSet,
22@@ -1281,10 +1282,16 @@ class ProductPackagesPortletView(LaunchpadView):
23 @cachedproperty
24 def sourcepackages(self):
25 """The project's latest source packages."""
26+ distro_series_packages = defaultdict(list)
27+ for sp in self.context.sourcepackages:
28+ distro_series_packages[sp.distroseries].append(
29+ sp.sourcepackagename
30+ )
31+ releases = getUtility(IDistroSeriesSet).getCurrentSourceReleases(
32+ distro_series_packages
33+ )
34 current_packages = [
35- sp
36- for sp in self.context.sourcepackages
37- if sp.currentrelease is not None
38+ sp for sp in self.context.sourcepackages if sp in releases
39 ]
40 current_packages.reverse()
41 return current_packages[0:5]
42diff --git a/lib/lp/registry/browser/tests/test_product.py b/lib/lp/registry/browser/tests/test_product.py
43index 61618f0..5a0ec75 100644
44--- a/lib/lp/registry/browser/tests/test_product.py
45+++ b/lib/lp/registry/browser/tests/test_product.py
46@@ -35,6 +35,7 @@ from lp.registry.interfaces.series import SeriesStatus
47 from lp.registry.model.product import Product
48 from lp.services.config import config
49 from lp.services.database.interfaces import IStore
50+from lp.services.propertycache import clear_property_cache
51 from lp.services.webapp.publisher import RedirectionView, canonical_url
52 from lp.services.webapp.servers import WebServiceTestRequest
53 from lp.services.webapp.vhosts import allvhosts
54@@ -798,6 +799,32 @@ class TestProductView(BrowserTestCase):
55 self.assertIn(proprietary_name, browser.contents)
56
57
58+class TestProductPackagesPortletView(TestCaseWithFactory):
59+
60+ layer = DatabaseFunctionalLayer
61+
62+ def test_query_count(self):
63+ # The query count of ProductPackagesPortletView.sourcepackages is
64+ # constant in the number of packages.
65+ product = self.factory.makeProduct()
66+ view = create_initialized_view(product, "+portlet-packages")
67+
68+ def create_packages():
69+ self.factory.makePackagingLink(
70+ productseries=product.development_focus, in_ubuntu=True
71+ )
72+
73+ def get_packages():
74+ clear_property_cache(product)
75+ clear_property_cache(view)
76+ return view.sourcepackages
77+
78+ recorder1, recorder2 = record_two_runs(
79+ get_packages, create_packages, 2
80+ )
81+ self.assertThat(recorder2, HasQueryCount.byEquality(recorder1))
82+
83+
84 class TestProductEditView(BrowserTestCase):
85 """Tests for the ProductEditView"""
86

Subscribers

People subscribed via source and target branches

to status/vote changes: