Merge lp:~wgrant/launchpad/bug-728246-copy-expiry-queries into lp:launchpad

Proposed by William Grant
Status: Merged
Approved by: William Grant
Approved revision: no longer in the source branch.
Merged at revision: 12522
Proposed branch: lp:~wgrant/launchpad/bug-728246-copy-expiry-queries
Merge into: lp:launchpad
Diff against target: 213 lines (+90/-34)
4 files modified
lib/lp/soyuz/model/binarypackagerelease.py (+11/-3)
lib/lp/soyuz/model/publishing.py (+40/-29)
lib/lp/soyuz/scripts/packagecopier.py (+1/-1)
lib/lp/soyuz/tests/test_publishing.py (+38/-1)
To merge this branch: bzr merge lp:~wgrant/launchpad/bug-728246-copy-expiry-queries
Reviewer Review Type Date Requested Status
Gavin Panella (community) Approve
Review via email: mp+52020@code.launchpad.net

Commit message

[r=allenap][bug=728246] Bring the package copier's expired file check down to constant queries.

Description of the change

This branch starts to address the package copier's scaling problems, making the expired binary check take a constant number of queries (bug #728246).

BinaryPackageRelease.files is now a cached list. getBuiltBinaries has been Stormified, had its prejoin replaced with a separate BPR preload query, and now takes an option to preload the BPFs and LFAs. I added a unit test to ensure that the query count is constant and that the preloading works.

This eliminates two queries per copied binary publication.

To post a comment you must log in.
Revision history for this message
Gavin Panella (allenap) wrote :

Nice.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/soyuz/model/binarypackagerelease.py'
2--- lib/lp/soyuz/model/binarypackagerelease.py 2010-12-24 02:22:11 +0000
3+++ lib/lp/soyuz/model/binarypackagerelease.py 2011-03-03 09:01:49 +0000
4@@ -22,6 +22,7 @@
5 Date,
6 Int,
7 Reference,
8+ Store,
9 Storm,
10 )
11 from zope.interface import implements
12@@ -35,6 +36,10 @@
13 SQLBase,
14 sqlvalues,
15 )
16+from lp.services.propertycache import (
17+ cachedproperty,
18+ get_property_cache,
19+ )
20 from lp.soyuz.enums import (
21 BinaryPackageFileType,
22 BinaryPackageFormat,
23@@ -84,9 +89,6 @@
24 debug_package = ForeignKey(dbName='debug_package',
25 foreignKey='BinaryPackageRelease')
26
27- files = SQLMultipleJoin('BinaryPackageFile',
28- joinColumn='binarypackagerelease', orderBy="libraryfile")
29-
30 _user_defined_fields = StringCol(dbName='user_defined_fields')
31
32 def __init__(self, *args, **kwargs):
33@@ -136,6 +138,11 @@
34 self.binarypackagename)
35 return distroarchseries_binary_package.currentrelease is None
36
37+ @cachedproperty
38+ def files(self):
39+ return list(
40+ Store.of(self).find(BinaryPackageFile, binarypackagerelease=self))
41+
42 def addFile(self, file):
43 """See `IBinaryPackageRelease`."""
44 determined_filetype = None
45@@ -151,6 +158,7 @@
46 raise AssertionError(
47 'Unsupported file type: %s' % file.filename)
48
49+ del get_property_cache(self).files
50 return BinaryPackageFile(binarypackagerelease=self,
51 filetype=determined_filetype,
52 libraryfile=file)
53
54=== modified file 'lib/lp/soyuz/model/publishing.py'
55--- lib/lp/soyuz/model/publishing.py 2011-02-22 22:14:32 +0000
56+++ lib/lp/soyuz/model/publishing.py 2011-03-03 09:01:49 +0000
57@@ -464,40 +464,51 @@
58 for source, binary_pub, binary, binary_name, arch
59 in result_set]
60
61- def getBuiltBinaries(self):
62+ def getBuiltBinaries(self, want_files=False):
63 """See `ISourcePackagePublishingHistory`."""
64- clauses = """
65- BinaryPackagePublishingHistory.binarypackagerelease=
66- BinaryPackageRelease.id AND
67- BinaryPackagePublishingHistory.distroarchseries=
68- DistroArchSeries.id AND
69- BinaryPackageRelease.build=BinaryPackageBuild.id AND
70- BinaryPackageBuild.source_package_release=%s AND
71- DistroArchSeries.distroseries=%s AND
72- BinaryPackagePublishingHistory.archive=%s AND
73- BinaryPackagePublishingHistory.pocket=%s
74- """ % sqlvalues(self.sourcepackagerelease, self.distroseries,
75- self.archive, self.pocket)
76-
77- clauseTables = [
78- 'BinaryPackageBuild', 'BinaryPackageRelease', 'DistroArchSeries']
79- orderBy = ['-BinaryPackagePublishingHistory.id']
80- preJoins = ['binarypackagerelease']
81-
82- results = BinaryPackagePublishingHistory.select(
83- clauses, orderBy=orderBy, clauseTables=clauseTables,
84- prejoins=preJoins)
85- binary_publications = list(results)
86-
87- unique_binary_ids = set(
88- [pub.binarypackagerelease.id for pub in binary_publications])
89+ from lp.soyuz.model.binarypackagebuild import BinaryPackageBuild
90+ from lp.soyuz.model.distroarchseries import DistroArchSeries
91+ binary_publications = list(Store.of(self).find(
92+ BinaryPackagePublishingHistory,
93+ BinaryPackagePublishingHistory.binarypackagereleaseID ==
94+ BinaryPackageRelease.id,
95+ BinaryPackagePublishingHistory.distroarchseriesID ==
96+ DistroArchSeries.id,
97+ BinaryPackagePublishingHistory.archiveID == self.archiveID,
98+ BinaryPackagePublishingHistory.pocket == self.pocket,
99+ BinaryPackageBuild.id == BinaryPackageRelease.buildID,
100+ BinaryPackageBuild.source_package_release_id ==
101+ self.sourcepackagereleaseID,
102+ DistroArchSeries.distroseriesID == self.distroseriesID))
103+
104+ # Preload attached BinaryPackageReleases.
105+ bpr_ids = set(
106+ pub.binarypackagereleaseID for pub in binary_publications)
107+ list(Store.of(self).find(
108+ BinaryPackageRelease, BinaryPackageRelease.id.is_in(bpr_ids)))
109+
110+ if want_files:
111+ # Preload BinaryPackageFiles.
112+ bpfs = list(Store.of(self).find(
113+ BinaryPackageFile,
114+ BinaryPackageFile.binarypackagereleaseID.is_in(bpr_ids)))
115+ bpfs_by_bpr = defaultdict(list)
116+ for bpf in bpfs:
117+ bpfs_by_bpr[bpf.binarypackagerelease].append(bpf)
118+ for bpr in bpfs_by_bpr:
119+ get_property_cache(bpr).files = bpfs_by_bpr[bpr]
120+
121+ # Preload LibraryFileAliases.
122+ lfa_ids = set(bpf.libraryfileID for bpf in bpfs)
123+ list(Store.of(self).find(
124+ LibraryFileAlias, LibraryFileAlias.id.is_in(lfa_ids)))
125
126 unique_binary_publications = []
127 for pub in binary_publications:
128- if pub.binarypackagerelease.id in unique_binary_ids:
129+ if pub.binarypackagerelease.id in bpr_ids:
130 unique_binary_publications.append(pub)
131- unique_binary_ids.remove(pub.binarypackagerelease.id)
132- if len(unique_binary_ids) == 0:
133+ bpr_ids.remove(pub.binarypackagerelease.id)
134+ if len(bpr_ids) == 0:
135 break
136
137 return unique_binary_publications
138
139=== modified file 'lib/lp/soyuz/scripts/packagecopier.py'
140--- lib/lp/soyuz/scripts/packagecopier.py 2011-01-14 14:03:28 +0000
141+++ lib/lp/soyuz/scripts/packagecopier.py 2011-03-03 09:01:49 +0000
142@@ -410,7 +410,7 @@
143 raise CannotCopy('source contains expired files')
144
145 if self.include_binaries:
146- built_binaries = source.getBuiltBinaries()
147+ built_binaries = source.getBuiltBinaries(want_files=True)
148 if len(built_binaries) == 0:
149 raise CannotCopy("source has no binaries to be copied")
150 # Deny copies of binary publications containing files with
151
152=== modified file 'lib/lp/soyuz/tests/test_publishing.py'
153--- lib/lp/soyuz/tests/test_publishing.py 2011-01-15 00:05:55 +0000
154+++ lib/lp/soyuz/tests/test_publishing.py 2011-03-03 09:01:49 +0000
155@@ -11,6 +11,8 @@
156 import tempfile
157
158 import pytz
159+from storm.store import Store
160+from testtools.matchers import Equals
161 import transaction
162 from zope.component import getUtility
163 from zope.security.proxy import removeSecurityProxy
164@@ -58,9 +60,10 @@
165 SourcePackagePublishingHistory,
166 )
167 from lp.testing import (
168- login_as,
169+ StormStatementRecorder,
170 TestCaseWithFactory,
171 )
172+from lp.testing.matchers import HasQueryCount
173 from lp.testing.factory import LaunchpadObjectFactory
174
175
176@@ -1362,3 +1365,37 @@
177 spph = self.factory.makeSourcePackagePublishingHistory(
178 ancestor=ancestor)
179 self.assertEquals(spph.ancestor.displayname, ancestor.displayname)
180+
181+
182+class TestGetBuiltBinaries(TestNativePublishingBase):
183+ """Test SourcePackagePublishingHistory.getBuiltBinaries() works."""
184+
185+ def test_flat_query_count(self):
186+ spph = self.getPubSource(architecturehintlist='any')
187+ store = Store.of(spph)
188+ store.flush()
189+ store.invalidate()
190+
191+ # An initial invocation issues one query for the each of the
192+ # SPPH, BPPHs and BPRs.
193+ with StormStatementRecorder() as recorder:
194+ bins = spph.getBuiltBinaries()
195+ self.assertEquals(0, len(bins))
196+ self.assertThat(recorder, HasQueryCount(Equals(3)))
197+
198+ self.getPubBinaries(pub_source=spph)
199+ store.flush()
200+ store.invalidate()
201+
202+ # A subsequent invocation with files preloaded queries the SPPH,
203+ # BPPHs, BPRs, BPFs and LFAs. Checking the filenames of each
204+ # BPF has no query penalty.
205+ with StormStatementRecorder() as recorder:
206+ bins = spph.getBuiltBinaries(want_files=True)
207+ self.assertEquals(2, len(bins))
208+ for bpph in bins:
209+ files = bpph.binarypackagerelease.files
210+ self.assertEqual(1, len(files))
211+ for bpf in files:
212+ bpf.libraryfile.filename
213+ self.assertThat(recorder, HasQueryCount(Equals(5)))