12652.2.1
by Jeroen Vermeulen
Clean up some FakeLogger-related issues. |
1 |
# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
|
8687.15.18
by Karl Fogel
Add the copyright header block to files under lib/canonical/. |
2 |
# GNU Affero General Public License version 3 (see the file LICENSE).
|
4271.3.2
by Jeroen Vermeulen
Renamed TunedLoop to TunableLoop based on pre-impl chat with BjornT. |
3 |
|
4 |
__metaclass__ = type |
|
5 |
||
7675.599.4
by Graham Binns
Added a first test and a query. O happy day. |
6 |
__all__ = [ |
7 |
'DBLoopTuner', |
|
14560.2.12
by Curtis Hovey
Move looptuner to lp.services. |
8 |
'ITunableLoop', |
7675.599.4
by Graham Binns
Added a first test and a query. O happy day. |
9 |
'LoopTuner', |
10 |
'TunableLoop', |
|
11 |
]
|
|
4271.3.2
by Jeroen Vermeulen
Renamed TunedLoop to TunableLoop based on pre-impl chat with BjornT. |
12 |
|
13 |
||
8083.1.11
by Stuart Bishop
timedelta conversion |
14 |
from datetime import timedelta |
9893.11.3
by Stuart Bishop
Tests for exception handling edge cases |
15 |
import sys |
4271.3.1
by Jeroen Vermeulen
First stab at extracting goal-oriented loop-tuning code |
16 |
import time |
17 |
||
8083.1.10
by Stuart Bishop
Ensure dblooptuner doesn't hold open transactions when blocked. |
18 |
import transaction |
8083.1.4
by Stuart Bishop
Missing imports |
19 |
from zope.component import getUtility |
14560.2.12
by Curtis Hovey
Move looptuner to lp.services. |
20 |
from zope.interface import ( |
14612.2.1
by William Grant
format-imports on lib/. So many imports. |
21 |
implements, |
14560.2.12
by Curtis Hovey
Move looptuner to lp.services. |
22 |
Interface, |
23 |
)
|
|
8083.1.4
by Stuart Bishop
Missing imports |
24 |
|
14565.2.15
by Curtis Hovey
Moved canonical.launchpad.scripts __init__ to lp.services.scripts. |
25 |
import lp.services.scripts |
14600.2.2
by Curtis Hovey
Moved webapp to lp.services. |
26 |
from lp.services.webapp.interfaces import ( |
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
27 |
IStoreSelector, |
28 |
MAIN_STORE, |
|
29 |
MASTER_FLAVOR, |
|
30 |
)
|
|
4271.3.21
by Jeroen Vermeulen
Few more improvements suggested by reviewer. |
31 |
|
4271.3.1
by Jeroen Vermeulen
First stab at extracting goal-oriented loop-tuning code |
32 |
|
14560.2.12
by Curtis Hovey
Move looptuner to lp.services. |
33 |
class ITunableLoop(Interface): |
34 |
"""Interface for self-tuning loop bodies to be driven by LoopTuner.
|
|
35 |
||
36 |
To construct a self-tuning batched loop, define your loop body as a class
|
|
37 |
implementing TunableLoop, and pass an instance to your LoopTuner.
|
|
38 |
"""
|
|
39 |
def isDone(): |
|
40 |
"""Is this loop finished?
|
|
41 |
||
42 |
Once this returns True, the LoopTuner will no longer touch this
|
|
43 |
object.
|
|
44 |
"""
|
|
45 |
||
46 |
def __call__(chunk_size): |
|
47 |
"""Perform an iteration of the loop.
|
|
48 |
||
49 |
The chunk_size parameter says (in some way you define) how much work
|
|
50 |
the LoopTuner believes you should try to do in this iteration in order
|
|
51 |
to get as close as possible to your time goal.
|
|
14625.2.6
by Colin Watson
Document that chunk_size is a float. |
52 |
|
53 |
Note that chunk_size is a float, so, for example, if you use it to
|
|
54 |
slice a list, be careful to round it to an int first.
|
|
14560.2.12
by Curtis Hovey
Move looptuner to lp.services. |
55 |
"""
|
56 |
||
57 |
def cleanUp(self): |
|
58 |
"""Clean up any open resources.
|
|
59 |
||
60 |
Optional.
|
|
61 |
||
62 |
This method is needed because loops may be aborted before
|
|
63 |
completion, so clean up code in the isDone() method may
|
|
64 |
never be invoked.
|
|
65 |
"""
|
|
66 |
||
67 |
||
4271.3.1
by Jeroen Vermeulen
First stab at extracting goal-oriented loop-tuning code |
68 |
class LoopTuner: |
69 |
"""A loop that tunes itself to approximate an ideal time per iteration.
|
|
70 |
||
71 |
Use this for large processing jobs that need to be broken down into chunks
|
|
72 |
of such size that processing a single chunk takes approximately a given
|
|
73 |
ideal time. For example, large database operations may have to be
|
|
74 |
performed and committed in a large number of small steps in order to avoid
|
|
75 |
locking out other clients that need to access the same data. Regular
|
|
76 |
commits allow other clients to get their work done.
|
|
77 |
||
78 |
In such a situation, committing for every step is often far too costly.
|
|
79 |
Imagine inserting a million rows and committing after every row! You
|
|
80 |
could hand-pick a static number of steps per commit, but that takes a lot
|
|
81 |
of experimental guesswork and it will still waste time when things go
|
|
82 |
well, and on the other hand, it will still end up taking too much time per
|
|
83 |
batch when the system slows down for whatever reason.
|
|
84 |
||
4271.3.16
by Jeroen Vermeulen
More feedback from the review team; made TunableLoop an interface; replaced performChunk() with __call__() |
85 |
Instead, define your loop body in an ITunableLoop; parameterize it on the
|
86 |
number of steps per batch; say how much time you'd like to spend per
|
|
87 |
batch; and pass it to a LoopTuner. The LoopTuner will execute your loop,
|
|
88 |
dynamically tuning its batch-size parameter to stay close to your time
|
|
89 |
goal. If things go faster than expected, it will ask your loop body to do
|
|
90 |
more work for the next batch. If a batch takes too much time, the next
|
|
91 |
batch will be smaller. There is also some cushioning for one-off spikes
|
|
92 |
and troughs in processing speed.
|
|
4271.3.1
by Jeroen Vermeulen
First stab at extracting goal-oriented loop-tuning code |
93 |
"""
|
94 |
||
8870.1.2
by Stuart Bishop
Add abort times to loop tuner and garbo |
95 |
def __init__( |
96 |
self, operation, goal_seconds, |
|
97 |
minimum_chunk_size=1, maximum_chunk_size=1000000000, |
|
98 |
abort_time=None, cooldown_time=None, log=None): |
|
4271.3.1
by Jeroen Vermeulen
First stab at extracting goal-oriented loop-tuning code |
99 |
"""Initialize a loop, to be run to completion at most once.
|
100 |
||
101 |
Parameters:
|
|
102 |
||
4271.3.21
by Jeroen Vermeulen
Few more improvements suggested by reviewer. |
103 |
operation: an object implementing the loop body. It must support the
|
104 |
ITunableLoop interface.
|
|
4271.3.1
by Jeroen Vermeulen
First stab at extracting goal-oriented loop-tuning code |
105 |
|
4271.3.15
by Jeroen Vermeulen
Changes based on review by bac. |
106 |
goal_seconds: the ideal number of seconds for any one iteration to
|
107 |
take. The algorithm will vary chunk size in order to stick close
|
|
108 |
to this ideal.
|
|
4271.3.1
by Jeroen Vermeulen
First stab at extracting goal-oriented loop-tuning code |
109 |
|
110 |
minimum_chunk_size: the smallest chunk size that is reasonable. The
|
|
111 |
tuning algorithm will never let chunk size sink below this value.
|
|
4271.3.15
by Jeroen Vermeulen
Changes based on review by bac. |
112 |
|
113 |
maximum_chunk_size: the largest allowable chunk size. A maximum is
|
|
4271.3.16
by Jeroen Vermeulen
More feedback from the review team; made TunableLoop an interface; replaced performChunk() with __call__() |
114 |
needed even if the ITunableLoop ignores chunk size for whatever
|
4271.3.15
by Jeroen Vermeulen
Changes based on review by bac. |
115 |
reason, since reaching floating-point infinity would seriously
|
116 |
break the algorithm's arithmetic.
|
|
8083.1.14
by Stuart Bishop
Lower loglevel used by LoopTuner |
117 |
|
7667.12.12
by Danilo Šegan
Clean up LoopTuner and provide cooldown_time. |
118 |
cooldown_time: time (in seconds, float) to sleep between consecutive
|
119 |
operation runs. Defaults to None for no sleep.
|
|
8083.1.14
by Stuart Bishop
Lower loglevel used by LoopTuner |
120 |
|
8870.1.2
by Stuart Bishop
Add abort times to loop tuner and garbo |
121 |
abort_time: abort the loop, logging a WARNING message, if the runtime
|
122 |
takes longer than this many seconds.
|
|
123 |
||
8083.1.14
by Stuart Bishop
Lower loglevel used by LoopTuner |
124 |
log: The log object to use. DEBUG level messages are logged
|
125 |
giving iteration statistics.
|
|
4271.3.1
by Jeroen Vermeulen
First stab at extracting goal-oriented loop-tuning code |
126 |
"""
|
4271.3.21
by Jeroen Vermeulen
Few more improvements suggested by reviewer. |
127 |
assert(ITunableLoop.providedBy(operation)) |
4271.3.1
by Jeroen Vermeulen
First stab at extracting goal-oriented loop-tuning code |
128 |
self.operation = operation |
4271.3.15
by Jeroen Vermeulen
Changes based on review by bac. |
129 |
self.goal_seconds = float(goal_seconds) |
4271.3.1
by Jeroen Vermeulen
First stab at extracting goal-oriented loop-tuning code |
130 |
self.minimum_chunk_size = minimum_chunk_size |
4271.3.8
by Jeroen Vermeulen
Make chunk_size a float (but round it in MultiTableCopy!), lots more tests |
131 |
self.maximum_chunk_size = maximum_chunk_size |
7667.12.12
by Danilo Šegan
Clean up LoopTuner and provide cooldown_time. |
132 |
self.cooldown_time = cooldown_time |
8870.1.2
by Stuart Bishop
Add abort times to loop tuner and garbo |
133 |
self.abort_time = abort_time |
8083.1.6
by Stuart Bishop
Fix logging |
134 |
if log is None: |
14565.2.15
by Curtis Hovey
Moved canonical.launchpad.scripts __init__ to lp.services.scripts. |
135 |
self.log = lp.services.scripts.log |
8083.1.6
by Stuart Bishop
Fix logging |
136 |
else: |
137 |
self.log = log |
|
4271.3.1
by Jeroen Vermeulen
First stab at extracting goal-oriented loop-tuning code |
138 |
|
7675.670.2
by Stuart Bishop
Make timeout flag sticky, so loop bodies that detect things will timeout will cause the loop to terminate rather than spin |
139 |
# True if this task has timed out. Set by _isTimedOut().
|
140 |
_has_timed_out = False |
|
141 |
||
8758.2.16
by Stuart Bishop
Review feedback |
142 |
def _isTimedOut(self, extra_seconds=0): |
7675.670.2
by Stuart Bishop
Make timeout flag sticky, so loop bodies that detect things will timeout will cause the loop to terminate rather than spin |
143 |
"""Return True if the task will be timed out in extra_seconds.
|
144 |
||
145 |
If this method returns True, all future calls will also return
|
|
146 |
True.
|
|
147 |
"""
|
|
148 |
if self.abort_time is None: |
|
149 |
return False |
|
150 |
if self._has_timed_out: |
|
151 |
return True |
|
152 |
if self._time() + extra_seconds >= self.start_time + self.abort_time: |
|
153 |
self._has_timed_out = True |
|
154 |
return True |
|
155 |
return False |
|
8758.2.15
by Stuart Bishop
Let looptuner jobs abort if they timeout during the cooldown, which might be hours in the case of dblooptuner waiting for replication lag to clear |
156 |
|
7667.12.12
by Danilo Šegan
Clean up LoopTuner and provide cooldown_time. |
157 |
def run(self): |
4271.3.1
by Jeroen Vermeulen
First stab at extracting goal-oriented loop-tuning code |
158 |
"""Run the loop to completion."""
|
9893.11.2
by Stuart Bishop
Better LoopTuner exception logging |
159 |
# Cleanup function, if we have one.
|
160 |
cleanup = getattr(self.operation, 'cleanUp', lambda: None) |
|
8758.2.35
by Stuart Bishop
Add optional cleanUp method to ITunableLoop |
161 |
try: |
162 |
chunk_size = self.minimum_chunk_size |
|
163 |
iteration = 0 |
|
164 |
total_size = 0 |
|
165 |
self.start_time = self._time() |
|
166 |
last_clock = self.start_time |
|
167 |
while not self.operation.isDone(): |
|
168 |
||
169 |
if self._isTimedOut(): |
|
170 |
self.log.warn( |
|
8758.3.2
by Stuart Bishop
Fix bad debug2 format string in looptuner, and make logging consistent |
171 |
"Task aborted after %d seconds.", self.abort_time) |
8758.2.35
by Stuart Bishop
Add optional cleanUp method to ITunableLoop |
172 |
break
|
173 |
||
174 |
self.operation(chunk_size) |
|
175 |
||
176 |
new_clock = self._time() |
|
177 |
time_taken = new_clock - last_clock |
|
178 |
last_clock = new_clock |
|
12652.2.3
by Jeroen Vermeulen
Should call log.debug2, not log.log(DEBUG2, ...); XXX comment for later. |
179 |
|
9893.6.44
by Stuart Bishop
Revert workarounds to Bug #741650 |
180 |
self.log.debug2( |
12652.2.1
by Jeroen Vermeulen
Clean up some FakeLogger-related issues. |
181 |
"Iteration %d (size %.1f): %.3f seconds", |
182 |
iteration, chunk_size, time_taken) |
|
8758.2.35
by Stuart Bishop
Add optional cleanUp method to ITunableLoop |
183 |
|
184 |
last_clock = self._coolDown(last_clock) |
|
185 |
||
186 |
total_size += chunk_size |
|
187 |
||
188 |
# Adjust parameter value to approximate goal_seconds.
|
|
189 |
# The new value is the average of two numbers: the
|
|
190 |
# previous value, and an estimate of how many rows would
|
|
191 |
# take us to exactly goal_seconds seconds. The weight in
|
|
192 |
# this estimate of any given historic measurement decays
|
|
193 |
# exponentially with an exponent of 1/2. This softens
|
|
194 |
# the blows from spikes and dips in processing time. Set
|
|
195 |
# a reasonable minimum for time_taken, just in case we
|
|
196 |
# get weird values for whatever reason and destabilize
|
|
197 |
# the algorithm.
|
|
13980.2.1
by Jeroen Vermeulen
Lint. Lots of lint. |
198 |
time_taken = max(self.goal_seconds / 10, time_taken) |
199 |
chunk_size *= (1 + self.goal_seconds / time_taken) / 2 |
|
8758.2.35
by Stuart Bishop
Add optional cleanUp method to ITunableLoop |
200 |
chunk_size = max(chunk_size, self.minimum_chunk_size) |
201 |
chunk_size = min(chunk_size, self.maximum_chunk_size) |
|
202 |
iteration += 1 |
|
203 |
||
204 |
total_time = last_clock - self.start_time |
|
13980.2.1
by Jeroen Vermeulen
Lint. Lots of lint. |
205 |
average_size = total_size / max(1, iteration) |
206 |
average_speed = total_size / max(1, total_time) |
|
9893.6.44
by Stuart Bishop
Revert workarounds to Bug #741650 |
207 |
self.log.debug2( |
8758.3.2
by Stuart Bishop
Fix bad debug2 format string in looptuner, and make logging consistent |
208 |
"Done. %d items in %d iterations, %3f seconds, " |
12652.2.1
by Jeroen Vermeulen
Clean up some FakeLogger-related issues. |
209 |
"average size %f (%s/s)", |
210 |
total_size, iteration, total_time, average_size, |
|
211 |
average_speed) |
|
9893.11.2
by Stuart Bishop
Better LoopTuner exception logging |
212 |
except Exception: |
9893.11.3
by Stuart Bishop
Tests for exception handling edge cases |
213 |
exc_info = sys.exc_info() |
9893.11.2
by Stuart Bishop
Better LoopTuner exception logging |
214 |
try: |
215 |
cleanup() |
|
216 |
except Exception: |
|
9893.11.3
by Stuart Bishop
Tests for exception handling edge cases |
217 |
# We need to raise the original exception, but we don't
|
218 |
# want to lose the information about the cleanup
|
|
219 |
# failure, so log it.
|
|
9893.11.2
by Stuart Bishop
Better LoopTuner exception logging |
220 |
self.log.exception("Unhandled exception in cleanUp") |
9893.11.3
by Stuart Bishop
Tests for exception handling edge cases |
221 |
# Reraise the original exception.
|
222 |
raise exc_info[0], exc_info[1], exc_info[2] |
|
223 |
else: |
|
9893.11.2
by Stuart Bishop
Better LoopTuner exception logging |
224 |
cleanup() |
4271.3.1
by Jeroen Vermeulen
First stab at extracting goal-oriented loop-tuning code |
225 |
|
7667.12.12
by Danilo Šegan
Clean up LoopTuner and provide cooldown_time. |
226 |
def _coolDown(self, bedtime): |
227 |
"""Sleep for `self.cooldown_time` seconds, if set.
|
|
228 |
||
229 |
Assumes that anything the main LoopTuner loop does apart from
|
|
230 |
doing a chunk of work or sleeping takes zero time.
|
|
231 |
||
232 |
:param bedtime: Time the cooldown started, i.e. the time the
|
|
233 |
chunk of real work was completed.
|
|
234 |
:return: Time when cooldown completed, i.e. the starting time
|
|
235 |
for a next chunk of work.
|
|
236 |
"""
|
|
237 |
if self.cooldown_time is None or self.cooldown_time <= 0.0: |
|
238 |
return bedtime |
|
239 |
else: |
|
8758.2.16
by Stuart Bishop
Review feedback |
240 |
self._sleep(self.cooldown_time) |
7667.12.12
by Danilo Šegan
Clean up LoopTuner and provide cooldown_time. |
241 |
return self._time() |
242 |
||
4271.3.4
by Jeroen Vermeulen
Hook for tests to fake time.time(), thanks BjornT! |
243 |
def _time(self): |
244 |
"""Monotonic system timer with unit of 1 second.
|
|
245 |
||
246 |
Overridable so tests can fake processing speeds accurately and without
|
|
247 |
actually waiting.
|
|
248 |
"""
|
|
249 |
return time.time() |
|
250 |
||
8758.2.16
by Stuart Bishop
Review feedback |
251 |
def _sleep(self, seconds): |
252 |
"""Sleep.
|
|
253 |
||
254 |
If the sleep interval would put us over the tasks timeout,
|
|
255 |
do nothing.
|
|
256 |
"""
|
|
8758.2.20
by Stuart Bishop
Sleep the correct amount of time |
257 |
if not self._isTimedOut(seconds): |
258 |
time.sleep(seconds) |
|
8758.2.16
by Stuart Bishop
Review feedback |
259 |
|
8083.1.1
by Stuart Bishop
DB specific looptuner that plays nicer with PostgreSQL |
260 |
|
8083.1.11
by Stuart Bishop
timedelta conversion |
261 |
def timedelta_to_seconds(td): |
262 |
return 24 * 60 * td.days + td.seconds |
|
263 |
||
264 |
||
8083.1.1
by Stuart Bishop
DB specific looptuner that plays nicer with PostgreSQL |
265 |
class DBLoopTuner(LoopTuner): |
8083.1.14
by Stuart Bishop
Lower loglevel used by LoopTuner |
266 |
"""A LoopTuner that plays well with PostgreSQL and replication.
|
267 |
||
268 |
This LoopTuner blocks when database replication is lagging.
|
|
269 |
Making updates faster than replication can deal with them is
|
|
270 |
counter productive and in extreme cases can put the database
|
|
271 |
into a death spiral. So we don't do that.
|
|
272 |
||
273 |
This LoopTuner also blocks when there are long running
|
|
274 |
transactions. Vacuuming is ineffective when there are long
|
|
275 |
running transactions. We block when long running transactions
|
|
276 |
have been detected, as it means we have already been bloating
|
|
277 |
the database for some time and we shouldn't make it worse. Once
|
|
278 |
the long running transactions have completed, we know the dead
|
|
279 |
space we have already caused can be cleaned up so we can keep
|
|
280 |
going.
|
|
281 |
||
282 |
INFO level messages are logged when the DBLoopTuner blocks in addition
|
|
283 |
to the DEBUG level messages emitted by the standard LoopTuner.
|
|
284 |
"""
|
|
8083.1.1
by Stuart Bishop
DB specific looptuner that plays nicer with PostgreSQL |
285 |
|
286 |
# We block until replication lag is under this threshold.
|
|
13980.2.1
by Jeroen Vermeulen
Lint. Lots of lint. |
287 |
acceptable_replication_lag = timedelta(seconds=30) # In seconds. |
8083.1.1
by Stuart Bishop
DB specific looptuner that plays nicer with PostgreSQL |
288 |
|
289 |
# We block if there are transactions running longer than this threshold.
|
|
13980.2.1
by Jeroen Vermeulen
Lint. Lots of lint. |
290 |
long_running_transaction = 30 * 60 # In seconds. |
8083.1.1
by Stuart Bishop
DB specific looptuner that plays nicer with PostgreSQL |
291 |
|
292 |
def _blockWhenLagged(self): |
|
293 |
"""When database replication lag is high, block until it drops."""
|
|
294 |
# Lag is most meaningful on the master.
|
|
295 |
store = getUtility(IStoreSelector).get(MAIN_STORE, MASTER_FLAVOR) |
|
7675.670.2
by Stuart Bishop
Make timeout flag sticky, so loop bodies that detect things will timeout will cause the loop to terminate rather than spin |
296 |
msg_counter = 0 |
8758.2.16
by Stuart Bishop
Review feedback |
297 |
while not self._isTimedOut(): |
8083.1.1
by Stuart Bishop
DB specific looptuner that plays nicer with PostgreSQL |
298 |
lag = store.execute("SELECT replication_lag()").get_one()[0] |
7675.670.2
by Stuart Bishop
Make timeout flag sticky, so loop bodies that detect things will timeout will cause the loop to terminate rather than spin |
299 |
if lag is None or lag <= self.acceptable_replication_lag: |
8083.1.1
by Stuart Bishop
DB specific looptuner that plays nicer with PostgreSQL |
300 |
return
|
301 |
||
7675.670.2
by Stuart Bishop
Make timeout flag sticky, so loop bodies that detect things will timeout will cause the loop to terminate rather than spin |
302 |
# Report just once every 10 minutes to avoid log spam.
|
303 |
msg_counter += 1 |
|
304 |
if msg_counter % 60 == 1: |
|
305 |
self.log.info( |
|
306 |
"Database replication lagged %s. " |
|
8758.3.2
by Stuart Bishop
Fix bad debug2 format string in looptuner, and make logging consistent |
307 |
"Sleeping up to 10 minutes.", lag) |
8083.1.1
by Stuart Bishop
DB specific looptuner that plays nicer with PostgreSQL |
308 |
|
13980.2.1
by Jeroen Vermeulen
Lint. Lots of lint. |
309 |
# Don't become a long running transaction!
|
310 |
transaction.abort() |
|
7675.670.2
by Stuart Bishop
Make timeout flag sticky, so loop bodies that detect things will timeout will cause the loop to terminate rather than spin |
311 |
self._sleep(10) |
8083.1.1
by Stuart Bishop
DB specific looptuner that plays nicer with PostgreSQL |
312 |
|
313 |
def _blockForLongRunningTransactions(self): |
|
314 |
"""If there are long running transactions, block to avoid making
|
|
315 |
bloat worse."""
|
|
8758.2.16
by Stuart Bishop
Review feedback |
316 |
if self.long_running_transaction is None: |
8083.1.1
by Stuart Bishop
DB specific looptuner that plays nicer with PostgreSQL |
317 |
return
|
318 |
store = getUtility(IStoreSelector).get(MAIN_STORE, MASTER_FLAVOR) |
|
8758.2.16
by Stuart Bishop
Review feedback |
319 |
msg_counter = 0 |
320 |
while not self._isTimedOut(): |
|
8083.1.1
by Stuart Bishop
DB specific looptuner that plays nicer with PostgreSQL |
321 |
results = list(store.execute(""" |
322 |
SELECT
|
|
323 |
CURRENT_TIMESTAMP - xact_start,
|
|
324 |
procpid,
|
|
325 |
usename,
|
|
326 |
datname,
|
|
327 |
current_query
|
|
7675.177.3
by Stuart Bishop
Make DBLoopTuner look up current activity via a SECURITY DEFINER wrapper to pg_stat_activity, so it can detect long running transactions when not running as a superuser |
328 |
FROM activity()
|
8083.1.1
by Stuart Bishop
DB specific looptuner that plays nicer with PostgreSQL |
329 |
WHERE xact_start < CURRENT_TIMESTAMP - interval '%f seconds' |
8666.1.1
by Stuart Bishop
Transaction ids are now per-database, so no need to block on long running transactions in other databases |
330 |
AND datname = current_database()
|
7675.670.2
by Stuart Bishop
Make timeout flag sticky, so loop bodies that detect things will timeout will cause the loop to terminate rather than spin |
331 |
ORDER BY xact_start LIMIT 4
|
8083.1.1
by Stuart Bishop
DB specific looptuner that plays nicer with PostgreSQL |
332 |
""" % self.long_running_transaction).get_all()) |
8083.1.5
by Stuart Bishop
Typo |
333 |
if not results: |
8083.1.1
by Stuart Bishop
DB specific looptuner that plays nicer with PostgreSQL |
334 |
break
|
8758.2.15
by Stuart Bishop
Let looptuner jobs abort if they timeout during the cooldown, which might be hours in the case of dblooptuner waiting for replication lag to clear |
335 |
|
7675.670.2
by Stuart Bishop
Make timeout flag sticky, so loop bodies that detect things will timeout will cause the loop to terminate rather than spin |
336 |
# Check for long running transactions every 10 seconds, but
|
8758.2.15
by Stuart Bishop
Let looptuner jobs abort if they timeout during the cooldown, which might be hours in the case of dblooptuner waiting for replication lag to clear |
337 |
# only report every 10 minutes to avoid log spam.
|
8758.2.16
by Stuart Bishop
Review feedback |
338 |
msg_counter += 1 |
7675.670.2
by Stuart Bishop
Make timeout flag sticky, so loop bodies that detect things will timeout will cause the loop to terminate rather than spin |
339 |
if msg_counter % 60 == 1: |
8758.2.15
by Stuart Bishop
Let looptuner jobs abort if they timeout during the cooldown, which might be hours in the case of dblooptuner waiting for replication lag to clear |
340 |
for runtime, procpid, usename, datname, query in results: |
341 |
self.log.info( |
|
8758.3.2
by Stuart Bishop
Fix bad debug2 format string in looptuner, and make logging consistent |
342 |
"Blocked on %s old xact %s@%s/%d - %s.", |
343 |
runtime, usename, datname, procpid, query) |
|
8758.2.15
by Stuart Bishop
Let looptuner jobs abort if they timeout during the cooldown, which might be hours in the case of dblooptuner waiting for replication lag to clear |
344 |
self.log.info("Sleeping for up to 10 minutes.") |
13980.2.1
by Jeroen Vermeulen
Lint. Lots of lint. |
345 |
# Don't become a long running transaction!
|
346 |
transaction.abort() |
|
7675.670.2
by Stuart Bishop
Make timeout flag sticky, so loop bodies that detect things will timeout will cause the loop to terminate rather than spin |
347 |
self._sleep(10) |
8083.1.1
by Stuart Bishop
DB specific looptuner that plays nicer with PostgreSQL |
348 |
|
349 |
def _coolDown(self, bedtime): |
|
350 |
"""As per LoopTuner._coolDown, except we always wait until there
|
|
351 |
is no replication lag.
|
|
352 |
"""
|
|
353 |
self._blockForLongRunningTransactions() |
|
354 |
self._blockWhenLagged() |
|
355 |
if self.cooldown_time is not None and self.cooldown_time > 0.0: |
|
356 |
remaining_nap = self._time() - bedtime + self.cooldown_time |
|
7675.670.2
by Stuart Bishop
Make timeout flag sticky, so loop bodies that detect things will timeout will cause the loop to terminate rather than spin |
357 |
if remaining_nap > 0.0: |
358 |
self._sleep(remaining_nap) |
|
8083.1.1
by Stuart Bishop
DB specific looptuner that plays nicer with PostgreSQL |
359 |
return self._time() |
360 |
||
7675.599.3
by Graham Binns
Moved TunableLoop to somewhere where we can import it without running the risk of getting all circular. |
361 |
|
362 |
class TunableLoop: |
|
363 |
"""A base implementation of `ITunableLoop`."""
|
|
364 |
implements(ITunableLoop) |
|
365 |
||
8758.3.21
by Stuart Bishop
Reduce default goal transaction time for tunable loops to 2 seconds |
366 |
goal_seconds = 2 |
7675.599.3
by Graham Binns
Moved TunableLoop to somewhere where we can import it without running the risk of getting all circular. |
367 |
minimum_chunk_size = 1 |
13980.2.1
by Jeroen Vermeulen
Lint. Lots of lint. |
368 |
maximum_chunk_size = None # Override. |
7675.599.3
by Graham Binns
Moved TunableLoop to somewhere where we can import it without running the risk of getting all circular. |
369 |
cooldown_time = 0 |
370 |
||
371 |
def __init__(self, log, abort_time=None): |
|
372 |
self.log = log |
|
373 |
self.abort_time = abort_time |
|
374 |
||
7675.809.6
by Robert Collins
Create a garbo daily task to clean up file access tokens. |
375 |
def isDone(self): |
376 |
"""Return True when the TunableLoop is complete."""
|
|
377 |
raise NotImplementedError(self.isDone) |
|
378 |
||
7675.599.3
by Graham Binns
Moved TunableLoop to somewhere where we can import it without running the risk of getting all circular. |
379 |
def run(self): |
380 |
assert self.maximum_chunk_size is not None, ( |
|
381 |
"Did not override maximum_chunk_size.") |
|
382 |
DBLoopTuner( |
|
383 |
self, self.goal_seconds, |
|
8758.2.65
by Stuart Bishop
whitespace |
384 |
minimum_chunk_size=self.minimum_chunk_size, |
385 |
maximum_chunk_size=self.maximum_chunk_size, |
|
386 |
cooldown_time=self.cooldown_time, |
|
387 |
abort_time=self.abort_time, |
|
388 |
log=self.log).run() |