Merge lp:~jcsackett/launchpad/archivemirror-timeout-618400 into lp:launchpad

Proposed by j.c.sackett
Status: Merged
Approved by: j.c.sackett
Approved revision: no longer in the source branch.
Merged at revision: 12235
Proposed branch: lp:~jcsackett/launchpad/archivemirror-timeout-618400
Merge into: lp:launchpad
Diff against target: 187 lines (+80/-23)
5 files modified
lib/lp/registry/browser/distribution.py (+9/-2)
lib/lp/registry/browser/tests/distribution-views.txt (+16/-0)
lib/lp/registry/interfaces/distribution.py (+8/-0)
lib/lp/registry/model/distribution.py (+39/-18)
lib/lp/registry/model/distributionmirror.py (+8/-3)
To merge this branch: bzr merge lp:~jcsackett/launchpad/archivemirror-timeout-618400
Reviewer Review Type Date Requested Status
Robert Collins (community) Approve
Review via email: mp+46550@code.launchpad.net

Commit message

[r=lifeless][ui=none][bug=618400][incr] Provides a method to preload countries when pulling mirrors by country to remove a timeout on +archivemirrors

Description of the change

Summary
=======

Distribution:+archivemirrors has a timeout caused by a combination of factors: 1) repeated country queries for each distributionmirror that get expensive in aggregate and 2) a very expensive mirror freshness query/calculation.

This branch addresses the first cause; a second branch will need to be landed to address the second cause and close bug 618400.

In the process there is a slight refactor to remove some repeated code.

Proposed fix
============

When getting distribution mirrors, get the countries for the mirrors at the same time, rather than querying for them one by one later.

Preimplementation
=================

Spoke with Curtis Hovey and William Grant about the country query issue.

Implementation
==============

lib/lp/registry/interfaces/distribution.py
lib/lp/registry/model/distribution.py
-------------------------------------

Added a method, _getActiveMirrors, which can generate the resultset for either cdimages or archive mirrors, with or without the country join. Updated the archive_mirrors and cdimage_mirrors properties to use this method. Added an archive_mirrors_by_country and cdimage_mirrors_by_country property that also uses this method, and added them as not-exported CollectionFields to the interface.

lib/lp/registry/browser/distribution.py
---------------------------------------

Updated the +archivemirrors view and +cdmirrors view to use their respective _by_country property. Updated the parent view to use the returned data properly.

lib/lp/registry/model/distributionmirror.py
-------------------------------------------

Drive by reformat of the __all__ clause per style guidelines.

lib/lp/registry/browser/tests/distribution-views.txt
----------------------------------------------------

Updated test to include archive mirrors alongside cdimage.

Tests
=====

bin/test -t distribution-views\.txt
bin/test -t distributionmirror-views\.txt

QA
==

Make certain that Distribution:+archivemirrors loads fine. Then, make certain it doesn't timeout and ceases to be in the timeout reports. The bug itself doesn't indicate how often the timeout occurs, which makes this a touch difficult to QA.

Lint
====

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/registry/browser/distribution.py
  lib/lp/registry/browser/tests/distribution-views.txt
  lib/lp/registry/interfaces/distribution.py
  lib/lp/registry/model/distribution.py
  lib/lp/registry/model/distributionmirror.py

./lib/lp/registry/model/distributionmirror.py
     594: E501 line too long (80 characters)
     460: Line exceeds 78 characters.
     478: Line exceeds 78 characters.
     594: Line exceeds 78 characters.
     812: Line exceeds 78 characters.
     865: Line exceeds 78 characters.

The above lint errors are from a number of queries (not in this branch) that I think are already hard enough to read without odd wrapping changes.

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

A few small comments.

Firstly, rather than selecting wider, consider using the pre_iter_hook on DecoratedResultSet.

This trades off redundant data for a second query with less duplication. It's usually a win.

That said, because you're not exposing the ResultSet we don't even need DecoratedResultSet - so we can just do this:

+ mirrors = list(Store.of(self).find(DistributionMirror,
+ And(
+ DistributionMirror.distribution == self.id,
+ DistributionMirror.content == mirror_content_type,
+ DistributionMirror.enabled == True,
+ DistributionMirror.status == MirrorStatus.OFFICIAL,
+ DistributionMirror.official_candidate == True,
+ )))
+ if not by_country or not mirrors:
+ return mirrors
+ else:
+ list(Store.of(self).find(Country, Country.id.is_in(
+ [mirror.countryID for mirror in mirrors]))

Secondly, the new collections probably want doNotSnapshot on them.

Cheers,
Rob

review: Approve
Revision history for this message
j.c.sackett (jcsackett) wrote :

Rob--

I started making the larger change mentioned above, and I have run into some confusion.

With the list of Countries created in the last line of the diff you provided, I don't see a way to reconcile mirror with country in the form we need down the road, without loading mirror.country in some way to match; I suppose one could bounce around with Country.id and mirror.countryID to get the pairs matched up, but it seems odd.

It occured to me though that perhaps there's some Storm magic here I don't understand; does the process of querying for the countries above lead to the country attribute on DistributionMirror already being populated, so I could just use mirror.country without causing a query down the chain?

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/registry/browser/distribution.py'
2--- lib/lp/registry/browser/distribution.py 2010-12-30 12:50:16 +0000
3+++ lib/lp/registry/browser/distribution.py 2011-01-19 15:54:00 +0000
4@@ -980,7 +980,11 @@
5
6 @cachedproperty
7 def mirrors(self):
8- return self.context.archive_mirrors
9+ return self.context.archive_mirrors_by_country
10+
11+ @cachedproperty
12+ def mirror_count(self):
13+ return len(self.mirrors)
14
15
16 class DistributionSeriesMirrorsView(DistributionMirrorsView):
17@@ -992,8 +996,11 @@
18
19 @cachedproperty
20 def mirrors(self):
21- return self.context.cdimage_mirrors
22+ return self.context.cdimage_mirrors_by_country
23
24+ @cachedproperty
25+ def mirror_count(self):
26+ return len(self.mirrors)
27
28 class DistributionMirrorsRSSBaseView(LaunchpadView):
29 """A base class for RSS feeds of distribution mirrors."""
30
31=== modified file 'lib/lp/registry/browser/tests/distribution-views.txt'
32--- lib/lp/registry/browser/tests/distribution-views.txt 2010-12-16 19:59:20 +0000
33+++ lib/lp/registry/browser/tests/distribution-views.txt 2011-01-19 15:54:00 +0000
34@@ -25,6 +25,22 @@
35 Germany: 1 mirror(s)
36 United Kingdom: 1 mirror(s)
37
38+Lists of archive mirrors
39+------------------------
40+
41+ >>> ubuntu_archivemirrors = create_view(ubuntu, name="+archivemirrors")
42+ >>> country_and_mirrors = (
43+ ... ubuntu_archivemirrors.getMirrorsGroupedByCountry())
44+ >>> for country_and_mirror in country_and_mirrors:
45+ ... country = country_and_mirror['country']
46+ ... mirrors = country_and_mirror['mirrors']
47+ ... for mirror in mirrors:
48+ ... assert mirror.country.name == country
49+ ... print "%s: %d mirror(s)" % (country, len(mirrors))
50+ Antarctica: 1 mirror(s)
51+ France: 2 mirror(s)
52+ United Kingdom: 1 mirror(s)
53+
54
55 Distribution modification views
56 ===============================
57
58=== modified file 'lib/lp/registry/interfaces/distribution.py'
59--- lib/lp/registry/interfaces/distribution.py 2010-12-31 03:39:53 +0000
60+++ lib/lp/registry/interfaces/distribution.py 2011-01-19 15:54:00 +0000
61@@ -239,11 +239,19 @@
62 description=_("All enabled and official ARCHIVE mirrors "
63 "of this Distribution."),
64 readonly=True, value_type=Object(schema=IDistributionMirror))))
65+ archive_mirrors_by_country = doNotSnapshot(CollectionField(
66+ description=_("All enabled and official ARCHIVE mirrors "
67+ "of this Distribution."),
68+ readonly=True, value_type=Object(schema=IDistributionMirror)))
69 cdimage_mirrors = exported(doNotSnapshot(
70 CollectionField(
71 description=_("All enabled and official RELEASE mirrors "
72 "of this Distribution."),
73 readonly=True, value_type=Object(schema=IDistributionMirror))))
74+ cdimage_mirrors_by_country = doNotSnapshot(CollectionField(
75+ description=_("All enabled and official ARCHIVE mirrors "
76+ "of this Distribution."),
77+ readonly=True, value_type=Object(schema=IDistributionMirror)))
78 disabled_mirrors = Attribute(
79 "All disabled and official mirrors of this Distribution.")
80 unofficial_mirrors = Attribute(
81
82=== modified file 'lib/lp/registry/model/distribution.py'
83--- lib/lp/registry/model/distribution.py 2010-12-21 00:26:41 +0000
84+++ lib/lp/registry/model/distribution.py 2011-01-19 15:54:00 +0000
85@@ -19,6 +19,7 @@
86 )
87 from sqlobject.sqlbuilder import SQLConstant
88 from storm.locals import (
89+ And,
90 Desc,
91 Int,
92 Join,
93@@ -155,6 +156,7 @@
94 cachedproperty,
95 get_property_cache,
96 )
97+from lp.services.worlddata.model.country import Country
98 from lp.soyuz.enums import (
99 ArchivePurpose,
100 ArchiveStatus,
101@@ -439,27 +441,46 @@
102 else:
103 return [archive.id]
104
105+ def _getActiveMirrors(self, mirror_content_type, by_country=False):
106+ """Builds the query to get the mirror data for various purposes."""
107+ mirrors = list(Store.of(self).find(
108+ DistributionMirror,
109+ And(
110+ DistributionMirror.distribution == self.id,
111+ DistributionMirror.content == mirror_content_type,
112+ DistributionMirror.enabled == True,
113+ DistributionMirror.status == MirrorStatus.OFFICIAL,
114+ DistributionMirror.official_candidate == True)))
115+ if by_country and mirrors:
116+ #Since country data is needed, fetch countries into the cache.
117+ countries = list(Store.of(self).find(
118+ Country,
119+ Country.id.is_in(mirror.countryID for mirror in mirrors)))
120+ return mirrors
121+
122 @property
123 def archive_mirrors(self):
124 """See `IDistribution`."""
125- return Store.of(self).find(
126- DistributionMirror,
127- distribution=self,
128- content=MirrorContent.ARCHIVE,
129- enabled=True,
130- status=MirrorStatus.OFFICIAL,
131- official_candidate=True)
132-
133- @property
134- def cdimage_mirrors(self):
135- """See `IDistribution`."""
136- return Store.of(self).find(
137- DistributionMirror,
138- distribution=self,
139- content=MirrorContent.RELEASE,
140- enabled=True,
141- status=MirrorStatus.OFFICIAL,
142- official_candidate=True)
143+ return self._getActiveMirrors(MirrorContent.ARCHIVE)
144+
145+ @property
146+ def archive_mirrors_by_country(self):
147+ """See `IDistribution`."""
148+ return self._getActiveMirrors(
149+ MirrorContent.ARCHIVE,
150+ by_country=True)
151+
152+ @property
153+ def cdimage_mirrors(self, by_country=False):
154+ """See `IDistribution`."""
155+ return self._getActiveMirrors(MirrorContent.RELEASE)
156+
157+ @property
158+ def cdimage_mirrors_by_country(self):
159+ """See `IDistribution`."""
160+ return self._getActiveMirrors(
161+ MirrorContent.RELEASE,
162+ by_country=True)
163
164 @property
165 def disabled_mirrors(self):
166
167=== modified file 'lib/lp/registry/model/distributionmirror.py'
168--- lib/lp/registry/model/distributionmirror.py 2010-09-29 14:38:30 +0000
169+++ lib/lp/registry/model/distributionmirror.py 2011-01-19 15:54:00 +0000
170@@ -6,9 +6,14 @@
171 """Module docstring goes here."""
172
173 __metaclass__ = type
174-__all__ = ['DistributionMirror', 'MirrorDistroArchSeries',
175- 'MirrorDistroSeriesSource', 'MirrorProbeRecord',
176- 'DistributionMirrorSet', 'MirrorCDImageDistroSeries']
177+__all__ = [
178+ 'DistributionMirror',
179+ 'MirrorDistroArchSeries',
180+ 'MirrorDistroSeriesSource',
181+ 'MirrorProbeRecord',
182+ 'DistributionMirrorSet',
183+ 'MirrorCDImageDistroSeries',
184+ ]
185
186 from datetime import (
187 datetime,