Merge lp:~wgrant/launchpad/bug-899776 into lp:launchpad

Proposed by William Grant
Status: Merged
Approved by: William Grant
Approved revision: no longer in the source branch.
Merged at revision: 15168
Proposed branch: lp:~wgrant/launchpad/bug-899776
Merge into: lp:launchpad
Diff against target: 167 lines (+79/-22)
2 files modified
lib/lp/bugs/model/bugtasksearch.py (+22/-20)
lib/lp/bugs/tests/test_bugtask_search.py (+57/-2)
To merge this branch: bzr merge lp:~wgrant/launchpad/bug-899776
Reviewer Review Type Date Requested Status
Ian Booth (community) Approve
Review via email: mp+103806@code.launchpad.net

Commit message

Massively optimise bugtasksearch's source package component filtering.

Description of the change

This branch rewrites the package component filtering part of bugtasksearch. Old plan is https://pastebin.canonical.com/65085/, new is https://pastebin.canonical.com/65104/ -- it's about 200x faster. The full improvement is only seen with a new index that is being done in a separate branch, but it's still substantially faster without it.

I changed the SELECT to use the relatively newly denormalised SPPH.sourcepackagename, allowing rapid direct indexing into the table. It's now a normal subquery instead of a CTE, after extensive performance testing. In addition, the archive set is minimised: if we're just searching for main and universe, we don't need to search the partner archive.

To post a comment you must log in.
Revision history for this message
Ian Booth (wallyworld) wrote :

To my very untrained eye wrt package related queries, this looks great but I am relying on our test coverage to determine the absolute correctness of the changes. I assume the new tests are to compliment the existing test coverage?

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/bugs/model/bugtasksearch.py'
2--- lib/lp/bugs/model/bugtasksearch.py 2012-04-26 05:00:19 +0000
3+++ lib/lp/bugs/model/bugtasksearch.py 2012-04-27 06:44:19 +0000
4@@ -91,6 +91,7 @@
5 NULL,
6 )
7 from lp.soyuz.enums import PackagePublishingStatus
8+from lp.soyuz.model.publishing import SourcePackagePublishingHistory
9
10
11 # This abstracts most of the columns involved in search so we can switch
12@@ -737,29 +738,30 @@
13 "distribution or distroseries.")
14
15 if zope_isinstance(params.component, any):
16- component_ids = sqlvalues(*params.component.query_values)
17+ components = params.component.query_values
18 else:
19- component_ids = sqlvalues(params.component)
20-
21- distro_archive_ids = [
22- archive.id
23- for archive in distroseries.distribution.all_distro_archives]
24- with_clauses.append("""spns as (
25- SELECT spr.sourcepackagename
26- FROM SourcePackagePublishingHistory
27- JOIN SourcePackageRelease AS spr ON spr.id =
28- SourcePackagePublishingHistory.sourcepackagerelease AND
29- SourcePackagePublishingHistory.distroseries = %s AND
30- SourcePackagePublishingHistory.archive IN %s AND
31- SourcePackagePublishingHistory.component IN %s AND
32- SourcePackagePublishingHistory.status = %s
33- )""" % sqlvalues(distroseries,
34- distro_archive_ids,
35- component_ids,
36- PackagePublishingStatus.PUBLISHED))
37+ components = [params.component]
38+
39+ # It's much faster to query for a single archive, so don't
40+ # include partner unless we have to.
41+ archive_ids = set(
42+ distroseries.distribution.getArchiveByComponent(c.name).id
43+ for c in components)
44+
45 extra_clauses.append(
46 cols['BugTask.sourcepackagenameID'].is_in(
47- SQL('SELECT sourcepackagename FROM spns')))
48+ Select(
49+ SourcePackagePublishingHistory.sourcepackagenameID,
50+ tables=[SourcePackagePublishingHistory],
51+ where=And(
52+ SourcePackagePublishingHistory.archiveID.is_in(
53+ archive_ids),
54+ SourcePackagePublishingHistory.distroseries ==
55+ distroseries,
56+ SourcePackagePublishingHistory.componentID.is_in(
57+ c.id for c in components),
58+ SourcePackagePublishingHistory.status ==
59+ PackagePublishingStatus.PUBLISHED))))
60
61 upstream_clause = _build_upstream_clause(params, cols)
62 if upstream_clause:
63
64=== modified file 'lib/lp/bugs/tests/test_bugtask_search.py'
65--- lib/lp/bugs/tests/test_bugtask_search.py 2012-04-26 04:53:53 +0000
66+++ lib/lp/bugs/tests/test_bugtask_search.py 2012-04-27 06:44:19 +0000
67@@ -39,6 +39,9 @@
68 any,
69 greater_than,
70 )
71+from lp.soyuz.interfaces.archive import ArchivePurpose
72+from lp.soyuz.interfaces.component import IComponentSet
73+from lp.soyuz.interfaces.publishing import PackagePublishingStatus
74 from lp.testing import (
75 person_logged_in,
76 TestCase,
77@@ -719,6 +722,48 @@
78 self.assertSearchFinds(params, self.bugtasks)
79
80
81+class DistributionAndDistroSeriesTests:
82+ """Tests which are useful for distributions and their series."""
83+
84+ def makeBugInComponent(self, archive, series, component):
85+ pub = self.factory.makeSourcePackagePublishingHistory(
86+ archive=archive, distroseries=series, component=component,
87+ status=PackagePublishingStatus.PUBLISHED)
88+ return self.factory.makeBugTask(
89+ target=self.searchtarget.getSourcePackage(pub.sourcepackagename))
90+
91+ def test_search_by_component(self):
92+ series = self.getCurrentSeries()
93+ distro = series.distribution
94+ self.factory.makeArchive(
95+ distribution=distro, purpose=ArchivePurpose.PARTNER)
96+
97+ main = getUtility(IComponentSet)['main']
98+ main_task = self.makeBugInComponent(
99+ distro.main_archive, series, main)
100+ universe = getUtility(IComponentSet)['universe']
101+ universe_task = self.makeBugInComponent(
102+ distro.main_archive, series, universe)
103+ partner = getUtility(IComponentSet)['partner']
104+ partner_task = self.makeBugInComponent(
105+ distro.getArchiveByComponent('partner'), series, partner)
106+
107+ # Searches for a single component work.
108+ params = self.getBugTaskSearchParams(user=None, component=main)
109+ self.assertSearchFinds(params, [main_task])
110+ params = self.getBugTaskSearchParams(user=None, component=universe)
111+ self.assertSearchFinds(params, [universe_task])
112+
113+ # Non-primary-archive component searches also work.
114+ params = self.getBugTaskSearchParams(user=None, component=partner)
115+ self.assertSearchFinds(params, [partner_task])
116+
117+ # A combination of archives works.
118+ params = self.getBugTaskSearchParams(
119+ user=None, component=any(partner, main))
120+ self.assertSearchFinds(params, [main_task, partner_task])
121+
122+
123 class BugTargetTestBase:
124 """A base class for the bug target mixin classes.
125
126@@ -1062,7 +1107,8 @@
127
128 class DistributionTarget(BugTargetTestBase, ProductAndDistributionTests,
129 BugTargetWithBugSuperVisor,
130- ProjectGroupAndDistributionTests):
131+ ProjectGroupAndDistributionTests,
132+ DistributionAndDistroSeriesTests):
133 """Use a distribution as the bug target."""
134
135 def setUp(self):
136@@ -1087,6 +1133,11 @@
137 """See `ProductAndDistributionTests`."""
138 return self.factory.makeDistroSeries(distribution=self.searchtarget)
139
140+ def getCurrentSeries(self):
141+ if self.searchtarget.currentseries is None:
142+ self.makeSeries()
143+ return self.searchtarget.currentseries
144+
145 def setUpStructuralSubscriptions(self):
146 # See `ProjectGroupAndDistributionTests`.
147 subscriber = self.factory.makePerson()
148@@ -1110,7 +1161,8 @@
149 return self.bugtasks[1:] + self.bugtasks[:1]
150
151
152-class DistroseriesTarget(BugTargetTestBase, ProjectGroupAndDistributionTests):
153+class DistroseriesTarget(BugTargetTestBase, ProjectGroupAndDistributionTests,
154+ DistributionAndDistroSeriesTests):
155 """Use a distro series as the bug target."""
156
157 def setUp(self):
158@@ -1132,6 +1184,9 @@
159 def setBugParamsTarget(self, params, target):
160 params.setDistroSeries(target)
161
162+ def getCurrentSeries(self):
163+ return self.searchtarget
164+
165 def setUpMilestoneSorting(self):
166 with person_logged_in(self.owner):
167 milestone_1 = self.factory.makeMilestone(