Merge lp:~rvb/launchpad/activereviews-bug-867941-eagerload2 into lp:launchpad

Proposed by Raphaël Badin
Status: Merged
Approved by: Raphaël Badin
Approved revision: no longer in the source branch.
Merged at revision: 14366
Proposed branch: lp:~rvb/launchpad/activereviews-bug-867941-eagerload2
Merge into: lp:launchpad
Prerequisite: lp:~rvb/launchpad/activereviews-bug-867941-hugequery
Diff against target: 160 lines (+55/-18)
4 files modified
lib/lp/code/browser/tests/test_branchmergeproposallisting.py (+15/-8)
lib/lp/code/model/branchcollection.py (+15/-6)
lib/lp/code/model/diff.py (+21/-1)
lib/lp/testing/factory.py (+4/-3)
To merge this branch: bzr merge lp:~rvb/launchpad/activereviews-bug-867941-eagerload2
Reviewer Review Type Date Requested Status
Gavin Panella (community) Approve
Review via email: mp+82904@code.launchpad.net

Commit message

[r=allenap][bug=867941] Eager load objects related to BMPs.

Description of the change

This branch eager loads more stuff to bring down the number of queries issued by person:+activereviews.

= Tests =

./bin/test -vvc test_branchmergeproposallisting test_activereviews_query_count

= Q/A =

The goal of this change is to reduce the number of queries issued by person:+activereviews. It does that by eager loading objects related to the bmps displayed… ideally, no repeated statement should be issued after this change has landed. Here is the number of queries currently issued by https://code.launchpad.net/~ubuntu-branches/+activereviews and https://code.qastaging.launchpad.net/~ubuntu-branches/+activereviews
qastaging: [678+, 807+, 1068+, 939+, 1056+, 1140+]
prod: [1405, 849+, 777+, 1034+, 639+, 1382+]
(a '+' next to the number means that the whole page timed out and thus the number of queries is not accurate)

To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote :

That remaining query just needs a bit more cachedproperty love - see for instance BranchCollection which does the same trick with backreferences elsewhere in the branch space.

Revision history for this message
Raphaël Badin (rvb) wrote :

> That remaining query just needs a bit more cachedproperty love - see for
> instance BranchCollection which does the same trick with backreferences
> elsewhere in the branch space.

Thanks for helping me out with this. I did not realize the lookup was caused by a security check.
Since the number of storm references in queries plus the number of places where the field is set is greater than the number of places where the field is read, I've decided to keep branch_merge_proposal as the Reference and add a cached property named _branch_merge_proposal.

Revision history for this message
Raphaël Badin (rvb) wrote :

Back to WIP, for consistency, I'll follow lifeless advice to have _branch_merge_proposal be the Reference and branch_merge_proposal the cached property.

Revision history for this message
Raphaël Badin (rvb) wrote :

Done.

Revision history for this message
Gavin Panella (allenap) wrote :

Looks good.

[1]

+ bmps_preview = dict([(bmp.preview_diff_id, bmp) for bmp in bmps])

No need for the intermediate list here.

review: Approve
Revision history for this message
Raphaël Badin (rvb) wrote :

> [1]
>
> + bmps_preview = dict([(bmp.preview_diff_id, bmp) for bmp in bmps])
>
> No need for the intermediate list here.

True. That is something I do very often…

Thanks for the review allenap!

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/code/browser/tests/test_branchmergeproposallisting.py'
2--- lib/lp/code/browser/tests/test_branchmergeproposallisting.py 2011-11-17 09:14:28 +0000
3+++ lib/lp/code/browser/tests/test_branchmergeproposallisting.py 2011-11-22 16:06:28 +0000
4@@ -1,4 +1,4 @@
5-# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
6+# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
7 # GNU Affero General Public License version 3 (see the file LICENSE).
8
9 """Unit tests for BranchMergeProposal listing views."""
10@@ -16,7 +16,10 @@
11
12 from canonical.database.sqlbase import flush_database_caches
13 from canonical.launchpad.webapp.servers import LaunchpadTestRequest
14-from canonical.testing.layers import DatabaseFunctionalLayer
15+from canonical.testing.layers import (
16+ DatabaseFunctionalLayer,
17+ LaunchpadFunctionalLayer,
18+ )
19 from lp.code.browser.branchmergeproposallisting import (
20 ActiveReviewsView,
21 BranchMergeProposalListingItem,
22@@ -388,16 +391,17 @@
23 class PersonActiveReviewsPerformance(TestCaseWithFactory):
24 """Test the performance of the person's active reviews page."""
25
26- layer = DatabaseFunctionalLayer
27+ layer = LaunchpadFunctionalLayer
28
29 def createBMP(self, reviewer=None, target_branch_owner=None):
30 target_branch = None
31 if target_branch_owner is not None:
32- target_branch = self.factory.makeProductBranch(
33+ target_branch = self.factory.makePackageBranch(
34 owner=target_branch_owner)
35 bmp = self.factory.makeBranchMergeProposal(
36 reviewer=reviewer,
37 target_branch=target_branch)
38+ self.factory.makePreviewDiff(merge_proposal=bmp)
39 login_person(bmp.source_branch.owner)
40 bmp.requestReview()
41 return bmp
42@@ -431,9 +435,12 @@
43 # Note that we keep the number of bmps created small (3 and 7)
44 # so that the all the per-cached objects will fit into the cache
45 # used in tests (storm_cache_size: 100).
46- recorder1, view1 = self.createBMPsAndRecordQueries(3)
47- self.assertEqual(3, view1.proposal_count)
48+ base_bmps = 3
49+ added_bmps = 4
50+ recorder1, view1 = self.createBMPsAndRecordQueries(base_bmps)
51+ self.assertEqual(base_bmps, view1.proposal_count)
52 self.addDetail("r1tb", Content(UTF8_TEXT, lambda: [str(recorder1)]))
53- recorder2, view2 = self.createBMPsAndRecordQueries(7)
54- self.assertEqual(7, view2.proposal_count)
55+ recorder2, view2 = self.createBMPsAndRecordQueries(
56+ base_bmps + added_bmps)
57+ self.assertEqual(base_bmps + added_bmps, view2.proposal_count)
58 self.assertThat(recorder2, HasQueryCount(Equals(recorder1.count)))
59
60=== modified file 'lib/lp/code/model/branchcollection.py'
61--- lib/lp/code/model/branchcollection.py 2011-11-19 17:23:00 +0000
62+++ lib/lp/code/model/branchcollection.py 2011-11-22 16:06:28 +0000
63@@ -441,19 +441,28 @@
64 In(BranchMergeProposal.id, result._get_select()))
65
66 def do_eager_load(rows):
67+ # Load the related diffs.
68+ preview_diffs = load_related(
69+ PreviewDiff, rows, ['preview_diff_id'])
70+ PreviewDiff.preloadData(preview_diffs)
71+ load_related(Diff, preview_diffs, ['diff_id'])
72+ # Load related branches.
73 source_branches = load_related(Branch, rows, ['source_branchID'])
74+ target_branches = load_related(Branch, rows, ['target_branchID'])
75+ load_related(Branch, rows, ['prerequisite_branchID'])
76 # Cache person's data (registrants of the proposal and
77- # owners of the source branches).
78+ # owners of the source branches).
79 person_ids = set().union(
80 (proposal.registrantID for proposal in rows),
81 (branch.ownerID for branch in source_branches))
82 list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
83 person_ids, need_validity=True))
84- # Load the source/target branches and preload the data for
85- # these branches.
86- target_branches = load_related(Branch, rows, ['target_branchID'])
87- self._preloadDataForBranches(target_branches + source_branches)
88- load_related(Product, target_branches, ['productID'])
89+ # Preload the data for source/target branches.
90+ branches = target_branches + source_branches
91+ load_related(SourcePackageName, branches, ['sourcepackagenameID'])
92+ load_related(DistroSeries, branches, ['distroseriesID'])
93+ self._preloadDataForBranches(branches)
94+ load_related(Product, branches, ['productID'])
95
96 return DecoratedResultSet(resultset, pre_iter_hook=do_eager_load)
97
98
99=== modified file 'lib/lp/code/model/diff.py'
100--- lib/lp/code/model/diff.py 2011-09-26 06:30:07 +0000
101+++ lib/lp/code/model/diff.py 2011-11-22 16:06:28 +0000
102@@ -50,6 +50,11 @@
103 IPreviewDiff,
104 )
105 from lp.codehosting.bzrutils import read_locked
106+from lp.services.database.bulk import load_referencing
107+from lp.services.propertycache import (
108+ cachedproperty,
109+ get_property_cache,
110+ )
111
112
113 class Diff(SQLBase):
114@@ -342,10 +347,25 @@
115 def has_conflicts(self):
116 return self.conflicts is not None and self.conflicts != ''
117
118- branch_merge_proposal = Reference(
119+ @staticmethod
120+ def preloadData(preview_diffs):
121+ # Circular imports.
122+ from lp.code.model.branchmergeproposal import BranchMergeProposal
123+ bmps = load_referencing(
124+ BranchMergeProposal, preview_diffs, ['preview_diff_id'])
125+ bmps_preview = dict((bmp.preview_diff_id, bmp) for bmp in bmps)
126+ for preview_diff in preview_diffs:
127+ cache = get_property_cache(preview_diff)
128+ cache.branch_merge_proposal = bmps_preview[preview_diff.id]
129+
130+ _branch_merge_proposal = Reference(
131 "PreviewDiff.id", "BranchMergeProposal.preview_diff_id",
132 on_remote=True)
133
134+ @cachedproperty
135+ def branch_merge_proposal(self):
136+ return self._branch_merge_proposal
137+
138 @classmethod
139 def fromBranchMergeProposal(cls, bmp):
140 """Create a `PreviewDiff` from a `BranchMergeProposal`.
141
142=== modified file 'lib/lp/testing/factory.py'
143--- lib/lp/testing/factory.py 2011-11-22 11:26:38 +0000
144+++ lib/lp/testing/factory.py 2011-11-22 16:06:28 +0000
145@@ -1527,11 +1527,12 @@
146 return ProxyFactory(
147 Diff.fromFile(StringIO(diff_text), len(diff_text)))
148
149- def makePreviewDiff(self, conflicts=u''):
150+ def makePreviewDiff(self, conflicts=u'', merge_proposal=None):
151 diff = self.makeDiff()
152- bmp = self.makeBranchMergeProposal()
153+ if merge_proposal is None:
154+ merge_proposal = self.makeBranchMergeProposal()
155 preview_diff = PreviewDiff()
156- preview_diff.branch_merge_proposal = bmp
157+ preview_diff._branch_merge_proposal = merge_proposal
158 preview_diff.conflicts = conflicts
159 preview_diff.diff = diff
160 preview_diff.source_revision_id = self.getUniqueUnicode()