Merge lp:~abentley/launchpad/efficient-universal-source into lp:launchpad

Proposed by Aaron Bentley on 2012-04-26
Status: Merged
Approved by: j.c.sackett on 2012-04-26
Approved revision: no longer in the source branch.
Merged at revision: 15170
Proposed branch: lp:~abentley/launchpad/efficient-universal-source
Merge into: lp:launchpad
Prerequisite: lp:~abentley/launchpad/celery-everywhere-9
Diff against target: 207 lines (+47/-63)
6 files modified
lib/lp/code/model/tests/test_branchjob.py (+2/-2)
lib/lp/services/job/model/job.py (+19/-54)
lib/lp/services/job/runner.py (+8/-1)
lib/lp/services/job/tests/test_job.py (+12/-6)
lib/lp/translations/model/pofilestatsjob.py (+3/-0)
lib/lp/translations/model/translationsharingjob.py (+3/-0)
To merge this branch: bzr merge lp:~abentley/launchpad/efficient-universal-source
Reviewer Review Type Date Requested Status
j.c.sackett (community) 2012-04-26 Approve on 2012-04-26
Review via email: mp+103768@code.launchpad.net

Commit Message

Optimize UniversalJobSource

Description of the Change

= Summary =
Make UniversalJobSource more efficient

== Proposed fix ==
Provide database user name, database class module and database class name as part of the job_id.

== Pre-implementation notes ==
None

== LOC Rationale ==
Removes code

== Implementation details ==
Provide this database username as part of the "ujob_id". This means that the correct database user can be set immediately, instead of first doing a lookup of the job to determine the database user. It also means that clearStore can be folded into the get method, since it's only done once.

Provide the name and module name of the database job class (one which refers to a Job, e.g. BranchJob) as part of the ujob_id. Combined with providing the dbuser above, this means that getUserAndBaseJob is obsolete. The database table cannot be used for this purpose, because AFAICT Storm permits multiple classes to use a given table.

Extract rawGet from _getDerived

Update getUserAndBaseJob test to use rawGet instead.

Implement getDBClass methods on all derived job classes. This is needed because a few job types don't have a 'context' member which is a database class.

== Tests ==
bin/test --layer=CeleryJobLayer

== Demo and Q/A ==
None

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/services/job/runner.py
  lib/lp/registry/tests/test_membership_notification_job.py
  lib/lp/translations/model/pofilestatsjob.py
  lib/lp/soyuz/model/packagecopyjob.py
  lib/lp/code/model/tests/test_branchjob.py
  lib/lp/translations/model/translationsharingjob.py
  lib/lp/soyuz/tests/test_packagecopyjob.py
  lib/lp/services/job/tests/test_job.py
  lib/lp/answers/model/questionjob.py
  lib/lp/registry/model/persontransferjob.py
  lib/lp/registry/tests/test_person_merge_job.py
  lib/lp/services/job/model/job.py
  lib/lp/answers/tests/test_questionjob.py

To post a comment you must log in.
j.c.sackett (jcsackett) wrote :

This looks good; thanks for removing code.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/code/model/tests/test_branchjob.py'
2--- lib/lp/code/model/tests/test_branchjob.py 2012-04-13 18:31:35 +0000
3+++ lib/lp/code/model/tests/test_branchjob.py 2012-04-26 20:27:38 +0000
4@@ -1247,11 +1247,11 @@
5 commit.writeFile('foo.pot', 'gibberish')
6 with person_logged_in(db_branch.owner):
7 # wait for branch scan
8- with block_on_job():
9+ with block_on_job(self):
10 commit.commit('message')
11 transaction.commit()
12 series = self.factory.makeProductSeries(branch=db_branch)
13- with block_on_job():
14+ with block_on_job(self):
15 RosettaUploadJob.create(
16 commit.db_branch, NULL_REVISION,
17 force_translations_upload=True)
18
19=== modified file 'lib/lp/services/job/model/job.py'
20--- lib/lp/services/job/model/job.py 2012-04-26 20:27:32 +0000
21+++ lib/lp/services/job/model/job.py 2012-04-26 20:27:38 +0000
22@@ -38,7 +38,7 @@
23 from zope.component import getUtility
24 from zope.interface import implements
25
26-from lp.services.config import config, dbconfig
27+from lp.services.config import dbconfig
28 from lp.services.database import bulk
29 from lp.services.database.constants import UTC_NOW
30 from lp.services.database.datetimecol import UtcDateTimeCol
31@@ -258,59 +258,24 @@
32 needs_init = True
33
34 @staticmethod
35- def _getDerived(job_id, base_class):
36- store = IStore(base_class)
37- base_job = store.find(base_class, base_class.job == job_id).one()
38- if base_job is None:
39- return None, None, None
40- return base_job.makeDerived(), base_job.__class__, store
41-
42- @classmethod
43- def getUserAndBaseJob(cls, job_id):
44- """Return the derived branch job associated with the job id."""
45- # Avoid circular imports.
46- from lp.bugs.model.apportjob import ApportJob
47- from lp.code.model.branchjob import (
48- BranchJob,
49- )
50- from lp.code.model.branchmergeproposaljob import (
51- BranchMergeProposalJob,
52- )
53- from lp.registry.model.persontransferjob import PersonTransferJob
54- from lp.answers.model.questionjob import QuestionJob
55- from lp.soyuz.model.distributionjob import DistributionJob
56- from lp.soyuz.model.packagecopyjob import PackageCopyJob
57- from lp.translations.model.pofilestatsjob import POFileStatsJob
58- from lp.translations.model.translationsharingjob import (
59- TranslationSharingJob,
60- )
61- dbconfig.override(
62- dbuser=config.launchpad.dbuser, isolation_level='read_committed')
63-
64- for baseclass in [
65- ApportJob, BranchJob, BranchMergeProposalJob, DistributionJob,
66- PackageCopyJob, PersonTransferJob, POFileStatsJob, QuestionJob,
67- TranslationSharingJob,
68- ]:
69- derived, base_class, store = cls._getDerived(job_id, baseclass)
70- if derived is not None:
71- cls.clearStore(store)
72- return derived.config.dbuser, base_class
73- raise ValueError('No Job with job=%s.' % job_id)
74-
75- @staticmethod
76- def clearStore(store):
77- transaction.abort()
78- getUtility(IZStorm).remove(store)
79- store.close()
80-
81- @classmethod
82- def get(cls, job_id):
83- transaction.abort()
84+ def rawGet(job_id, module_name, class_name):
85+ bc_module = __import__(module_name, fromlist=[class_name])
86+ db_class = getattr(bc_module, class_name)
87+ store = IStore(db_class)
88+ db_job = store.find(db_class, db_class.job == job_id).one()
89+ if db_job is None:
90+ return None
91+ return db_job.makeDerived()
92+
93+ @classmethod
94+ def get(cls, ujob_id):
95 if cls.needs_init:
96+ transaction.abort()
97 scripts.execute_zcml_for_scripts(use_web_security=False)
98 cls.needs_init = False
99- cls.clearStore(IStore(Job))
100- dbuser, base_class = cls.getUserAndBaseJob(job_id)
101- dbconfig.override(dbuser=dbuser, isolation_level='read_committed')
102- return cls._getDerived(job_id, base_class)[0]
103+ transaction.abort()
104+ store = IStore(Job)
105+ getUtility(IZStorm).remove(store)
106+ store.close()
107+ dbconfig.override(dbuser=ujob_id[3], isolation_level='read_committed')
108+ return cls.rawGet(*ujob_id[:3])
109
110=== modified file 'lib/lp/services/job/runner.py'
111--- lib/lp/services/job/runner.py 2012-04-19 20:15:26 +0000
112+++ lib/lp/services/job/runner.py 2012-04-26 20:27:38 +0000
113@@ -200,7 +200,14 @@
114 cls = CeleryRunJobIgnoreResult
115 else:
116 cls = CeleryRunJob
117- return cls.apply_async((self.job_id,), queue=self.task_queue)
118+ db_class = self.getDBClass()
119+ ujob_id = (
120+ self.job_id, db_class.__module__, db_class.__name__,
121+ self.config.dbuser)
122+ return cls.apply_async((ujob_id,), queue=self.task_queue)
123+
124+ def getDBClass(self):
125+ return self.context.__class__
126
127 def celeryCommitHook(self, succeeded):
128 """Hook function to call when a commit completes."""
129
130=== modified file 'lib/lp/services/job/tests/test_job.py'
131--- lib/lp/services/job/tests/test_job.py 2012-04-24 15:04:25 +0000
132+++ lib/lp/services/job/tests/test_job.py 2012-04-26 20:27:38 +0000
133@@ -9,13 +9,12 @@
134 import pytz
135 from lazr.jobrunner.jobrunner import LeaseHeld
136 from storm.locals import Store
137+from testtools.matchers import Equals
138 import transaction
139
140 from lp.code.model.branchmergeproposaljob import (
141- BranchMergeProposalJob,
142 CodeReviewCommentEmailJob,
143 )
144-from lp.services.config import config
145 from lp.services.database.constants import UTC_NOW
146 from lp.services.database.lpstorm import IStore
147 from lp.services.job.interfaces.job import (
148@@ -29,10 +28,12 @@
149 )
150 from lp.services.webapp.testing import verifyObject
151 from lp.testing import (
152+ StormStatementRecorder,
153 TestCase,
154 TestCaseWithFactory,
155 )
156 from lp.testing.layers import ZopelessDatabaseLayer
157+from lp.testing.matchers import HasQueryCount
158
159
160 class TestJob(TestCaseWithFactory):
161@@ -461,9 +462,14 @@
162
163 layer = ZopelessDatabaseLayer
164
165- def test_getUserAndBaseJob_with_merge_proposal_job(self):
166+ def test_rawGet_with_merge_proposal_job(self):
167 comment = self.factory.makeCodeReviewComment()
168 job = CodeReviewCommentEmailJob.create(comment)
169- dbuser, base_class = UniversalJobSource.getUserAndBaseJob(job.job_id)
170- self.assertEqual(dbuser, config.merge_proposal_jobs.dbuser)
171- self.assertEqual(base_class, BranchMergeProposalJob)
172+ job_id = job.job_id
173+ transaction.commit()
174+ with StormStatementRecorder() as recorder:
175+ got_job = UniversalJobSource.rawGet(
176+ job_id, 'lp.code.model.branchmergeproposaljob',
177+ 'BranchMergeProposalJob')
178+ self.assertThat(recorder, HasQueryCount(Equals(1)))
179+ self.assertEqual(got_job, job)
180
181=== modified file 'lib/lp/translations/model/pofilestatsjob.py'
182--- lib/lp/translations/model/pofilestatsjob.py 2012-04-25 14:15:32 +0000
183+++ lib/lp/translations/model/pofilestatsjob.py 2012-04-26 20:27:38 +0000
184@@ -115,6 +115,9 @@
185 """
186 return self
187
188+ def getDBClass(self):
189+ return self.__class__
190+
191
192 def schedule(pofile):
193 """Schedule a job to update a POFile's stats."""
194
195=== modified file 'lib/lp/translations/model/translationsharingjob.py'
196--- lib/lp/translations/model/translationsharingjob.py 2012-04-24 18:26:06 +0000
197+++ lib/lp/translations/model/translationsharingjob.py 2012-04-26 20:27:38 +0000
198@@ -121,6 +121,9 @@
199
200 delegates(ITranslationSharingJob, 'job')
201
202+ def getDBClass(self):
203+ return TranslationSharingJob
204+
205 _event_types = {}
206
207 @property