Merge lp:~edwin-grubbs/launchpad/bug-638924-milestone-page-eager-loading into lp:launchpad
| Status: | Merged | ||||
|---|---|---|---|---|---|
| Approved by: | Edwin Grubbs on 2010-12-09 | ||||
| Approved revision: | no longer in the source branch. | ||||
| Merged at revision: | 12038 | ||||
| Proposed branch: | lp:~edwin-grubbs/launchpad/bug-638924-milestone-page-eager-loading | ||||
| Merge into: | lp:launchpad | ||||
| Diff against target: |
838 lines (+483/-129) 7 files modified
lib/lp/bugs/interfaces/bugtask.py (+9/-1) lib/lp/bugs/model/bugtask.py (+74/-8) lib/lp/registry/browser/milestone.py (+5/-19) lib/lp/registry/browser/tests/test_milestone.py (+212/-4) lib/lp/registry/interfaces/person.py (+18/-0) lib/lp/registry/model/person.py (+137/-95) lib/lp/registry/tests/test_person.py (+28/-2) |
||||
| To merge this branch: | bzr merge lp:~edwin-grubbs/launchpad/bug-638924-milestone-page-eager-loading | ||||
| Related bugs: |
|
| Reviewer | Review Type | Date Requested | Status |
|---|---|---|---|
| Abel Deuring (community) | code | 2010-12-03 | Approve on 2010-12-03 |
|
Review via email:
|
|||
Commit Message
[r=adeuring]
Description of the Change
Summary
-------
This branch fixes timeouts viewing a distro's milestone +index page with
a ton of bugs. The number of queries will be constant for a milestone
page on a distro or a project. The milestone page for a project group
will make two additional queries for each of its project that has a
milestone with a matching name.
Implementation details
-------
Added search functionality to BugTaskSet.search() to eliminate the need
to call getConjoinedMas
the bug assignees and their validity. (Validity is used by tales to
decide whether the person icon should be grayed out.)
lib/
lib/
lib/
lib/
Moved code for precaching various person attributes from the Person
class into the PersonSet class so that it can be more easily reused.
lib/
lib/
Tests
-----
./bin/test -vv -t 'test_milestone
Demo and Q/A
------------
* Open https:/
* It should not timeout, and the query count should be under 40.
* Open https:/
* It should not timeout, and the query count should be under 80.
* Open https:/
* It should not timeout, and the query count should be under 40.
Lint
----
Checking for conflicts and issues in changed files.
Linting changed files:
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
./lib/lp/
493: E302 expected 2 blank lines, found 1
2095: E302 expected 2 blank lines, found 3
| Edwin Grubbs (edwin-grubbs) wrote : | # |
> Hi Edwin,
>
> this is a very nice branch! I just have a few remarks, nothing serious.
>
>
>
> > + def getPrecachedNon
> > + """See `IBugTaskSet`."""
> > + params = BugTaskSearchPa
> > + user, milestone=
> > + orderby=['status', '-importance', 'id'],
> > + omit_dupes=True, exclude_
> > + non_conjoined_
> > +
> > + def cache_people(rows):
> > + assignee_ids = set(
> > + bug_task.assigneeID for bug_task in rows)
> > + assignees = getUtility(
> > + assignee_ids, need_validity=True)
> > + # Execute query to load storm cache.
> > + list(assignees)
> > +
> > + return DecoratedResultSet(
> > + non_conjoined_
> > +
>
> Preloading the Person records this way is fine, but did you consider
> to use the optional BugTaskSet.search() parameter prejoins? Like so:
>
> self.search(
> params, prejoins=[(Person, LeftJoin(
I thought about it, but I think there are some advantages to not loading a bunch of duplicate data since many of the bugs will share the same assignee. I also needed the Person.
> Seeing that you implemented another prejoin mechanism in PersonSet,
> I get the impression that we should perhaps "normalize" the approaches
> to preload records. But that's something for future branches.
I didn't actually implement that. The code was already there for precaching info in Team.allmembers. I just moved some of the code to PersonSet where it could be reused more easily.
> > +class TestProjectGrou
> > +
> > + layer = DatabaseFunctio
>
> The tests for the different milestone targets a quite similar. Did
> you consider to use a comon base class containing the test methods
> and to setup the milestone, milestone target etc in derived classes?
I have refactored some of the redundancy out, but I left the test methods in each class, since the number of queries may differ for each milestone target type.
I also added a test for PersonSet.
| Edwin Grubbs (edwin-grubbs) wrote : | # |
Here is the incremental diff:
=== modified file 'lib/lp/
--- lib/lp/
+++ lib/lp/
@@ -169,7 +169,39 @@
-class TestProjectMile
+class TestQueryCountB
+
+ def assert_
+ query_limit):
+ # Assert that the number of queries run by view.bugtasks is low.
+ self.add_
+ login_person(
+ view = create_
+ # Eliminate permission check for the admin team from the
+ # recorded queries by loading it now. If the test ever breaks,
+ # the person fixing it won't waste time trying to track this
+ # query down.
+ getUtility(
+ with StormStatementR
+ bugtasks = list(view.bugtasks)
+ self.assertThat
+ self.assertEqua
+
+ def assert_
+ collector = QueryCollector()
+ collector.
+ try:
+ milestone_url = canonical_
+ browser = self.getUserBro
+ browser.
+ self.assertThat
+ finally:
+ # Unregister now in case this method is called multiple
+ # times in a single test.
+ collector.
+
+
+class TestProjectMile
layer = DatabaseFunctio
@@ -182,8 +214,8 @@
'''))
- owner = self.factory.
- self.product = self.factory.
+ self.owner = self.factory.
+ self.product = self.factory.
@@ -206,36 +238,19 @@
# 3. Load links to specifications.
# 4. Load links to branches.
- self.add_
- login_person(
- view = create_
- # Eliminate permission check for the admin team from the
- # recorded queries by loading it now. If the test ever breaks,
- # the person fixing it won't waste time trying to track this
- # query down.
- getUtility(
- with StormStatementR
- bugtasks = list(view.bugtasks)
- self.assertThat
| Abel Deuring (adeuring) wrote : | # |
Hi Edwin,
thanks for the additional changes, r=me again. Just one nitpick:
> @@ -339,56 +354,29 @@
>
> def test_bugtasks_
> # The view.bugtasks attribute will make four queries:
s/four/five/
| Robert Collins (lifeless) wrote : | # |
I have one small comment... you've moved from Store.of(object) to
IStore - this is a problem because it will miss the storm cache if
some page/request determines it needs IMaster explicitly.
You need to pass the store into the precache help you've put on PersonSet.
-Rob
| Edwin Grubbs (edwin-grubbs) wrote : | # |
I have added a "store" parameter to the _getPrecachedPe
| Robert Collins (lifeless) wrote : | # |
Thanks!

Hi Edwin,
this is a very nice branch! I just have a few remarks, nothing serious.
> + def getPrecachedNon ConjoinedBugTas ks(self, user, milestone): rams( milestone, conjoined_ tasks=True) slaves = self.search(params) IPersonSet) .getPrecachedPe rsonsFromIDs( slaves, pre_iter_ hook=cache_ people)
> + """See `IBugTaskSet`."""
> + params = BugTaskSearchPa
> + user, milestone=
> + orderby=['status', '-importance', 'id'],
> + omit_dupes=True, exclude_
> + non_conjoined_
> +
> + def cache_people(rows):
> + assignee_ids = set(
> + bug_task.assigneeID for bug_task in rows)
> + assignees = getUtility(
> + assignee_ids, need_validity=True)
> + # Execute query to load storm cache.
> + list(assignees)
> +
> + return DecoratedResultSet(
> + non_conjoined_
> +
Preloading the Person records this way is fine, but did you consider
to use the optional BugTaskSet.search() parameter prejoins? Like so:
self.search( BugTask. assignee= =Person. id)])
params, prejoins=[(Person, LeftJoin(
Seeing that you implemented another prejoin mechanism in PersonSet,
I get the impression that we should perhaps "normalize" the approaches
to preload records. But that's something for future branches.
> +class TestProjectGrou pMilestoneIndex QueryCount( TestCaseWithFac tory): nalLayer
> +
> + layer = DatabaseFunctio
The tests for the different milestone targets a quite similar. Did
you consider to use a comon base class containing the test methods
and to setup the milestone, milestone target etc in derived classes?