SCHEDULER_README.html revision 1.1.1.3.26.1 1 1.1 tron <!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
2 1.1.1.3 tron "http://www.w3.org/TR/html4/loose.dtd">
3 1.1 tron
4 1.1 tron <html>
5 1.1 tron
6 1.1 tron <head>
7 1.1 tron
8 1.1 tron <title>Postfix Queue Scheduler</title>
9 1.1 tron
10 1.1.1.3.26.1 martin <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
11 1.1.1.3.26.1 martin <link rel='stylesheet' type='text/css' href='postfix-doc.css'>
12 1.1 tron
13 1.1 tron </head>
14 1.1 tron
15 1.1 tron <body>
16 1.1 tron
17 1.1 tron <h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix
18 1.1 tron Queue Scheduler</h1>
19 1.1 tron
20 1.1 tron <hr>
21 1.1 tron
22 1.1.1.2 tron <h2> Disclaimer </h2>
23 1.1.1.2 tron
24 1.1.1.2 tron <p> Many of the <i>transport</i>-specific configuration parameters
25 1.1.1.2 tron discussed in this document will not show up in "postconf" command
26 1.1.1.2 tron output before Postfix version 2.9. This limitation applies to many
27 1.1.1.2 tron parameters whose name is a combination of a <a href="master.5.html">master.cf</a> service name
28 1.1.1.2 tron such as "relay" and a built-in suffix such as
29 1.1.1.2 tron "_destination_concurrency_limit". </p>
30 1.1.1.2 tron
31 1.1 tron <h2> Overview </h2>
32 1.1 tron
33 1.1 tron <p> The queue manager is by far the most complex part of the Postfix
34 1.1 tron mail system. It schedules delivery of new mail, retries failed
35 1.1 tron deliveries at specific times, and removes mail from the queue after
36 1.1 tron the last delivery attempt. There are two major classes of mechanisms
37 1.1 tron that control the operation of the queue manager. </p>
38 1.1 tron
39 1.1 tron <p> Topics covered by this document: </p>
40 1.1 tron
41 1.1 tron <ul>
42 1.1 tron
43 1.1 tron <li> <a href="#concurrency"> Concurrency scheduling</a>, concerned
44 1.1 tron with the number of concurrent deliveries to a specific destination,
45 1.1 tron including decisions on when to suspend deliveries after persistent
46 1.1 tron failures.
47 1.1 tron
48 1.1 tron <li> <a href="#jobs"> Preemptive scheduling</a>, concerned with
49 1.1 tron the selection of email messages and recipients for a given destination.
50 1.1 tron
51 1.1 tron <li> <a href="#credits"> Credits</a>, something this document would not be
52 1.1 tron complete without.
53 1.1 tron
54 1.1 tron </ul>
55 1.1 tron
56 1.1 tron <!--
57 1.1 tron
58 1.1 tron <p> Once started, the <a href="qmgr.8.html">qmgr(8)</a> process runs until "postfix reload"
59 1.1 tron or "postfix stop". As a persistent process, the queue manager has
60 1.1 tron to meet strict requirements with respect to code correctness and
61 1.1 tron robustness. Unlike non-persistent daemon processes, the queue manager
62 1.1 tron cannot benefit from Postfix's process rejuvenation mechanism that
63 1.1 tron limit the impact from resource leaks and other coding errors
64 1.1 tron (translation: replacing a process after a short time covers up bugs
65 1.1 tron before they can become a problem). </p>
66 1.1 tron
67 1.1 tron -->
68 1.1 tron
69 1.1 tron <h2> <a name="concurrency"> Concurrency scheduling </a> </h2>
70 1.1 tron
71 1.1 tron <p> The following sections document the Postfix 2.5 concurrency
72 1.1.1.3 tron scheduler, after a discussion of the limitations of the earlier
73 1.1 tron concurrency scheduler. This is followed by results of medium-concurrency
74 1.1 tron experiments, and a discussion of trade-offs between performance and
75 1.1 tron robustness. </p>
76 1.1 tron
77 1.1 tron <p> The material is organized as follows: </p>
78 1.1 tron
79 1.1 tron <ul>
80 1.1 tron
81 1.1 tron <li> <a href="#concurrency_drawbacks"> Drawbacks of the existing
82 1.1 tron concurrency scheduler </a>
83 1.1 tron
84 1.1 tron <li> <a href="#concurrency_summary_2_5"> Summary of the Postfix 2.5
85 1.1 tron concurrency feedback algorithm </a>
86 1.1 tron
87 1.1 tron <li> <a href="#dead_summary_2_5"> Summary of the Postfix 2.5 "dead
88 1.1 tron destination" detection algorithm </a>
89 1.1 tron
90 1.1 tron <li> <a href="#pseudo_code_2_5"> Pseudocode for the Postfix 2.5
91 1.1 tron concurrency scheduler </a>
92 1.1 tron
93 1.1 tron <li> <a href="#concurrency_results"> Results for delivery to
94 1.1 tron concurrency limited servers </a>
95 1.1 tron
96 1.1 tron <li> <a href="#concurrency_discussion"> Discussion of concurrency
97 1.1 tron limited server results </a>
98 1.1 tron
99 1.1 tron <li> <a href="#concurrency_limitations"> Limitations of less-than-1
100 1.1 tron per delivery feedback </a>
101 1.1 tron
102 1.1 tron <li> <a href="#concurrency_config"> Concurrency configuration
103 1.1 tron parameters </a>
104 1.1 tron
105 1.1 tron </ul>
106 1.1 tron
107 1.1 tron <h3> <a name="concurrency_drawbacks"> Drawbacks of the existing
108 1.1 tron concurrency scheduler </a> </h3>
109 1.1 tron
110 1.1 tron <p> From the start, Postfix has used a simple but robust algorithm
111 1.1 tron where the per-destination delivery concurrency is decremented by 1
112 1.1 tron after delivery failed due to connection or handshake failure, and
113 1.1 tron incremented by 1 otherwise. Of course the concurrency is never
114 1.1 tron allowed to exceed the maximum per-destination concurrency limit.
115 1.1 tron And when a destination's concurrency level drops to zero, the
116 1.1 tron destination is declared "dead" and delivery is suspended. </p>
117 1.1 tron
118 1.1 tron <p> Drawbacks of +/-1 concurrency feedback per delivery are: <p>
119 1.1 tron
120 1.1 tron <ul>
121 1.1 tron
122 1.1 tron <li> <p> Overshoot due to exponential delivery concurrency growth
123 1.1 tron with each pseudo-cohort(*). This can be an issue with high-concurrency
124 1.1 tron channels. For example, with the default initial concurrency of 5,
125 1.1 tron concurrency would proceed over time as (5-10-20). </p>
126 1.1 tron
127 1.1 tron <li> <p> Throttling down to zero concurrency after a single
128 1.1 tron pseudo-cohort(*) failure. This was especially an issue with
129 1.1 tron low-concurrency channels where a single failure could be sufficient
130 1.1 tron to mark a destination as "dead", causing the suspension of further
131 1.1 tron deliveries to the affected destination. </p>
132 1.1 tron
133 1.1 tron </ul>
134 1.1 tron
135 1.1 tron <p> (*) A pseudo-cohort is a number of delivery requests equal to
136 1.1 tron a destination's delivery concurrency. </p>
137 1.1 tron
138 1.1 tron <p> The revised concurrency scheduler has a highly modular structure.
139 1.1 tron It uses separate mechanisms for per-destination concurrency control
140 1.1 tron and for "dead destination" detection. The concurrency control in
141 1.1 tron turn is built from two separate mechanisms: it supports less-than-1
142 1.1 tron feedback per delivery to allow for more gradual concurrency
143 1.1 tron adjustments, and it uses feedback hysteresis to suppress concurrency
144 1.1 tron oscillations. And instead of waiting for delivery concurrency to
145 1.1 tron throttle down to zero, a destination is declared "dead" after a
146 1.1 tron configurable number of pseudo-cohorts reports connection or handshake
147 1.1 tron failure. </p>
148 1.1 tron
149 1.1 tron <h3> <a name="concurrency_summary_2_5"> Summary of the Postfix 2.5
150 1.1 tron concurrency feedback algorithm </a> </h3>
151 1.1 tron
152 1.1 tron <p> We want to increment a destination's delivery concurrency when
153 1.1 tron some (not necessarily consecutive) number of deliveries complete
154 1.1 tron without connection or handshake failure. This is implemented with
155 1.1 tron positive feedback g(N) where N is the destination's delivery
156 1.1 tron concurrency. With g(N)=1 feedback per delivery, concurrency increases
157 1.1 tron by 1 after each positive feedback event; this gives us the old
158 1.1 tron scheduler's exponential growth in time. With g(N)=1/N feedback per
159 1.1 tron delivery, concurrency increases by 1 after an entire pseudo-cohort
160 1.1 tron N of positive feedback reports; this gives us linear growth in time.
161 1.1 tron Less-than-1 feedback per delivery and integer truncation naturally
162 1.1 tron give us hysteresis, so that transitions to larger concurrency happen
163 1.1 tron every 1/g(N) positive feedback events. </p>
164 1.1 tron
165 1.1 tron <p> We want to decrement a destination's delivery concurrency when
166 1.1 tron some (not necessarily consecutive) number of deliveries complete
167 1.1 tron after connection or handshake failure. This is implemented with
168 1.1 tron negative feedback f(N) where N is the destination's delivery
169 1.1 tron concurrency. With f(N)=1 feedback per delivery, concurrency decreases
170 1.1 tron by 1 after each negative feedback event; this gives us the old
171 1.1 tron scheduler's behavior where concurrency is throttled down dramatically
172 1.1 tron after a single pseudo-cohort failure. With f(N)=1/N feedback per
173 1.1 tron delivery, concurrency backs off more gently. Again, less-than-1
174 1.1 tron feedback per delivery and integer truncation naturally give us
175 1.1 tron hysteresis, so that transitions to lower concurrency happen every
176 1.1 tron 1/f(N) negative feedback events. </p>
177 1.1 tron
178 1.1 tron <p> However, with negative feedback we introduce a subtle twist.
179 1.1 tron We "reverse" the negative hysteresis cycle so that the transition
180 1.1 tron to lower concurrency happens at the <b>beginning</b> of a sequence
181 1.1 tron of 1/f(N) negative feedback events. Otherwise, a correction for
182 1.1 tron overload would be made too late. This makes the choice of f(N)
183 1.1 tron relatively unimportant, as borne out by measurements later in this
184 1.1 tron document. </p>
185 1.1 tron
186 1.1 tron <p> In summary, the main ingredients for the Postfix 2.5 concurrency
187 1.1 tron feedback algorithm are a) the option of less-than-1 positive feedback
188 1.1 tron per delivery to avoid overwhelming servers, b) the option of
189 1.1 tron less-than-1 negative feedback per delivery to avoid giving up too
190 1.1 tron fast, c) feedback hysteresis to avoid rapid oscillation, and d) a
191 1.1 tron "reverse" hysteresis cycle for negative feedback, so that it can
192 1.1 tron correct for overload quickly. </p>
193 1.1 tron
194 1.1 tron <h3> <a name="dead_summary_2_5"> Summary of the Postfix 2.5 "dead destination" detection algorithm </a> </h3>
195 1.1 tron
196 1.1 tron <p> We want to suspend deliveries to a specific destination after
197 1.1 tron some number of deliveries suffers connection or handshake failure.
198 1.1 tron The old scheduler declares a destination "dead" when negative (-1)
199 1.1 tron feedback throttles the delivery concurrency down to zero. With
200 1.1 tron less-than-1 feedback per delivery, this throttling down would
201 1.1 tron obviously take too long. We therefore have to separate "dead
202 1.1 tron destination" detection from concurrency feedback. This is implemented
203 1.1 tron by introducing the concept of pseudo-cohort failure. The Postfix
204 1.1 tron 2.5 concurrency scheduler declares a destination "dead" after a
205 1.1 tron configurable number of pseudo-cohorts suffers from connection or
206 1.1 tron handshake failures. The old scheduler corresponds to the special
207 1.1 tron case where the pseudo-cohort failure limit is equal to 1. </p>
208 1.1 tron
209 1.1 tron <h3> <a name="pseudo_code_2_5"> Pseudocode for the Postfix 2.5 concurrency scheduler </a> </h3>
210 1.1 tron
211 1.1 tron <p> The pseudo code shows how the ideas behind new concurrency
212 1.1 tron scheduler are implemented as of November 2007. The actual code can
213 1.1 tron be found in the module qmgr/qmgr_queue.c. </p>
214 1.1 tron
215 1.1 tron <pre>
216 1.1 tron Types:
217 1.1 tron Each destination has one set of the following variables
218 1.1 tron int concurrency
219 1.1 tron double success
220 1.1 tron double failure
221 1.1 tron double fail_cohorts
222 1.1 tron
223 1.1 tron Feedback functions:
224 1.1 tron N is concurrency; x, y are arbitrary numbers in [0..1] inclusive
225 1.1 tron positive feedback: g(N) = x/N | x/sqrt(N) | x
226 1.1 tron negative feedback: f(N) = y/N | y/sqrt(N) | y
227 1.1 tron
228 1.1 tron Initialization:
229 1.1 tron concurrency = initial_concurrency
230 1.1 tron success = 0
231 1.1 tron failure = 0
232 1.1 tron fail_cohorts = 0
233 1.1 tron
234 1.1 tron After success:
235 1.1 tron fail_cohorts = 0
236 1.1 tron Be prepared for feedback > hysteresis, or rounding error
237 1.1 tron success += g(concurrency)
238 1.1 tron while (success >= 1) Hysteresis 1
239 1.1 tron concurrency += 1 Hysteresis 1
240 1.1 tron failure = 0
241 1.1 tron success -= 1 Hysteresis 1
242 1.1 tron Be prepared for overshoot
243 1.1 tron if (concurrency > concurrency limit)
244 1.1 tron concurrency = concurrency limit
245 1.1 tron
246 1.1 tron Safety:
247 1.1 tron Don't apply positive feedback unless
248 1.1 tron concurrency < busy_refcount + init_dest_concurrency
249 1.1 tron otherwise negative feedback effect could be delayed
250 1.1 tron
251 1.1 tron After failure:
252 1.1 tron if (concurrency > 0)
253 1.1 tron fail_cohorts += 1.0 / concurrency
254 1.1 tron if (fail_cohorts > cohort_failure_limit)
255 1.1 tron concurrency = 0
256 1.1 tron if (concurrency > 0)
257 1.1 tron Be prepared for feedback > hysteresis, rounding errors
258 1.1 tron failure -= f(concurrency)
259 1.1 tron while (failure < 0)
260 1.1 tron concurrency -= 1 Hysteresis 1
261 1.1 tron failure += 1 Hysteresis 1
262 1.1 tron success = 0
263 1.1 tron Be prepared for overshoot
264 1.1 tron if (concurrency < 1)
265 1.1 tron concurrency = 1
266 1.1 tron </pre>
267 1.1 tron
268 1.1 tron <h3> <a name="concurrency_results"> Results for delivery to concurrency-limited servers </a> </h3>
269 1.1 tron
270 1.1 tron <p> Discussions about the concurrency scheduler redesign started
271 1.1 tron early 2004, when the primary goal was to find alternatives that did
272 1.1 tron not exhibit exponential growth or rapid concurrency throttling. No
273 1.1 tron code was implemented until late 2007, when the primary concern had
274 1.1 tron shifted towards better handling of server concurrency limits. For
275 1.1 tron this reason we measure how well the new scheduler does this
276 1.1 tron job. The table below compares mail delivery performance of the old
277 1.1 tron +/-1 feedback per delivery with several less-than-1 feedback
278 1.1 tron functions, for different limited-concurrency server scenarios.
279 1.1 tron Measurements were done with a FreeBSD 6.2 client and with FreeBSD
280 1.1 tron 6.2 and various Linux servers. </p>
281 1.1 tron
282 1.1 tron <p> Server configuration: </p>
283 1.1 tron
284 1.1 tron <ul> <li> The mail flow was slowed down with 1 second latency per
285 1.1 tron recipient ("<a href="postconf.5.html#smtpd_client_restrictions">smtpd_client_restrictions</a> = sleep 1"). The purpose was
286 1.1 tron to make results less dependent on hardware details, by avoiding
287 1.1 tron slow-downs by queue file I/O, logging I/O, and network I/O.
288 1.1 tron
289 1.1 tron <li> Concurrency was limited by the server process limit
290 1.1 tron ("<a href="postconf.5.html#default_process_limit">default_process_limit</a> = 5" and "<a href="postconf.5.html#smtpd_client_event_limit_exceptions">smtpd_client_event_limit_exceptions</a>
291 1.1.1.2 tron = <a href="DATABASE_README.html#types">static</a>:all"). Postfix was stopped and started after changing the
292 1.1 tron process limit, because the same number is also used as the backlog
293 1.1 tron argument to the listen(2) system call, and "postfix reload" does
294 1.1 tron not re-issue this call.
295 1.1 tron
296 1.1.1.2 tron <li> Mail was discarded with "<a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a> = <a href="DATABASE_README.html#types">static</a>:all" and
297 1.1 tron "<a href="postconf.5.html#local_transport">local_transport</a> = discard". The discard action in access maps or
298 1.1 tron header/body checks
299 1.1 tron could not be used as it fails to update the <a href="postconf.5.html#in_flow_delay">in_flow_delay</a> counters.
300 1.1 tron
301 1.1 tron </ul>
302 1.1 tron
303 1.1 tron <p> Client configuration: </p>
304 1.1 tron
305 1.1 tron <ul>
306 1.1 tron
307 1.1 tron <li> Queue file overhead was minimized by sending one message to a
308 1.1 tron virtual alias that expanded into 2000 different remote recipients.
309 1.1 tron All recipients were accounted for according to the maillog file.
310 1.1 tron The <a href="postconf.5.html#virtual_alias_expansion_limit">virtual_alias_expansion_limit</a> setting was increased to avoid
311 1.1 tron complaints from the <a href="cleanup.8.html">cleanup(8)</a> server.
312 1.1 tron
313 1.1 tron <li> The number of deliveries was maximized with
314 1.1 tron "<a href="postconf.5.html#smtp_destination_recipient_limit">smtp_destination_recipient_limit</a> = 2". A smaller limit would cause
315 1.1 tron Postfix to schedule the concurrency per recipient instead of domain,
316 1.1 tron which is not what we want.
317 1.1 tron
318 1.1 tron <li> Maximum concurrency was limited with
319 1.1 tron "<a href="postconf.5.html#smtp_destination_concurrency_limit">smtp_destination_concurrency_limit</a> = 20", and
320 1.1 tron <a href="postconf.5.html#initial_destination_concurrency">initial_destination_concurrency</a> was set to the same value.
321 1.1 tron
322 1.1 tron <li> The positive and negative concurrency feedback hysteresis was
323 1.1 tron 1. Concurrency was incremented by 1 at the END of 1/feedback steps
324 1.1 tron of positive feedback, and was decremented by 1 at the START of
325 1.1 tron 1/feedback steps of negative feedback.
326 1.1 tron
327 1.1 tron <li> The SMTP client used the default 30s SMTP connect timeout and
328 1.1 tron 300s SMTP greeting timeout.
329 1.1 tron
330 1.1 tron </ul>
331 1.1 tron
332 1.1 tron <h4> Impact of the 30s SMTP connect timeout </h4>
333 1.1 tron
334 1.1 tron <p> The first results are for a FreeBSD 6.2 server, where our
335 1.1 tron artificially low listen(2) backlog results in a very short kernel
336 1.1 tron queue for established connections. The table shows that all deferred
337 1.1 tron deliveries failed due to a 30s connection timeout, and none failed
338 1.1 tron due to a server greeting timeout. This measurement simulates what
339 1.1 tron happens when the server's connection queue is completely full under
340 1.1 tron load, and the TCP engine drops new connections. </p>
341 1.1 tron
342 1.1 tron <blockquote>
343 1.1 tron
344 1.1 tron <table>
345 1.1 tron
346 1.1 tron <tr> <th>client<br> limit</th> <th>server<br> limit</th> <th>feedback<br>
347 1.1 tron style</th> <th>connection<br> caching</th> <th>percentage<br>
348 1.1 tron deferred</th> <th colspan="2">client concurrency<br> average/stddev</th>
349 1.1 tron <th colspan=2>timed-out in<br> connect/greeting </th> </tr>
350 1.1 tron
351 1.1 tron <tr> <td align="center" colspan="9"> <hr> </td> </tr>
352 1.1 tron
353 1.1 tron <tr><td align="center">20</td> <td align="center">5</td> <td
354 1.1 tron align="center">1/N</td> <td align="center">no</td> <td
355 1.1 tron align="center">9.9</td> <td align="center">19.4</td> <td
356 1.1 tron align="center">0.49</td> <td align="center">198</td> <td
357 1.1 tron align="center">-</td> </tr>
358 1.1 tron
359 1.1 tron <tr><td align="center">20</td> <td align="center">5</td> <td
360 1.1 tron align="center">1/N</td> <td align="center">yes</td> <td
361 1.1 tron align="center">10.3</td> <td align="center">19.4</td> <td
362 1.1 tron align="center">0.49</td> <td align="center">206</td> <td
363 1.1 tron align="center">-</td> </tr>
364 1.1 tron
365 1.1 tron <tr><td align="center">20</td> <td align="center">5</td> <td
366 1.1 tron align="center">1/sqrt(N)</td> <td align="center">no</td>
367 1.1 tron <td align="center">10.4</td> <td align="center">19.6</td> <td
368 1.1 tron align="center">0.59</td> <td align="center">208</td> <td
369 1.1 tron align="center">-</td> </tr>
370 1.1 tron
371 1.1 tron <tr><td align="center">20</td> <td align="center">5</td> <td
372 1.1 tron align="center">1/sqrt(N)</td> <td align="center">yes</td>
373 1.1 tron <td align="center">10.6</td> <td align="center">19.6</td> <td
374 1.1 tron align="center">0.61</td> <td align="center">212</td> <td
375 1.1 tron align="center">-</td> </tr>
376 1.1 tron
377 1.1 tron <tr><td align="center">20</td> <td align="center">5</td> <td
378 1.1 tron align="center">1</td> <td align="center">no</td> <td
379 1.1 tron align="center">10.1</td> <td align="center">19.5</td> <td
380 1.1 tron align="center">1.29</td> <td align="center">202</td> <td
381 1.1 tron align="center">-</td> </tr>
382 1.1 tron
383 1.1 tron <tr><td align="center">20</td> <td align="center">5</td> <td
384 1.1 tron align="center">1</td> <td align="center">yes</td> <td
385 1.1 tron align="center">10.8</td> <td align="center">19.3</td> <td
386 1.1 tron align="center">1.57</td> <td align="center">216</td> <td
387 1.1 tron align="center">-</td> </tr>
388 1.1 tron
389 1.1 tron <tr> <td align="center" colspan="9"> <hr> </td> </tr>
390 1.1 tron
391 1.1 tron </table>
392 1.1 tron
393 1.1 tron <p> A busy server with a completely full connection queue. N is
394 1.1 tron the client delivery concurrency. Failed deliveries time out after
395 1.1 tron 30s without completing the TCP handshake. See text for a discussion
396 1.1 tron of results. </p>
397 1.1 tron
398 1.1 tron </blockquote>
399 1.1 tron
400 1.1 tron <h4> Impact of the 300s SMTP greeting timeout </h4>
401 1.1 tron
402 1.1 tron <p> The next table shows results for a Fedora Core 8 server (results
403 1.1 tron for RedHat 7.3 are identical). In this case, the artificially small
404 1.1 tron listen(2) backlog argument does not impact our measurement. The
405 1.1 tron table shows that practically all deferred deliveries fail after the
406 1.1 tron 300s SMTP greeting timeout. As these timeouts were 10x longer than
407 1.1 tron with the first measurement, we increased the recipient count (and
408 1.1 tron thus the running time) by a factor of 10 to keep the results
409 1.1 tron comparable. The deferred mail percentages are a factor 10 lower
410 1.1 tron than with the first measurement, because the 1s per-recipient delay
411 1.1 tron was 1/300th of the greeting timeout instead of 1/30th of the
412 1.1 tron connection timeout. </p>
413 1.1 tron
414 1.1 tron <blockquote>
415 1.1 tron
416 1.1 tron <table>
417 1.1 tron
418 1.1 tron <tr> <th>client<br> limit</th> <th>server<br> limit</th> <th>feedback<br>
419 1.1 tron style</th> <th>connection<br> caching</th> <th>percentage<br>
420 1.1 tron deferred</th> <th colspan="2">client concurrency<br> average/stddev</th>
421 1.1 tron <th colspan=2>timed-out in<br> connect/greeting </th> </tr>
422 1.1 tron
423 1.1 tron <tr> <td align="center" colspan="9"> <hr> </td> </tr>
424 1.1 tron
425 1.1 tron <tr> <td align="center">20</td> <td align="center">5</td> <td
426 1.1 tron align="center">1/N</td> <td align="center">no</td> <td
427 1.1 tron align="center">1.16</td> <td align="center">19.8</td> <td
428 1.1 tron align="center">0.37</td> <td align="center">-</td> <td
429 1.1 tron align="center">230</td> </tr>
430 1.1 tron
431 1.1 tron <tr> <td align="center">20</td> <td align="center">5</td> <td
432 1.1 tron align="center">1/N</td> <td align="center">yes</td> <td
433 1.1 tron align="center">1.36</td> <td align="center">19.8</td> <td
434 1.1 tron align="center">0.36</td> <td align="center">-</td> <td
435 1.1 tron align="center">272</td> </tr>
436 1.1 tron
437 1.1 tron <tr> <td align="center">20</td> <td align="center">5</td> <td
438 1.1 tron align="center">1/sqrt(N)</td> <td align="center">no</td>
439 1.1 tron <td align="center">1.21</td> <td align="center">19.9</td> <td
440 1.1 tron align="center">0.23</td> <td align="center">4</td> <td
441 1.1 tron align="center">238</td> </tr>
442 1.1 tron
443 1.1 tron <tr> <td align="center">20</td> <td align="center">5</td> <td
444 1.1 tron align="center">1/sqrt(N)</td> <td align="center">yes</td>
445 1.1 tron <td align="center">1.36</td> <td align="center">20.0</td> <td
446 1.1 tron align="center">0.23</td> <td align="center">-</td> <td
447 1.1 tron align="center">272</td> </tr>
448 1.1 tron
449 1.1 tron <tr> <td align="center">20</td> <td align="center">5</td> <td
450 1.1 tron align="center">1</td> <td align="center">no</td> <td
451 1.1 tron align="center">1.18</td> <td align="center">20.0</td> <td
452 1.1 tron align="center">0.16</td> <td align="center">-</td> <td
453 1.1 tron align="center">236</td> </tr>
454 1.1 tron
455 1.1 tron <tr> <td align="center">20</td> <td align="center">5</td> <td
456 1.1 tron align="center">1</td> <td align="center">yes</td> <td
457 1.1 tron align="center">1.39</td> <td align="center">20.0</td> <td
458 1.1 tron align="center">0.16</td> <td align="center">-</td> <td
459 1.1 tron align="center">278</td> </tr>
460 1.1 tron
461 1.1 tron <tr> <td align="center" colspan="9"> <hr> </td> </tr>
462 1.1 tron
463 1.1 tron </table>
464 1.1 tron
465 1.1 tron <p> A busy server with a non-full connection queue. N is the client
466 1.1 tron delivery concurrency. Failed deliveries complete at the TCP level,
467 1.1 tron but time out after 300s while waiting for the SMTP greeting. See
468 1.1 tron text for a discussion of results. </p>
469 1.1 tron
470 1.1 tron </blockquote>
471 1.1 tron
472 1.1 tron <h4> Impact of active server concurrency limiter </h4>
473 1.1 tron
474 1.1 tron <p> The final concurrency-limited result shows what happens when
475 1.1 tron SMTP connections don't time out, but are rejected immediately with
476 1.1 tron the Postfix server's <a href="postconf.5.html#smtpd_client_connection_count_limit">smtpd_client_connection_count_limit</a> feature
477 1.1 tron (the server replies with a 421 status and disconnects immediately).
478 1.1 tron Similar results can be expected with concurrency limiting features
479 1.1 tron built into other MTAs or firewalls. For this measurement we specified
480 1.1 tron a server concurrency limit and a client initial destination concurrency
481 1.1 tron of 5, and a server process limit of 10; all other conditions were
482 1.1 tron the same as with the first measurement. The same result would be
483 1.1 tron obtained with a FreeBSD or Linux server, because the "pushing back"
484 1.1 tron is done entirely by the receiving side. </p>
485 1.1 tron
486 1.1 tron <blockquote>
487 1.1 tron
488 1.1 tron <table>
489 1.1 tron
490 1.1 tron <tr> <th>client<br> limit</th> <th>server<br> limit</th> <th>feedback<br>
491 1.1 tron style</th> <th>connection<br> caching</th> <th>percentage<br>
492 1.1 tron deferred</th> <th colspan="2">client concurrency<br> average/stddev</th>
493 1.1 tron <th>theoretical<br>defer rate</th> </tr>
494 1.1 tron
495 1.1 tron <tr> <td align="center" colspan="9"> <hr> </td> </tr>
496 1.1 tron
497 1.1 tron <tr> <td align="center">20</td> <td align="center">5</td> <td
498 1.1 tron align="center">1/N</td> <td align="center">no</td> <td
499 1.1 tron align="center">16.5</td> <td align="center">5.17</td> <td
500 1.1 tron align="center">0.38</td> <td align="center">1/6</td> </tr>
501 1.1 tron
502 1.1 tron <tr> <td align="center">20</td> <td align="center">5</td> <td
503 1.1 tron align="center">1/N</td> <td align="center">yes</td> <td
504 1.1 tron align="center">16.5</td> <td align="center">5.17</td> <td
505 1.1 tron align="center">0.38</td> <td align="center">1/6</td> </tr>
506 1.1 tron
507 1.1 tron <tr> <td align="center">20</td> <td align="center">5</td> <td
508 1.1 tron align="center">1/sqrt(N)</td> <td align="center">no</td>
509 1.1 tron <td align="center">24.5</td> <td align="center">5.28</td> <td
510 1.1 tron align="center">0.45</td> <td align="center">1/4</td> </tr>
511 1.1 tron
512 1.1 tron <tr> <td align="center">20</td> <td align="center">5</td> <td
513 1.1 tron align="center">1/sqrt(N)</td> <td align="center">yes</td>
514 1.1 tron <td align="center">24.3</td> <td align="center">5.28</td> <td
515 1.1 tron align="center">0.46</td> <td align="center">1/4</td> </tr>
516 1.1 tron
517 1.1 tron <tr> <td align="center">20</td> <td align="center">5</td> <td
518 1.1 tron align="center">1</td> <td align="center">no</td> <td
519 1.1 tron align="center">49.7</td> <td align="center">5.63</td> <td
520 1.1 tron align="center">0.67</td> <td align="center">1/2</td> </tr>
521 1.1 tron
522 1.1 tron <tr> <td align="center">20</td> <td align="center">5</td> <td
523 1.1 tron align="center">1</td> <td align="center">yes</td> <td
524 1.1 tron align="center">49.7</td> <td align="center">5.68</td> <td
525 1.1 tron align="center">0.70</td> <td align="center">1/2</td> </tr>
526 1.1 tron
527 1.1 tron <tr> <td align="center" colspan="9"> <hr> </td> </tr>
528 1.1 tron
529 1.1 tron </table>
530 1.1 tron
531 1.1 tron <p> A server with active per-client concurrency limiter that replies
532 1.1 tron with 421 and disconnects. N is the client delivery concurrency.
533 1.1 tron The theoretical defer rate is 1/(1+roundup(1/feedback)). This is
534 1.1 tron always 1/2 with the fixed +/-1 feedback per delivery; with the
535 1.1 tron concurrency-dependent feedback variants, the defer rate decreases
536 1.1 tron with increasing concurrency. See text for a discussion of results.
537 1.1 tron </p>
538 1.1 tron
539 1.1 tron </blockquote>
540 1.1 tron
541 1.1 tron <h3> <a name="concurrency_discussion"> Discussion of concurrency-limited server results </a> </h3>
542 1.1 tron
543 1.1 tron <p> All results in the previous sections are based on the first
544 1.1 tron delivery runs only; they do not include any second etc. delivery
545 1.1 tron attempts. It's also worth noting that the measurements look at
546 1.1 tron steady-state behavior only. They don't show what happens when the
547 1.1 tron client starts sending at a much higher or lower concurrency.
548 1.1 tron </p>
549 1.1 tron
550 1.1 tron <p> The first two examples show that the effect of feedback
551 1.1 tron is negligible when concurrency is limited due to congestion. This
552 1.1 tron is because the initial concurrency is already at the client's
553 1.1 tron concurrency maximum, and because there is 10-100 times more positive
554 1.1 tron than negative feedback. Under these conditions, it is no surprise
555 1.1 tron that the contribution from SMTP connection caching is also negligible.
556 1.1 tron </p>
557 1.1 tron
558 1.1 tron <p> In the last example, the old +/-1 feedback per delivery will
559 1.1 tron defer 50% of the mail when confronted with an active (anvil-style)
560 1.1 tron server concurrency limit, where the server hangs up immediately
561 1.1 tron with a 421 status (a TCP-level RST would have the same result).
562 1.1 tron Less aggressive feedback mechanisms fare better than more aggressive
563 1.1 tron ones. Concurrency-dependent feedback fares even better at higher
564 1.1 tron concurrencies than shown here, but has limitations as discussed in
565 1.1 tron the next section. </p>
566 1.1 tron
567 1.1 tron <h3> <a name="concurrency_limitations"> Limitations of less-than-1 per delivery feedback </a> </h3>
568 1.1 tron
569 1.1 tron <p> Less-than-1 feedback is of interest primarily when sending large
570 1.1 tron amounts of mail to destinations with active concurrency limiters
571 1.1 tron (servers that reply with 421, or firewalls that send RST). When
572 1.1 tron sending small amounts of mail per destination, less-than-1 per-delivery
573 1.1 tron feedback won't have a noticeable effect on the per-destination
574 1.1 tron concurrency, because the number of deliveries to the same destination
575 1.1 tron is too small. You might just as well use zero per-delivery feedback
576 1.1 tron and stay with the initial per-destination concurrency. And when
577 1.1 tron mail deliveries fail due to congestion instead of active concurrency
578 1.1 tron limiters, the measurements above show that per-delivery feedback
579 1.1 tron has no effect. With large amounts of mail you might just as well
580 1.1 tron use zero per-delivery feedback and start with the maximal per-destination
581 1.1 tron concurrency. </p>
582 1.1 tron
583 1.1 tron <p> The scheduler with less-than-1 concurrency
584 1.1 tron feedback per delivery solves a problem with servers that have active
585 1.1 tron concurrency limiters. This works only because feedback is handled
586 1.1 tron in a peculiar manner: positive feedback will increment the concurrency
587 1.1 tron by 1 at the <b>end</b> of a sequence of events of length 1/feedback,
588 1.1 tron while negative feedback will decrement concurrency by 1 at the
589 1.1 tron <b>beginning</b> of such a sequence. This is how Postfix adjusts
590 1.1 tron quickly for overshoot without causing lots of mail to be deferred.
591 1.1 tron Without this difference in feedback treatment, less-than-1 feedback
592 1.1 tron per delivery would defer 50% of the mail, and would be no better
593 1.1 tron in this respect than the old +/-1 feedback per delivery. </p>
594 1.1 tron
595 1.1 tron <p> Unfortunately, the same feature that corrects quickly for
596 1.1 tron concurrency overshoot also makes the scheduler more sensitive for
597 1.1 tron noisy negative feedback. The reason is that one lonely negative
598 1.1 tron feedback event has the same effect as a complete sequence of length
599 1.1 tron 1/feedback: in both cases delivery concurrency is dropped by 1
600 1.1 tron immediately. As a worst-case scenario, consider multiple servers
601 1.1 tron behind a load balancer on a single IP address, and no backup MX
602 1.1 tron address. When 1 out of K servers fails to complete the SMTP handshake
603 1.1 tron or drops the connection, a scheduler with 1/N (N = concurrency)
604 1.1 tron feedback stops increasing its concurrency once it reaches a concurrency
605 1.1 tron level of about K, even though the good servers behind the load
606 1.1 tron balancer are perfectly capable of handling more traffic. </p>
607 1.1 tron
608 1.1 tron <p> This noise problem gets worse as the amount of positive feedback
609 1.1 tron per delivery gets smaller. A compromise is to use fixed less-than-1
610 1.1 tron positive feedback values instead of concurrency-dependent positive
611 1.1 tron feedback. For example, to tolerate 1 of 4 bad servers in the above
612 1.1 tron load balancer scenario, use positive feedback of 1/4 per "good"
613 1.1 tron delivery (no connect or handshake error), and use an equal or smaller
614 1.1 tron amount of negative feedback per "bad" delivery. The downside of
615 1.1 tron using concurrency-independent feedback is that some of the old +/-1
616 1.1 tron feedback problems will return at large concurrencies. Sites that
617 1.1 tron must deliver mail at non-trivial per-destination concurrencies will
618 1.1 tron require special configuration. </p>
619 1.1 tron
620 1.1 tron <h3> <a name="concurrency_config"> Concurrency configuration parameters </a> </h3>
621 1.1 tron
622 1.1 tron <p> The Postfix 2.5 concurrency scheduler is controlled with the
623 1.1 tron following configuration parameters, where "<i>transport</i>_foo"
624 1.1 tron provides a transport-specific parameter override. All parameter
625 1.1 tron default settings are compatible with earlier Postfix versions. </p>
626 1.1 tron
627 1.1 tron <blockquote>
628 1.1 tron
629 1.1 tron <table border="0">
630 1.1 tron
631 1.1 tron <tr> <th> Parameter name </th> <th> Postfix version </th> <th>
632 1.1 tron Description </th> </tr>
633 1.1 tron
634 1.1 tron <tr> <td colspan="3"> <hr> </td> </tr>
635 1.1 tron
636 1.1 tron <tr> <td> <a href="postconf.5.html#initial_destination_concurrency">initial_destination_concurrency</a><br>
637 1.1 tron <a href="postconf.5.html#transport_initial_destination_concurrency"><i>transport</i>_initial_destination_concurrency</a> </td> <td
638 1.1 tron align="center"> all<br> 2.5 </td> <td> Initial per-destination
639 1.1 tron delivery concurrency </td> </tr>
640 1.1 tron
641 1.1 tron <tr> <td> <a href="postconf.5.html#default_destination_concurrency_limit">default_destination_concurrency_limit</a><br>
642 1.1 tron <a href="postconf.5.html#transport_destination_concurrency_limit"><i>transport</i>_destination_concurrency_limit</a> </td> <td align="center">
643 1.1 tron all<br> all </td> <td> Maximum per-destination delivery concurrency
644 1.1 tron </td> </tr>
645 1.1 tron
646 1.1 tron <tr> <td> <a href="postconf.5.html#default_destination_concurrency_positive_feedback">default_destination_concurrency_positive_feedback</a><br>
647 1.1 tron <a href="postconf.5.html#transport_destination_concurrency_positive_feedback"><i>transport</i>_destination_concurrency_positive_feedback</a> </td>
648 1.1 tron <td align="center"> 2.5<br> 2.5 </td> <td> Per-destination positive
649 1.1 tron feedback amount, per delivery that does not fail with connection
650 1.1 tron or handshake failure </td> </tr>
651 1.1 tron
652 1.1 tron <tr> <td> <a href="postconf.5.html#default_destination_concurrency_negative_feedback">default_destination_concurrency_negative_feedback</a><br>
653 1.1 tron <a href="postconf.5.html#transport_destination_concurrency_negative_feedback"><i>transport</i>_destination_concurrency_negative_feedback</a> </td>
654 1.1 tron <td align="center"> 2.5<br> 2.5 </td> <td> Per-destination negative
655 1.1 tron feedback amount, per delivery that fails with connection or handshake
656 1.1 tron failure </td> </tr>
657 1.1 tron
658 1.1 tron <tr> <td> <a href="postconf.5.html#default_destination_concurrency_failed_cohort_limit">default_destination_concurrency_failed_cohort_limit</a><br>
659 1.1 tron <a href="postconf.5.html#transport_destination_concurrency_failed_cohort_limit"><i>transport</i>_destination_concurrency_failed_cohort_limit</a> </td>
660 1.1 tron <td align="center"> 2.5<br> 2.5 </td> <td> Number of failed
661 1.1 tron pseudo-cohorts after which a destination is declared "dead" and
662 1.1 tron delivery is suspended </td> </tr>
663 1.1 tron
664 1.1 tron <tr> <td> <a href="postconf.5.html#destination_concurrency_feedback_debug">destination_concurrency_feedback_debug</a></td> <td align="center">
665 1.1 tron 2.5 </td> <td> Enable verbose logging of concurrency scheduler
666 1.1 tron activity </td> </tr>
667 1.1 tron
668 1.1 tron <tr> <td colspan="3"> <hr> </td> </tr>
669 1.1 tron
670 1.1 tron </table>
671 1.1 tron
672 1.1 tron </blockquote>
673 1.1 tron
674 1.1 tron <h2> <a name="jobs"> Preemptive scheduling </a> </h2>
675 1.1 tron
676 1.1 tron <p>
677 1.1 tron
678 1.1 tron The following sections describe the new queue manager and its
679 1.1 tron preemptive scheduler algorithm. Note that the document was originally
680 1.1 tron written to describe the changes between the new queue manager (in
681 1.1 tron this text referred to as <tt>nqmgr</tt>, the name it was known by
682 1.1 tron before it became the default queue manager) and the old queue manager
683 1.1 tron (referred to as <tt>oqmgr</tt>). This is why it refers to <tt>oqmgr</tt>
684 1.1 tron every so often.
685 1.1 tron
686 1.1 tron </p>
687 1.1 tron
688 1.1 tron <p>
689 1.1 tron
690 1.1 tron This document is divided into sections as follows:
691 1.1 tron
692 1.1 tron </p>
693 1.1 tron
694 1.1 tron <ul>
695 1.1 tron
696 1.1 tron <li> <a href="#<tt>nqmgr</tt>_structures"> The structures used by
697 1.1 tron nqmgr </a>
698 1.1 tron
699 1.1 tron <li> <a href="#<tt>nqmgr</tt>_pickup"> What happens when nqmgr picks
700 1.1 tron up the message </a> - how it is assigned to transports, jobs, peers,
701 1.1 tron entries
702 1.1 tron
703 1.1 tron <li> <a href="#<tt>nqmgr</tt>_selection"> How the entry selection
704 1.1 tron works </a>
705 1.1 tron
706 1.1 tron <li> <a href="#<tt>nqmgr</tt>_preemption"> How the preemption
707 1.1 tron works </a> - what messages may be preempted and how and what messages
708 1.1 tron are chosen to preempt them
709 1.1 tron
710 1.1 tron <li> <a href="#<tt>nqmgr</tt>_concurrency"> How destination concurrency
711 1.1 tron limits affect the scheduling algorithm </a>
712 1.1 tron
713 1.1 tron <li> <a href="#<tt>nqmgr</tt>_memory"> Dealing with memory resource
714 1.1 tron limits </a>
715 1.1 tron
716 1.1 tron </ul>
717 1.1 tron
718 1.1 tron <h3> <a name="<tt>nqmgr</tt>_structures"> The structures used by
719 1.1 tron nqmgr </a> </h3>
720 1.1 tron
721 1.1 tron <p>
722 1.1 tron
723 1.1 tron Let's start by recapitulating the structures and terms used when
724 1.1.1.3.26.1 martin referring to the queue manager and how it operates. Many of these are
725 1.1 tron partially described elsewhere, but it is nice to have a coherent
726 1.1 tron overview in one place:
727 1.1 tron
728 1.1 tron </p>
729 1.1 tron
730 1.1 tron <ul>
731 1.1 tron
732 1.1 tron <li> <p> Each message structure represents one mail message which
733 1.1 tron Postfix is to deliver. The message recipients specify to what
734 1.1 tron destinations is the message to be delivered and what transports are
735 1.1 tron going to be used for the delivery. </p>
736 1.1 tron
737 1.1 tron <li> <p> Each recipient entry groups a batch of recipients of one
738 1.1.1.3 tron message which are all going to be delivered to the same destination
739 1.1.1.3 tron (and over the same transport).
740 1.1 tron </p>
741 1.1 tron
742 1.1 tron <li> <p> Each transport structure groups everything what is going
743 1.1 tron to be delivered by delivery agents dedicated for that transport.
744 1.1 tron Each transport maintains a set of queues (describing the destinations
745 1.1 tron it shall talk to) and jobs (referencing the messages it shall
746 1.1 tron deliver). </p>
747 1.1 tron
748 1.1 tron <li> <p> Each transport queue (not to be confused with the on-disk
749 1.1.1.3.26.1 martin "<a href="QSHAPE_README.html#active_queue">active" queue</a> or "<a href="QSHAPE_README.html#incoming_queue">incoming" queue</a>) groups everything what is going be
750 1.1 tron delivered to given destination (aka nexthop) by its transport. Each
751 1.1 tron queue belongs to one transport, so each destination may be referred
752 1.1 tron to by several queues, one for each transport. Each queue maintains
753 1.1 tron a list of all recipient entries (batches of message recipients)
754 1.1 tron which shall be delivered to given destination (the todo list), and
755 1.1 tron a list of recipient entries already being delivered by the delivery
756 1.1 tron agents (the busy list). </p>
757 1.1 tron
758 1.1 tron <li> <p> Each queue corresponds to multiple peer structures. Each
759 1.1 tron peer structure is like the queue structure, belonging to one transport
760 1.1 tron and referencing one destination. The difference is that it lists
761 1.1 tron only the recipient entries which all originate from the same message,
762 1.1 tron unlike the queue structure, whose entries may originate from various
763 1.1 tron messages. For messages with few recipients, there is usually just
764 1.1 tron one recipient entry for each destination, resulting in one recipient
765 1.1 tron entry per peer. But for large mailing list messages the recipients
766 1.1 tron may need to be split to multiple recipient entries, in which case
767 1.1 tron the peer structure may list many entries for single destination.
768 1.1 tron </p>
769 1.1 tron
770 1.1 tron <li> <p> Each transport job groups everything it takes to deliver
771 1.1 tron one message via its transport. Each job represents one message
772 1.1 tron within the context of the transport. The job belongs to one transport
773 1.1 tron and message, so each message may have multiple jobs, one for each
774 1.1 tron transport. The job groups all the peer structures, which describe
775 1.1 tron the destinations the job's message has to be delivered to. </p>
776 1.1 tron
777 1.1 tron </ul>
778 1.1 tron
779 1.1 tron <p>
780 1.1 tron
781 1.1 tron The first four structures are common to both <tt>nqmgr</tt> and
782 1.1 tron <tt>oqmgr</tt>, the latter two were introduced by <tt>nqmgr</tt>.
783 1.1 tron
784 1.1 tron </p>
785 1.1 tron
786 1.1 tron <p>
787 1.1 tron
788 1.1 tron These terms are used extensively in the text below, feel free to
789 1.1 tron look up the description above anytime you'll feel you have lost a
790 1.1 tron sense what is what.
791 1.1 tron
792 1.1 tron </p>
793 1.1 tron
794 1.1 tron <h3> <a name="<tt>nqmgr</tt>_pickup"> What happens when nqmgr picks
795 1.1 tron up the message </a> </h3>
796 1.1 tron
797 1.1 tron <p>
798 1.1 tron
799 1.1.1.3.26.1 martin Whenever <tt>nqmgr</tt> moves a queue file into the "<a href="QSHAPE_README.html#active_queue">active" queue</a>,
800 1.1 tron the following happens: It reads all necessary information from the
801 1.1 tron queue file as <tt>oqmgr</tt> does, and also reads as many recipients
802 1.1 tron as possible - more on that later, for now let's just pretend it
803 1.1 tron always reads all recipients.
804 1.1 tron
805 1.1 tron </p>
806 1.1 tron
807 1.1 tron <p>
808 1.1 tron
809 1.1 tron Then it resolves the recipients as <tt>oqmgr</tt> does, which
810 1.1 tron means obtaining (address, nexthop, transport) triple for each
811 1.1 tron recipient. For each triple, it finds the transport; if it does not
812 1.1 tron exist yet, it instantiates it (unless it's dead). Within the
813 1.1.1.3.26.1 martin transport, it finds the destination queue for the given nexthop; if it
814 1.1 tron does not exist yet, it instantiates it (unless it's dead). The
815 1.1 tron triple is then bound to given destination queue. This happens in
816 1.1 tron qmgr_resolve() and is basically the same as in <tt>oqmgr</tt>.
817 1.1 tron
818 1.1 tron </p>
819 1.1 tron
820 1.1 tron <p>
821 1.1 tron
822 1.1 tron Then for each triple which was bound to some queue (and thus
823 1.1 tron transport), the program finds the job which represents the message
824 1.1 tron within that transport's context; if it does not exist yet, it
825 1.1 tron instantiates it. Within the job, it finds the peer which represents
826 1.1 tron the bound destination queue within this jobs context; if it does
827 1.1 tron not exist yet, it instantiates it. Finally, it stores the address
828 1.1 tron from the resolved triple to the recipient entry which is appended
829 1.1 tron to both the queue entry list and the peer entry list. The addresses
830 1.1.1.3.26.1 martin for the same nexthop are batched in the entries up to the
831 1.1.1.3.26.1 martin <a href="postconf.5.html#transport_destination_recipient_limit"><i>transport</i>_destination_recipient_limit</a> for that transport.
832 1.1.1.3.26.1 martin This happens in qmgr_message_assign(), and apart
833 1.1.1.3.26.1 martin from that it operates with job and peer structures, it is basically the
834 1.1 tron same as in <tt>oqmgr</tt>.
835 1.1 tron
836 1.1 tron </p>
837 1.1 tron
838 1.1 tron <p>
839 1.1 tron
840 1.1 tron When the job is instantiated, it is enqueued on the transport's job
841 1.1 tron list based on the time its message was picked up by <tt>nqmgr</tt>.
842 1.1 tron For first batch of recipients this means it is appended to the end
843 1.1 tron of the job list, but the ordering of the job list by the enqueue
844 1.1 tron time is important as we will see shortly.
845 1.1 tron
846 1.1 tron </p>
847 1.1 tron
848 1.1 tron <p>
849 1.1 tron
850 1.1.1.3.26.1 martin [Now you should have a pretty good idea what the state of the
851 1.1.1.3.26.1 martin <tt>nqmgr</tt> is after a couple of messages were picked up, and what the
852 1.1.1.3.26.1 martin relation is between all those job, peer, queue and entry structures.]
853 1.1 tron
854 1.1 tron </p>
855 1.1 tron
856 1.1 tron <h3> <a name="<tt>nqmgr</tt>_selection"> How the entry selection
857 1.1 tron works </a> </h3>
858 1.1 tron
859 1.1 tron <p>
860 1.1 tron
861 1.1 tron Having prepared all those above mentioned structures, the task of
862 1.1 tron the <tt>nqmgr</tt>'s scheduler is to choose the recipient entries
863 1.1 tron one at a time and pass them to the delivery agent for corresponding
864 1.1 tron transport. Now how does this work?
865 1.1 tron
866 1.1 tron </p>
867 1.1 tron
868 1.1 tron <p>
869 1.1 tron
870 1.1 tron The first approximation of the new scheduling algorithm is like this:
871 1.1 tron
872 1.1 tron </p>
873 1.1 tron
874 1.1 tron <blockquote>
875 1.1 tron <pre>
876 1.1 tron foreach transport (round-robin-by-transport)
877 1.1 tron do
878 1.1 tron if transport busy continue
879 1.1 tron if transport process limit reached continue
880 1.1 tron foreach transport's job (in the order of the transport's job list)
881 1.1 tron do
882 1.1.1.3 tron foreach job's peer (round-robin-by-destination)
883 1.1.1.3 tron if peer->queue->concurrency < peer->queue->window
884 1.1.1.3 tron return next peer entry.
885 1.1.1.3 tron done
886 1.1 tron done
887 1.1 tron done
888 1.1 tron </pre>
889 1.1 tron </blockquote>
890 1.1 tron
891 1.1 tron <p>
892 1.1 tron
893 1.1 tron Now what is the "order of the transport's job list"? As we know
894 1.1 tron already, the job list is by default kept in the order the message
895 1.1 tron was picked up by the <tt>nqmgr</tt>. So by default we get the
896 1.1 tron top-level round-robin transport, and within each transport we get
897 1.1 tron the FIFO message delivery. The round-robin of the peers by the
898 1.1 tron destination is perhaps of little importance in most real-life cases
899 1.1.1.3.26.1 martin (unless the <a href="postconf.5.html#transport_destination_recipient_limit"><i>transport</i>_destination_recipient_limit</a> is reached,
900 1.1.1.3.26.1 martin in one job there
901 1.1 tron is only one peer structure for each destination), but theoretically
902 1.1 tron it makes sure that even within single jobs, destinations are treated
903 1.1 tron fairly.
904 1.1 tron
905 1.1 tron </p>
906 1.1 tron
907 1.1 tron <p>
908 1.1 tron
909 1.1 tron [By now you should have a feeling you really know how the scheduler
910 1.1 tron works, except for the preemption, under ideal conditions - that is,
911 1.1 tron no recipient resource limits and no destination concurrency problems.]
912 1.1 tron
913 1.1 tron </p>
914 1.1 tron
915 1.1 tron <h3> <a name="<tt>nqmgr</tt>_preemption"> How the preemption
916 1.1 tron works </a> </h3>
917 1.1 tron
918 1.1 tron <p>
919 1.1 tron
920 1.1 tron As you might perhaps expect by now, the transport's job list does
921 1.1 tron not remain sorted by the job's message enqueue time all the time.
922 1.1 tron The most cool thing about <tt>nqmgr</tt> is not the simple FIFO
923 1.1 tron delivery, but that it is able to slip mail with little recipients
924 1.1 tron past the mailing-list bulk mail. This is what the job preemption
925 1.1 tron is about - shuffling the jobs on the transport's job list to get
926 1.1 tron the best message delivery rates. Now how is it achieved?
927 1.1 tron
928 1.1 tron </p>
929 1.1 tron
930 1.1 tron <p>
931 1.1 tron
932 1.1 tron First I have to tell you that there are in fact two job lists in
933 1.1 tron each transport. One is the scheduler's job list, which the scheduler
934 1.1 tron is free to play with, while the other one keeps the jobs always
935 1.1 tron listed in the order of the enqueue time and is used for recipient
936 1.1 tron pool management we will discuss later. For now, we will deal with
937 1.1 tron the scheduler's job list only.
938 1.1 tron
939 1.1 tron </p>
940 1.1 tron
941 1.1 tron <p>
942 1.1 tron
943 1.1 tron So, we have the job list, which is first ordered by the time the
944 1.1 tron jobs' messages were enqueued, oldest messages first, the most recently
945 1.1 tron picked one at the end. For now, let's assume that there are no
946 1.1 tron destination concurrency problems. Without preemption, we pick some
947 1.1 tron entry of the first (oldest) job on the queue, assign it to delivery
948 1.1 tron agent, pick another one from the same job, assign it again, and so
949 1.1 tron on, until all the entries are used and the job is delivered. We
950 1.1 tron would then move onto the next job and so on and on. Now how do we
951 1.1 tron manage to sneak in some entries from the recently added jobs when
952 1.1 tron the first job on the job list belongs to a message going to the
953 1.1 tron mailing-list and has thousands of recipient entries?
954 1.1 tron
955 1.1 tron </p>
956 1.1 tron
957 1.1 tron <p>
958 1.1 tron
959 1.1 tron The <tt>nqmgr</tt>'s answer is that we can artificially "inflate"
960 1.1 tron the delivery time of that first job by some constant for free - it
961 1.1 tron is basically the same trick you might remember as "accumulation of
962 1.1 tron potential" from the amortized complexity lessons. For example,
963 1.1 tron instead of delivering the entries of the first job on the job list
964 1.1 tron every time a delivery agent becomes available, we can do it only
965 1.1 tron every second time. If you view the moments the delivery agent becomes
966 1.1 tron available on a timeline as "delivery slots", then instead of using
967 1.1 tron every delivery slot for the first job, we can use only every other
968 1.1 tron slot, and still the overall delivery efficiency of the first job
969 1.1 tron remains the same. So the delivery <tt>11112222</tt> becomes
970 1.1 tron <tt>1.1.1.1.2.2.2.2</tt> (1 and 2 are the imaginary job numbers, .
971 1.1 tron denotes the free slot). Now what do we do with free slots?
972 1.1 tron
973 1.1 tron </p>
974 1.1 tron
975 1.1 tron <p>
976 1.1 tron
977 1.1 tron As you might have guessed, we will use them for sneaking the mail
978 1.1 tron with little recipients in. For example, if we have one four-recipient
979 1.1 tron mail followed by four one recipients mail, the delivery sequence
980 1.1 tron (that is, the sequence in which the jobs are assigned to the
981 1.1 tron delivery slots) might look like this: <tt>12131415</tt>. Hmm, fine
982 1.1 tron for sneaking in the single recipient mail, but how do we sneak in
983 1.1 tron the mail with more than one recipient? Say if we have one four-recipient
984 1.1 tron mail followed by two two-recipient mails?
985 1.1 tron
986 1.1 tron </p>
987 1.1 tron
988 1.1 tron <p>
989 1.1 tron
990 1.1 tron The simple answer would be to use delivery sequence <tt>12121313</tt>.
991 1.1 tron But the problem is that this does not scale well. Imagine you have
992 1.1.1.3.26.1 martin mail with a thousand recipients followed by mail with a hundred recipients.
993 1.1 tron It is tempting to suggest the delivery sequence like <tt>121212....</tt>,
994 1.1 tron but alas! Imagine there arrives another mail with say ten recipients.
995 1.1 tron But there are no free slots anymore, so it can't slip by, not even
996 1.1.1.3.26.1 martin if it had only one recipient. It will be stuck until the
997 1.1 tron hundred-recipient mail is delivered, which really sucks.
998 1.1 tron
999 1.1 tron </p>
1000 1.1 tron
1001 1.1 tron <p>
1002 1.1 tron
1003 1.1 tron So, it becomes obvious that while inflating the message to get
1004 1.1.1.3.26.1 martin free slots is a great idea, one has to be really careful of how the
1005 1.1 tron free slots are assigned, otherwise one might corner himself. So,
1006 1.1 tron how does <tt>nqmgr</tt> really use the free slots?
1007 1.1 tron
1008 1.1 tron </p>
1009 1.1 tron
1010 1.1 tron <p>
1011 1.1 tron
1012 1.1 tron The key idea is that one does not have to generate the free slots
1013 1.1 tron in a uniform way. The delivery sequence <tt>111...1</tt> is no
1014 1.1 tron worse than <tt>1.1.1.1</tt>, in fact, it is even better as some
1015 1.1 tron entries are in the first case selected earlier than in the second
1016 1.1 tron case, and none is selected later! So it is possible to first
1017 1.1 tron "accumulate" the free delivery slots and then use them all at once.
1018 1.1 tron It is even possible to accumulate some, then use them, then accumulate
1019 1.1 tron some more and use them again, as in <tt>11..1.1</tt> .
1020 1.1 tron
1021 1.1 tron </p>
1022 1.1 tron
1023 1.1 tron <p>
1024 1.1 tron
1025 1.1 tron Let's get back to the one hundred recipient example. We now know
1026 1.1 tron that we could first accumulate one hundred free slots, and only
1027 1.1 tron after then to preempt the first job and sneak the one hundred
1028 1.1 tron recipient mail in. Applying the algorithm recursively, we see the
1029 1.1 tron hundred recipient job can accumulate ten free delivery slots, and
1030 1.1 tron then we could preempt it and sneak in the ten-recipient mail...
1031 1.1 tron Wait wait wait! Could we? Aren't we overinflating the original one
1032 1.1 tron thousand recipient mail?
1033 1.1 tron
1034 1.1 tron </p>
1035 1.1 tron
1036 1.1 tron <p>
1037 1.1 tron
1038 1.1.1.3.26.1 martin Well, despite the fact that it looks so at the first glance, another trick will
1039 1.1 tron allow us to answer "no, we are not!". If we had said that we will
1040 1.1 tron inflate the delivery time twice at maximum, and then we consider
1041 1.1 tron every other slot as a free slot, then we would overinflate in case
1042 1.1 tron of the recursive preemption. BUT! The trick is that if we use only
1043 1.1 tron every n-th slot as a free slot for n>2, there is always some worst
1044 1.1 tron inflation factor which we can guarantee not to be breached, even
1045 1.1 tron if we apply the algorithm recursively. To be precise, if for every
1046 1.1 tron k>1 normally used slots we accumulate one free delivery slot, than
1047 1.1 tron the inflation factor is not worse than k/(k-1) no matter how many
1048 1.1 tron recursive preemptions happen. And it's not worse than (k+1)/k if
1049 1.1 tron only non-recursive preemption happens. Now, having got through the
1050 1.1 tron theory and the related math, let's see how <tt>nqmgr</tt> implements
1051 1.1 tron this.
1052 1.1 tron
1053 1.1 tron </p>
1054 1.1 tron
1055 1.1 tron <p>
1056 1.1 tron
1057 1.1 tron Each job has so called "available delivery slot" counter. Each
1058 1.1 tron transport has a <a href="postconf.5.html#transport_delivery_slot_cost"><i>transport</i>_delivery_slot_cost</a> parameter, which
1059 1.1 tron defaults to <a href="postconf.5.html#default_delivery_slot_cost">default_delivery_slot_cost</a> parameter which is set to 5
1060 1.1 tron by default. This is the k from the paragraph above. Each time k
1061 1.1 tron entries of the job are selected for delivery, this counter is
1062 1.1.1.3.26.1 martin incremented by one. Once there are some slots accumulated, a job which
1063 1.1 tron requires no more than that number of slots to be fully delivered
1064 1.1 tron can preempt this job.
1065 1.1 tron
1066 1.1 tron </p>
1067 1.1 tron
1068 1.1 tron <p>
1069 1.1 tron
1070 1.1 tron [Well, the truth is, the counter is incremented every time an entry
1071 1.1.1.3 tron is selected and it is divided by k when it is used.
1072 1.1.1.3.26.1 martin But to understand, it's good enough to use
1073 1.1 tron the above approximation of the truth.]
1074 1.1 tron
1075 1.1 tron </p>
1076 1.1 tron
1077 1.1 tron <p>
1078 1.1 tron
1079 1.1 tron OK, so now we know the conditions which must be satisfied so one
1080 1.1 tron job can preempt another one. But what job gets preempted, how do
1081 1.1 tron we choose what job preempts it if there are several valid candidates,
1082 1.1 tron and when does all this exactly happen?
1083 1.1 tron
1084 1.1 tron </p>
1085 1.1 tron
1086 1.1 tron <p>
1087 1.1 tron
1088 1.1 tron The answer for the first part is simple. The job whose entry was
1089 1.1.1.3.26.1 martin selected the last time is the so called current job. Normally, it is
1090 1.1 tron the first job on the scheduler's job list, but destination concurrency
1091 1.1 tron limits may change this as we will see later. It is always only the
1092 1.1 tron current job which may get preempted.
1093 1.1 tron
1094 1.1 tron </p>
1095 1.1 tron
1096 1.1 tron <p>
1097 1.1 tron
1098 1.1.1.3.26.1 martin Now for the second part. The current job has a certain amount of
1099 1.1 tron recipient entries, and as such may accumulate at maximum some amount
1100 1.1 tron of available delivery slots. It might have already accumulated some,
1101 1.1 tron and perhaps even already used some when it was preempted before
1102 1.1 tron (remember a job can be preempted several times). In either case,
1103 1.1 tron we know how many are accumulated and how many are left to deliver,
1104 1.1 tron so we know how many it may yet accumulate at maximum. Every other
1105 1.1 tron job which may be delivered by less than that number of slots is a
1106 1.1 tron valid candidate for preemption. How do we choose among them?
1107 1.1 tron
1108 1.1 tron </p>
1109 1.1 tron
1110 1.1 tron <p>
1111 1.1 tron
1112 1.1 tron The answer is - the one with maximum enqueue_time/recipient_entry_count.
1113 1.1 tron That is, the older the job is, the more we should try to deliver
1114 1.1 tron it in order to get best message delivery rates. These rates are of
1115 1.1 tron course subject to how many recipients the message has, therefore
1116 1.1 tron the division by the recipient (entry) count. No one shall be surprised
1117 1.1.1.3.26.1 martin that a message with n recipients takes n times longer to deliver than
1118 1.1.1.3.26.1 martin a message with one recipient.
1119 1.1 tron
1120 1.1 tron </p>
1121 1.1 tron
1122 1.1 tron <p>
1123 1.1 tron
1124 1.1 tron Now let's recap the previous two paragraphs. Isn't it too complicated?
1125 1.1 tron Why don't the candidates come only among the jobs which can be
1126 1.1 tron delivered within the number of slots the current job already
1127 1.1 tron accumulated? Why do we need to estimate how much it has yet to
1128 1.1 tron accumulate? If you found out the answer, congratulate yourself. If
1129 1.1 tron we did it this simple way, we would always choose the candidate
1130 1.1.1.3.26.1 martin with the fewest recipient entries. If there were enough single recipient
1131 1.1 tron mails coming in, they would always slip by the bulk mail as soon
1132 1.1.1.3.26.1 martin as possible, and the two or more recipients mail would never get
1133 1.1 tron a chance, no matter how long they have been sitting around in the
1134 1.1 tron job list.
1135 1.1 tron
1136 1.1 tron </p>
1137 1.1 tron
1138 1.1 tron <p>
1139 1.1 tron
1140 1.1.1.3.26.1 martin This candidate selection has an interesting implication - that when
1141 1.1 tron we choose the best candidate for preemption (this is done in
1142 1.1 tron qmgr_choose_candidate()), it may happen that we may not use it for
1143 1.1 tron preemption immediately. This leads to an answer to the last part
1144 1.1 tron of the original question - when does the preemption happen?
1145 1.1 tron
1146 1.1 tron </p>
1147 1.1 tron
1148 1.1 tron <p>
1149 1.1 tron
1150 1.1 tron The preemption attempt happens every time next transport's recipient
1151 1.1 tron entry is to be chosen for delivery. To avoid needless overhead, the
1152 1.1 tron preemption is not attempted if the current job could never accumulate
1153 1.1 tron more than <a href="postconf.5.html#transport_minimum_delivery_slots"><i>transport</i>_minimum_delivery_slots</a> (defaults to
1154 1.1.1.3.26.1 martin <a href="postconf.5.html#default_minimum_delivery_slots">default_minimum_delivery_slots</a> which defaults to 3). If there are
1155 1.1 tron already enough accumulated slots to preempt the current job by the
1156 1.1 tron chosen best candidate, it is done immediately. This basically means
1157 1.1 tron that the candidate is moved in front of the current job on the
1158 1.1 tron scheduler's job list and decreasing the accumulated slot counter
1159 1.1.1.3.26.1 martin by the amount used by the candidate. If there are not enough slots...
1160 1.1 tron well, I could say that nothing happens and the another preemption
1161 1.1 tron is attempted the next time. But that's not the complete truth.
1162 1.1 tron
1163 1.1 tron </p>
1164 1.1 tron
1165 1.1 tron <p>
1166 1.1 tron
1167 1.1 tron The truth is that it turns out that it is not really necessary to
1168 1.1 tron wait until the jobs counter accumulates all the delivery slots in
1169 1.1 tron advance. Say we have ten-recipient mail followed by two two-recipient
1170 1.1.1.3.26.1 martin mails. If the preemption happened when enough delivery slots accumulate
1171 1.1 tron (assuming slot cost 2), the delivery sequence becomes
1172 1.1 tron <tt>11112211113311</tt>. Now what would we get if we would wait
1173 1.1 tron only for 50% of the necessary slots to accumulate and we promise
1174 1.1 tron we would wait for the remaining 50% later, after we get back
1175 1.1.1.3.26.1 martin to the preempted job? If we use such a slot loan, the delivery sequence
1176 1.1.1.3.26.1 martin becomes <tt>11221111331111</tt>. As we can see, it makes it not
1177 1.1 tron considerably worse for the delivery of the ten-recipient mail, but
1178 1.1 tron it allows the small messages to be delivered sooner.
1179 1.1 tron
1180 1.1 tron </p>
1181 1.1 tron
1182 1.1 tron <p>
1183 1.1 tron
1184 1.1 tron The concept of these slot loans is where the
1185 1.1 tron <a href="postconf.5.html#transport_delivery_slot_discount"><i>transport</i>_delivery_slot_discount</a> and
1186 1.1 tron <a href="postconf.5.html#transport_delivery_slot_loan"><i>transport</i>_delivery_slot_loan</a> come from (they default to
1187 1.1 tron <a href="postconf.5.html#default_delivery_slot_discount">default_delivery_slot_discount</a> and <a href="postconf.5.html#default_delivery_slot_loan">default_delivery_slot_loan</a>, whose
1188 1.1 tron values are by default 50 and 3, respectively). The discount (resp.
1189 1.1 tron loan) specifies how many percent (resp. how many slots) one "gets
1190 1.1 tron in advance", when the number of slots required to deliver the best
1191 1.1 tron candidate is compared with the number of slots the current slot had
1192 1.1 tron accumulated so far.
1193 1.1 tron
1194 1.1 tron </p>
1195 1.1 tron
1196 1.1 tron <p>
1197 1.1 tron
1198 1.1.1.3.26.1 martin And that pretty much concludes this chapter.
1199 1.1 tron
1200 1.1 tron </p>
1201 1.1 tron
1202 1.1 tron <p>
1203 1.1 tron
1204 1.1 tron [Now you should have a feeling that you pretty much understand the
1205 1.1.1.3.26.1 martin scheduler and the preemption, or at least that you will have
1206 1.1.1.3.26.1 martin after you read the last chapter a couple more times. You shall clearly
1207 1.1 tron see the job list and the preemption happening at its head, in ideal
1208 1.1 tron delivery conditions. The feeling of understanding shall last until
1209 1.1 tron you start wondering what happens if some of the jobs are blocked,
1210 1.1 tron which you might eventually figure out correctly from what had been
1211 1.1 tron said already. But I would be surprised if your mental image of the
1212 1.1 tron scheduler's functionality is not completely shattered once you
1213 1.1 tron start wondering how it works when not all recipients may be read
1214 1.1 tron in-core. More on that later.]
1215 1.1 tron
1216 1.1 tron </p>
1217 1.1 tron
1218 1.1 tron <h3> <a name="<tt>nqmgr</tt>_concurrency"> How destination concurrency
1219 1.1 tron limits affect the scheduling algorithm </a> </h3>
1220 1.1 tron
1221 1.1 tron <p>
1222 1.1 tron
1223 1.1 tron The <tt>nqmgr</tt> uses the same algorithm for destination concurrency
1224 1.1 tron control as <tt>oqmgr</tt>. Now what happens when the destination
1225 1.1 tron limits are reached and no more entries for that destination may be
1226 1.1 tron selected by the scheduler?
1227 1.1 tron
1228 1.1 tron </p>
1229 1.1 tron
1230 1.1 tron <p>
1231 1.1 tron
1232 1.1.1.3.26.1 martin From the user's point of view it is all simple. If some of the peers
1233 1.1 tron of a job can't be selected, those peers are simply skipped by the
1234 1.1 tron entry selection algorithm (the pseudo-code described before) and
1235 1.1 tron only the selectable ones are used. If none of the peers may be
1236 1.1 tron selected, the job is declared a "blocker job". Blocker jobs are
1237 1.1 tron skipped by the entry selection algorithm and they are also excluded
1238 1.1.1.3.26.1 martin from the candidates for preemption of the current job. Thus the scheduler
1239 1.1 tron effectively behaves as if the blocker jobs didn't exist on the job
1240 1.1 tron list at all. As soon as at least one of the peers of a blocker job
1241 1.1 tron becomes unblocked (that is, the delivery agent handling the delivery
1242 1.1.1.3.26.1 martin of the recipient entry for the given destination successfully finishes),
1243 1.1 tron the job's blocker status is removed and the job again participates
1244 1.1 tron in all further scheduler actions normally.
1245 1.1 tron
1246 1.1 tron </p>
1247 1.1 tron
1248 1.1 tron <p>
1249 1.1 tron
1250 1.1 tron So the summary is that the users don't really have to be concerned
1251 1.1 tron about the interaction of the destination limits and scheduling
1252 1.1 tron algorithm. It works well on its own and there are no knobs they
1253 1.1 tron would need to control it.
1254 1.1 tron
1255 1.1 tron </p>
1256 1.1 tron
1257 1.1 tron <p>
1258 1.1 tron
1259 1.1 tron From a programmer's point of view, the blocker jobs complicate the
1260 1.1 tron scheduler quite a lot. Without them, the jobs on the job list would
1261 1.1 tron be normally delivered in strict FIFO order. If the current job is
1262 1.1 tron preempted, the job preempting it is completely delivered unless it
1263 1.1 tron is preempted itself. Without blockers, the current job is thus
1264 1.1 tron always either the first job on the job list, or the top of the stack
1265 1.1 tron of jobs preempting the first job on the job list.
1266 1.1 tron
1267 1.1 tron </p>
1268 1.1 tron
1269 1.1 tron <p>
1270 1.1 tron
1271 1.1 tron The visualization of the job list and the preemption stack without
1272 1.1 tron blockers would be like this:
1273 1.1 tron
1274 1.1 tron </p>
1275 1.1 tron
1276 1.1 tron <blockquote>
1277 1.1 tron <pre>
1278 1.1 tron first job-> 1--2--3--5--6--8--... <- job list
1279 1.1 tron on job list |
1280 1.1 tron 4 <- preemption stack
1281 1.1 tron |
1282 1.1 tron current job-> 7
1283 1.1 tron </pre>
1284 1.1 tron </blockquote>
1285 1.1 tron
1286 1.1 tron <p>
1287 1.1 tron
1288 1.1 tron In the example above we see that job 1 was preempted by job 4 and
1289 1.1 tron then job 4 was preempted by job 7. After job 7 is completed, remaining
1290 1.1 tron entries of job 4 are selected, and once they are all selected, job
1291 1.1 tron 1 continues.
1292 1.1 tron
1293 1.1 tron </p>
1294 1.1 tron
1295 1.1 tron <p>
1296 1.1 tron
1297 1.1 tron As we see, it's all very clean and straightforward. Now how does
1298 1.1 tron this change because of blockers?
1299 1.1 tron
1300 1.1 tron </p>
1301 1.1 tron
1302 1.1 tron <p>
1303 1.1 tron
1304 1.1.1.3.26.1 martin The answer is: a lot. Any job may become a blocker job at any time,
1305 1.1.1.3.26.1 martin and also become a normal job again at any time. This has several
1306 1.1 tron important implications:
1307 1.1 tron
1308 1.1 tron </p>
1309 1.1 tron
1310 1.1 tron <ol>
1311 1.1 tron
1312 1.1 tron <li> <p>
1313 1.1 tron
1314 1.1 tron The jobs may be completed in arbitrary order. For example, in the
1315 1.1 tron example above, if the current job 7 becomes blocked, the next job
1316 1.1 tron 4 may complete before the job 7 becomes unblocked again. Or if both
1317 1.1 tron 7 and 4 are blocked, then 1 is completed, then 7 becomes unblocked
1318 1.1 tron and is completed, then 2 is completed and only after that 4 becomes
1319 1.1 tron unblocked and is completed... You get the idea.
1320 1.1 tron
1321 1.1 tron </p>
1322 1.1 tron
1323 1.1 tron <p>
1324 1.1 tron
1325 1.1 tron [Interesting side note: even when jobs are delivered out of order,
1326 1.1.1.3.26.1 martin from a single destination's point of view the jobs are still delivered
1327 1.1 tron in the expected order (that is, FIFO unless there was some preemption
1328 1.1 tron involved). This is because whenever a destination queue becomes
1329 1.1 tron unblocked (the destination limit allows selection of more recipient
1330 1.1 tron entries for that destination), all jobs which have peers for that
1331 1.1 tron destination are unblocked at once.]
1332 1.1 tron
1333 1.1 tron </p>
1334 1.1 tron
1335 1.1 tron <li> <p>
1336 1.1 tron
1337 1.1 tron The idea of the preemption stack at the head of the job list is
1338 1.1 tron gone. That is, it must be possible to preempt any job on the job
1339 1.1 tron list. For example, if the jobs 7, 4, 1 and 2 in the example above
1340 1.1 tron become all blocked, job 3 becomes the current job. And of course
1341 1.1 tron we do not want the preemption to be affected by the fact that there
1342 1.1 tron are some blocked jobs or not. Therefore, if it turns out that job
1343 1.1 tron 3 might be preempted by job 6, the implementation shall make it
1344 1.1 tron possible.
1345 1.1 tron
1346 1.1 tron </p>
1347 1.1 tron
1348 1.1 tron <li> <p>
1349 1.1 tron
1350 1.1 tron The idea of the linear preemption stack itself is gone. It's no
1351 1.1 tron longer true that one job is always preempted by only one job at one
1352 1.1 tron time (that is directly preempted, not counting the recursively
1353 1.1 tron nested jobs). For example, in the example above, job 1 is directly
1354 1.1 tron preempted by only job 4, and job 4 by job 7. Now assume job 7 becomes
1355 1.1 tron blocked, and job 4 is being delivered. If it accumulates enough
1356 1.1 tron delivery slots, it is natural that it might be preempted for example
1357 1.1 tron by job 8. Now job 4 is preempted by both job 7 AND job 8 at the
1358 1.1 tron same time.
1359 1.1 tron
1360 1.1 tron </p>
1361 1.1 tron
1362 1.1 tron </ol>
1363 1.1 tron
1364 1.1 tron <p>
1365 1.1 tron
1366 1.1 tron Now combine the points 2) and 3) with point 1) again and you realize
1367 1.1 tron that the relations on the once linear job list became pretty
1368 1.1 tron complicated. If we extend the point 3) example: jobs 7 and 8 preempt
1369 1.1 tron job 4, now job 8 becomes blocked too, then job 4 completes. Tricky,
1370 1.1 tron huh?
1371 1.1 tron
1372 1.1 tron </p>
1373 1.1 tron
1374 1.1 tron <p>
1375 1.1 tron
1376 1.1 tron If I illustrate the relations after the above mentioned examples
1377 1.1.1.3.26.1 martin (but those in point 1), the situation would look like this:
1378 1.1 tron
1379 1.1 tron </p>
1380 1.1 tron
1381 1.1 tron <blockquote>
1382 1.1 tron <pre>
1383 1.1 tron v- parent
1384 1.1 tron
1385 1.1 tron adoptive parent -> 1--2--3--5--... <- "stack" level 0
1386 1.1 tron | |
1387 1.1 tron parent gone -> ? 6 <- "stack" level 1
1388 1.1 tron / \
1389 1.1 tron children -> 7 8 ^- child <- "stack" level 2
1390 1.1 tron
1391 1.1 tron ^- siblings
1392 1.1 tron </pre>
1393 1.1 tron </blockquote>
1394 1.1 tron
1395 1.1 tron <p>
1396 1.1 tron
1397 1.1 tron Now how does <tt>nqmgr</tt> deal with all these complicated relations?
1398 1.1 tron
1399 1.1 tron </p>
1400 1.1 tron
1401 1.1 tron <p>
1402 1.1 tron
1403 1.1 tron Well, it maintains them all as described, but fortunately, all these
1404 1.1.1.3.26.1 martin relations are necessary only for the purpose of proper counting of
1405 1.1.1.3.26.1 martin available delivery slots. For the purpose of ordering the jobs for
1406 1.1 tron entry selection, the original rule still applies: "the job preempting
1407 1.1 tron the current job is moved in front of the current job on the job
1408 1.1 tron list". So for entry selection purposes, the job relations remain
1409 1.1 tron as simple as this:
1410 1.1 tron
1411 1.1 tron </p>
1412 1.1 tron
1413 1.1 tron <blockquote>
1414 1.1 tron <pre>
1415 1.1 tron 7--8--1--2--6--3--5--.. <- scheduler's job list order
1416 1.1 tron </pre>
1417 1.1 tron </blockquote>
1418 1.1 tron
1419 1.1 tron <p>
1420 1.1 tron
1421 1.1 tron The job list order and the preemption parent/child/siblings relations
1422 1.1 tron are maintained separately. And because the selection works only
1423 1.1 tron with the job list, you can happily forget about those complicated
1424 1.1 tron relations unless you want to study the <tt>nqmgr</tt> sources. In
1425 1.1 tron that case the text above might provide some helpful introduction
1426 1.1 tron to the problem domain. Otherwise I suggest you just forget about
1427 1.1 tron all this and stick with the user's point of view: the blocker jobs
1428 1.1 tron are simply ignored.
1429 1.1 tron
1430 1.1 tron </p>
1431 1.1 tron
1432 1.1 tron <p>
1433 1.1 tron
1434 1.1.1.3.26.1 martin [By now, you should have a feeling that there are more things going
1435 1.1.1.3.26.1 martin on under the hood than you ever wanted to know. You decide that
1436 1.1 tron forgetting about this chapter is the best you can do for the sake
1437 1.1 tron of your mind's health and you basically stick with the idea how the
1438 1.1 tron scheduler works in ideal conditions, when there are no blockers,
1439 1.1 tron which is good enough.]
1440 1.1 tron
1441 1.1 tron </p>
1442 1.1 tron
1443 1.1 tron <h3> <a name="<tt>nqmgr</tt>_memory"> Dealing with memory resource
1444 1.1 tron limits </a> </h3>
1445 1.1 tron
1446 1.1 tron <p>
1447 1.1 tron
1448 1.1 tron When discussing the <tt>nqmgr</tt> scheduler, we have so far assumed
1449 1.1.1.3.26.1 martin that all recipients of all messages in the "<a href="QSHAPE_README.html#active_queue">active" queue</a> are completely
1450 1.1.1.3.26.1 martin read into memory. This is simply not true. There is an upper
1451 1.1 tron bound on the amount of memory the <tt>nqmgr</tt> may use, and
1452 1.1 tron therefore it must impose some limits on the information it may store
1453 1.1.1.3.26.1 martin in memory at any given time.
1454 1.1 tron
1455 1.1 tron </p>
1456 1.1 tron
1457 1.1 tron <p>
1458 1.1 tron
1459 1.1 tron First of all, not all messages may be read in-core at once. At any
1460 1.1 tron time, only <a href="postconf.5.html#qmgr_message_active_limit">qmgr_message_active_limit</a> messages may be read in-core
1461 1.1 tron at maximum. When read into memory, the messages are picked from the
1462 1.1.1.3.26.1 martin "<a href="QSHAPE_README.html#incoming_queue">incoming"</a> and "<a href="QSHAPE_README.html#deferred_queue">deferred" queues</a> and moved to the "<a href="QSHAPE_README.html#active_queue">active" queue</a>
1463 1.1.1.3.26.1 martin (incoming having priority), so if there are more than
1464 1.1.1.3.26.1 martin <a href="postconf.5.html#qmgr_message_active_limit">qmgr_message_active_limit</a> messages queued in the "<a href="QSHAPE_README.html#active_queue">active" queue</a>, the
1465 1.1.1.3.26.1 martin rest will have to wait until (some of) the messages in the "<a href="QSHAPE_README.html#active_queue">active" queue</a>
1466 1.1.1.3.26.1 martin are completely delivered (or deferred).
1467 1.1 tron
1468 1.1 tron </p>
1469 1.1 tron
1470 1.1 tron <p>
1471 1.1 tron
1472 1.1 tron Even with the limited amount of in-core messages, there is another
1473 1.1 tron limit which must be imposed in order to avoid memory exhaustion.
1474 1.1.1.3.26.1 martin Each message may contain a huge number of recipients (tens or hundreds
1475 1.1 tron of thousands are not uncommon), so if <tt>nqmgr</tt> read all
1476 1.1.1.3.26.1 martin recipients of all messages in the "<a href="QSHAPE_README.html#active_queue">active" queue</a>, it may easily run
1477 1.1 tron out of memory. Therefore there must be some upper bound on the
1478 1.1.1.3.26.1 martin amount of message recipients which are read into memory at the
1479 1.1 tron same time.
1480 1.1 tron
1481 1.1 tron </p>
1482 1.1 tron
1483 1.1 tron <p>
1484 1.1 tron
1485 1.1 tron Before discussing how exactly <tt>nqmgr</tt> implements the recipient
1486 1.1 tron limits, let's see how the sole existence of the limits themselves
1487 1.1 tron affects the <tt>nqmgr</tt> and its scheduler.
1488 1.1 tron
1489 1.1 tron </p>
1490 1.1 tron
1491 1.1 tron <p>
1492 1.1 tron
1493 1.1 tron The message limit is straightforward - it just limits the size of
1494 1.1 tron the
1495 1.1 tron lookahead the <tt>nqmgr</tt>'s scheduler has when choosing which
1496 1.1.1.3.26.1 martin message can preempt the current one. Messages not in the "<a href="QSHAPE_README.html#active_queue">active" queue</a>
1497 1.1.1.3.26.1 martin are simply not considered at all.
1498 1.1 tron
1499 1.1 tron </p>
1500 1.1 tron
1501 1.1 tron <p>
1502 1.1 tron
1503 1.1 tron The recipient limit complicates more things. First of all, the
1504 1.1 tron message reading code must support reading the recipients in batches,
1505 1.1 tron which among other things means accessing the queue file several
1506 1.1 tron times and continuing where the last recipient batch ended. This is
1507 1.1 tron invoked by the scheduler whenever the current job has space for more
1508 1.1 tron recipients, subject to transport's refill_limit and refill_delay parameters.
1509 1.1 tron It is also done any time when all
1510 1.1 tron in-core recipients of the message are dealt with (which may also
1511 1.1 tron mean they were deferred) but there are still more in the queue file.
1512 1.1 tron
1513 1.1 tron </p>
1514 1.1 tron
1515 1.1 tron <p>
1516 1.1 tron
1517 1.1 tron The second complication is that with some recipients left unread
1518 1.1 tron in the queue file, the scheduler can't operate with exact counts
1519 1.1 tron of recipient entries. With unread recipients, it is not clear how
1520 1.1 tron many recipient entries there will be, as they are subject to
1521 1.1 tron per-destination grouping. It is not even clear to what transports
1522 1.1 tron (and thus jobs) the recipients will be assigned. And with messages
1523 1.1.1.3.26.1 martin coming from the "<a href="QSHAPE_README.html#deferred_queue">deferred" queue</a>, it is not even clear how many unread
1524 1.1 tron recipients are still to be delivered. This all means that the
1525 1.1 tron scheduler must use only estimates of how many recipients entries
1526 1.1 tron there will be. Fortunately, it is possible to estimate the minimum
1527 1.1 tron and maximum correctly, so the scheduler can always err on the safe
1528 1.1.1.3.26.1 martin side. Obviously, the better the estimates, the better the results, so
1529 1.1 tron it is best when we are able to read all recipients in-core and turn
1530 1.1 tron the estimates into exact counts, or at least try to read as many
1531 1.1 tron as possible to make the estimates as accurate as possible.
1532 1.1 tron
1533 1.1 tron </p>
1534 1.1 tron
1535 1.1 tron <p>
1536 1.1 tron
1537 1.1 tron The third complication is that it is no longer true that the scheduler
1538 1.1 tron is done with a job once all of its in-core recipients are delivered.
1539 1.1 tron It is possible that the job will be revived later, when another
1540 1.1 tron batch of recipients is read in core. It is also possible that some
1541 1.1 tron jobs will be created for the first time long after the first batch
1542 1.1 tron of recipients was read in core. The <tt>nqmgr</tt> code must be
1543 1.1 tron ready to handle all such situations.
1544 1.1 tron
1545 1.1 tron </p>
1546 1.1 tron
1547 1.1 tron <p>
1548 1.1 tron
1549 1.1 tron And finally, the fourth complication is that the <tt>nqmgr</tt>
1550 1.1 tron code must somehow impose the recipient limit itself. Now how does
1551 1.1 tron it achieve it?
1552 1.1 tron
1553 1.1 tron </p>
1554 1.1 tron
1555 1.1 tron <p>
1556 1.1 tron
1557 1.1 tron Perhaps the easiest solution would be to say that each message may
1558 1.1.1.3.26.1 martin have at maximum X recipients stored in-core, but such a solution would
1559 1.1 tron be poor for several reasons. With reasonable <a href="postconf.5.html#qmgr_message_active_limit">qmgr_message_active_limit</a>
1560 1.1.1.3.26.1 martin values, the X would have to be quite low to maintain a reasonable
1561 1.1 tron memory footprint. And with low X lots of things would not work well.
1562 1.1 tron The <tt>nqmgr</tt> would have problems to use the
1563 1.1 tron <a href="postconf.5.html#transport_destination_recipient_limit"><i>transport</i>_destination_recipient_limit</a> efficiently. The
1564 1.1 tron scheduler's preemption would be suboptimal as the recipient count
1565 1.1 tron estimates would be inaccurate. The message queue file would have
1566 1.1 tron to be accessed many times to read in more recipients again and
1567 1.1 tron again.
1568 1.1 tron
1569 1.1 tron </p>
1570 1.1 tron
1571 1.1 tron <p>
1572 1.1 tron
1573 1.1 tron Therefore it seems reasonable to have a solution which does not use
1574 1.1.1.3.26.1 martin a limit imposed on a per-message basis, but which maintains a pool
1575 1.1 tron of available recipient slots, which can be shared among all messages
1576 1.1 tron in the most efficient manner. And as we do not want separate
1577 1.1 tron transports to compete for resources whenever possible, it seems
1578 1.1.1.3.26.1 martin appropriate to maintain such a recipient pool for each transport
1579 1.1 tron separately. This is the general idea, now how does it work in
1580 1.1 tron practice?
1581 1.1 tron
1582 1.1 tron </p>
1583 1.1 tron
1584 1.1 tron <p>
1585 1.1 tron
1586 1.1.1.3.26.1 martin First we have to solve a little chicken-and-egg problem. If we want
1587 1.1 tron to use the per-transport recipient pools, we first need to know to
1588 1.1.1.3.26.1 martin what transport(s) the message is assigned. But we will find that
1589 1.1.1.3.26.1 martin out only after we first read in the recipients. So it is obvious
1590 1.1 tron that we first have to read in some recipients, use them to find out
1591 1.1.1.3.26.1 martin to what transports the message is to be assigned, and only after
1592 1.1.1.3.26.1 martin that can we use the per-transport recipient pools.
1593 1.1 tron
1594 1.1 tron </p>
1595 1.1 tron
1596 1.1 tron <p>
1597 1.1 tron
1598 1.1 tron Now how many recipients shall we read for the first time? This is
1599 1.1 tron what <a href="postconf.5.html#qmgr_message_recipient_minimum">qmgr_message_recipient_minimum</a> and <a href="postconf.5.html#qmgr_message_recipient_limit">qmgr_message_recipient_limit</a>
1600 1.1 tron values control. The <a href="postconf.5.html#qmgr_message_recipient_minimum">qmgr_message_recipient_minimum</a> value specifies
1601 1.1.1.3.26.1 martin how many recipients of each message we will read the first time,
1602 1.1 tron no matter what. It is necessary to read at least one recipient
1603 1.1 tron before we can assign the message to a transport and create the first
1604 1.1 tron job. However, reading only <a href="postconf.5.html#qmgr_message_recipient_minimum">qmgr_message_recipient_minimum</a> recipients
1605 1.1 tron even if there are only few messages with few recipients in-core would
1606 1.1.1.3.26.1 martin be wasteful. Therefore if there are fewer than <a href="postconf.5.html#qmgr_message_recipient_limit">qmgr_message_recipient_limit</a>
1607 1.1 tron recipients in-core so far, the first batch of recipients may be
1608 1.1 tron larger than <a href="postconf.5.html#qmgr_message_recipient_minimum">qmgr_message_recipient_minimum</a> - as large as is required
1609 1.1 tron to reach the <a href="postconf.5.html#qmgr_message_recipient_limit">qmgr_message_recipient_limit</a> limit.
1610 1.1 tron
1611 1.1 tron </p>
1612 1.1 tron
1613 1.1 tron <p>
1614 1.1 tron
1615 1.1 tron Once the first batch of recipients was read in core and the message
1616 1.1 tron jobs were created, the size of the subsequent recipient batches (if
1617 1.1 tron any - of course it's best when all recipients are read in one batch)
1618 1.1 tron is based solely on the position of the message jobs on their
1619 1.1 tron corresponding transports' job lists. Each transport has a pool of
1620 1.1 tron <a href="postconf.5.html#transport_recipient_limit"><i>transport</i>_recipient_limit</a> recipient slots which it can
1621 1.1 tron distribute among its jobs (how this is done is described later).
1622 1.1 tron The subsequent recipient batch may be as large as the sum of all
1623 1.1 tron recipient slots of all jobs of the message permits (plus the
1624 1.1 tron <a href="postconf.5.html#qmgr_message_recipient_minimum">qmgr_message_recipient_minimum</a> amount which always applies).
1625 1.1 tron
1626 1.1 tron </p>
1627 1.1 tron
1628 1.1 tron <p>
1629 1.1 tron
1630 1.1.1.3.26.1 martin For example, if a message has three jobs, the first with 1 recipient
1631 1.1.1.3.26.1 martin still in-core and 4 recipient slots, the second with 5 recipients in-core
1632 1.1.1.3.26.1 martin and 5 recipient slots, and the third with 2 recipients in-core and 0
1633 1.1.1.3.26.1 martin recipient slots, it has 1+5+2=8 recipients in-core and 4+5+0=9 jobs'
1634 1.1 tron recipients slots in total. This means that we could immediately
1635 1.1 tron read 2+<a href="postconf.5.html#qmgr_message_recipient_minimum">qmgr_message_recipient_minimum</a> more recipients of that message
1636 1.1 tron in core.
1637 1.1 tron
1638 1.1 tron </p>
1639 1.1 tron
1640 1.1 tron <p>
1641 1.1 tron
1642 1.1 tron The above example illustrates several things which might be worth
1643 1.1 tron mentioning explicitly: first, note that although the per-transport
1644 1.1 tron slots are assigned to particular jobs, we can't guarantee that once
1645 1.1 tron the next batch of recipients is read in core, that the corresponding
1646 1.1 tron amounts of recipients will be assigned to those jobs. The jobs lend
1647 1.1 tron its slots to the message as a whole, so it is possible that some
1648 1.1 tron jobs end up sponsoring other jobs of their message. For example,
1649 1.1 tron if in the example above the 2 newly read recipients were assigned
1650 1.1 tron to the second job, the first job sponsored the second job with 2
1651 1.1 tron slots. The second notable thing is the third job, which has more
1652 1.1 tron recipients in-core than it has slots. Apart from sponsoring by other
1653 1.1 tron job we just saw it can be result of the first recipient batch, which
1654 1.1 tron is sponsored from global recipient pool of <a href="postconf.5.html#qmgr_message_recipient_limit">qmgr_message_recipient_limit</a>
1655 1.1 tron recipients. It can be also sponsored from the message recipient
1656 1.1 tron pool of <a href="postconf.5.html#qmgr_message_recipient_minimum">qmgr_message_recipient_minimum</a> recipients.
1657 1.1 tron
1658 1.1 tron </p>
1659 1.1 tron
1660 1.1 tron <p>
1661 1.1 tron
1662 1.1 tron Now how does each transport distribute the recipient slots among
1663 1.1 tron its jobs? The strategy is quite simple. As most scheduler activity
1664 1.1 tron happens on the head of the job list, it is our intention to make
1665 1.1 tron sure that the scheduler has the best estimates of the recipient
1666 1.1 tron counts for those jobs. As we mentioned above, this means that we
1667 1.1 tron want to try to make sure that the messages of those jobs have all
1668 1.1 tron recipients read in-core. Therefore the transport distributes the
1669 1.1 tron slots "along" the job list from start to end. In this case the job
1670 1.1 tron list sorted by message enqueue time is used, because it doesn't
1671 1.1 tron change over time as the scheduler's job list does.
1672 1.1 tron
1673 1.1 tron </p>
1674 1.1 tron
1675 1.1 tron <p>
1676 1.1 tron
1677 1.1 tron More specifically, each time a job is created and appended to the
1678 1.1 tron job list, it gets all unused recipient slots from its transport's
1679 1.1 tron pool. It keeps them until all recipients of its message are read.
1680 1.1 tron When this happens, all unused recipient slots are transferred to
1681 1.1.1.3.26.1 martin the next job (which is now in fact the first such job) on the job
1682 1.1 tron list which still has some recipients unread, or eventually back to
1683 1.1.1.3.26.1 martin the transport pool if there is no such job. Such a transfer then also
1684 1.1 tron happens whenever a recipient entry of that job is delivered.
1685 1.1 tron
1686 1.1 tron </p>
1687 1.1 tron
1688 1.1 tron <p>
1689 1.1 tron
1690 1.1 tron There is also a scenario when a job is not appended to the end of
1691 1.1.1.3.26.1 martin the job list (for example it was created as a result of a second or
1692 1.1 tron later recipient batch). Then it works exactly as above, except that
1693 1.1 tron if it was put in front of the first unread job (that is, the job
1694 1.1.1.3.26.1 martin of a message which still has some unread recipients in the queue file),
1695 1.1 tron that job is first forced to return all of its unused recipient slots
1696 1.1 tron to the transport pool.
1697 1.1 tron
1698 1.1 tron </p>
1699 1.1 tron
1700 1.1 tron <p>
1701 1.1 tron
1702 1.1 tron The algorithm just described leads to the following state: The first
1703 1.1 tron unread job on the job list always gets all the remaining recipient
1704 1.1 tron slots of that transport (if there are any). The jobs queued before
1705 1.1 tron this job are completely read (that is, all recipients of their
1706 1.1 tron message were already read in core) and have at maximum as many slots
1707 1.1 tron as they still have recipients in-core (the maximum is there because
1708 1.1 tron of the sponsoring mentioned before) and the jobs after this job get
1709 1.1 tron nothing from the transport recipient pool (unless they got something
1710 1.1 tron before and then the first unread job was created and enqueued in
1711 1.1.1.3.26.1 martin front of them later - in such a case, they also get at maximum as many
1712 1.1 tron slots as they have recipients in-core).
1713 1.1 tron
1714 1.1 tron </p>
1715 1.1 tron
1716 1.1 tron <p>
1717 1.1 tron
1718 1.1.1.3.26.1 martin Things work fine in such a state for most of the time, because the
1719 1.1.1.3.26.1 martin current job is either completely read in-core or has as many recipient
1720 1.1 tron slots as there are, but there is one situation which we still have
1721 1.1 tron to take care of specially. Imagine if the current job is preempted
1722 1.1 tron by some unread job from the job list and there are no more recipient
1723 1.1 tron slots available, so this new current job could read only batches
1724 1.1 tron of <a href="postconf.5.html#qmgr_message_recipient_minimum">qmgr_message_recipient_minimum</a> recipients at a time. This would
1725 1.1.1.3.26.1 martin really degrade performance. For this reason, each transport has an
1726 1.1 tron extra pool of <a href="postconf.5.html#transport_extra_recipient_limit"><i>transport</i>_extra_recipient_limit</a> recipient
1727 1.1 tron slots, dedicated exactly for this situation. Each time an unread
1728 1.1 tron job preempts the current job, it gets half of the remaining recipient
1729 1.1 tron slots from the normal pool and this extra pool.
1730 1.1 tron
1731 1.1 tron </p>
1732 1.1 tron
1733 1.1 tron <p>
1734 1.1 tron
1735 1.1 tron And that's it. It sure does sound pretty complicated, but fortunately
1736 1.1.1.3.26.1 martin most people don't really have to care exactly how it works as long
1737 1.1 tron as it works. Perhaps the only important things to know for most
1738 1.1 tron people are the following upper bound formulas:
1739 1.1 tron
1740 1.1 tron </p>
1741 1.1 tron
1742 1.1 tron <p>
1743 1.1 tron
1744 1.1 tron Each transport has at maximum
1745 1.1 tron
1746 1.1 tron </p>
1747 1.1 tron
1748 1.1 tron <blockquote>
1749 1.1 tron <pre>
1750 1.1 tron max(
1751 1.1 tron <a href="postconf.5.html#qmgr_message_recipient_minimum">qmgr_message_recipient_minimum</a> * <a href="postconf.5.html#qmgr_message_active_limit">qmgr_message_active_limit</a>
1752 1.1 tron + *_recipient_limit + *_extra_recipient_limit,
1753 1.1 tron <a href="postconf.5.html#qmgr_message_recipient_limit">qmgr_message_recipient_limit</a>
1754 1.1 tron )
1755 1.1 tron </pre>
1756 1.1 tron </blockquote>
1757 1.1 tron
1758 1.1 tron <p>
1759 1.1 tron
1760 1.1 tron recipients in core.
1761 1.1 tron
1762 1.1 tron </p>
1763 1.1 tron
1764 1.1 tron <p>
1765 1.1 tron
1766 1.1 tron The total amount of recipients in core is
1767 1.1 tron
1768 1.1 tron </p>
1769 1.1 tron
1770 1.1 tron <blockquote>
1771 1.1 tron <pre>
1772 1.1 tron max(
1773 1.1 tron <a href="postconf.5.html#qmgr_message_recipient_minimum">qmgr_message_recipient_minimum</a> * <a href="postconf.5.html#qmgr_message_active_limit">qmgr_message_active_limit</a>
1774 1.1 tron + sum( *_recipient_limit + *_extra_recipient_limit ),
1775 1.1 tron <a href="postconf.5.html#qmgr_message_recipient_limit">qmgr_message_recipient_limit</a>
1776 1.1 tron )
1777 1.1 tron </pre>
1778 1.1 tron </blockquote>
1779 1.1 tron
1780 1.1 tron <p>
1781 1.1 tron
1782 1.1 tron where the sum is over all used transports.
1783 1.1 tron
1784 1.1 tron </p>
1785 1.1 tron
1786 1.1 tron <p>
1787 1.1 tron
1788 1.1 tron And this terribly complicated chapter concludes the documentation
1789 1.1.1.3.26.1 martin of the <tt>nqmgr</tt> scheduler.
1790 1.1 tron
1791 1.1 tron </p>
1792 1.1 tron
1793 1.1 tron <p>
1794 1.1 tron
1795 1.1 tron [By now you should theoretically know the <tt>nqmgr</tt> scheduler
1796 1.1 tron inside out. In practice, you still hope that you will never have
1797 1.1 tron to really understand the last or last two chapters completely, and
1798 1.1 tron fortunately most people really won't. Understanding how the scheduler
1799 1.1.1.3.26.1 martin works in ideal conditions is more than good enough for the vast majority
1800 1.1 tron of users.]
1801 1.1 tron
1802 1.1 tron </p>
1803 1.1 tron
1804 1.1 tron <h2> <a name="credits"> Credits </a> </h2>
1805 1.1 tron
1806 1.1 tron <ul>
1807 1.1 tron
1808 1.1 tron <li> Wietse Venema designed and implemented the initial queue manager
1809 1.1 tron with per-domain FIFO scheduling, and per-delivery +/-1 concurrency
1810 1.1 tron feedback.
1811 1.1 tron
1812 1.1 tron <li> Patrik Rak designed and implemented preemption where mail with
1813 1.1 tron fewer recipients can slip past mail with more recipients in a
1814 1.1 tron controlled manner, and wrote up its documentation.
1815 1.1 tron
1816 1.1 tron <li> Wietse Venema initiated a discussion with Patrik Rak and Victor
1817 1.1 tron Duchovni on alternatives for the +/-1 feedback scheduler's aggressive
1818 1.1 tron behavior. This is when K/N feedback was reviewed (N = concurrency).
1819 1.1 tron The discussion ended without a good solution for both negative
1820 1.1 tron feedback and dead site detection.
1821 1.1 tron
1822 1.1 tron <li> Victor Duchovni resumed work on concurrency feedback in the
1823 1.1 tron context of concurrency-limited servers.
1824 1.1 tron
1825 1.1 tron <li> Wietse Venema then re-designed the concurrency scheduler in
1826 1.1 tron terms of the simplest possible concepts: less-than-1 concurrency
1827 1.1 tron feedback per delivery, forward and reverse concurrency feedback
1828 1.1 tron hysteresis, and pseudo-cohort failure. At this same time, concurrency
1829 1.1 tron feedback was separated from dead site detection.
1830 1.1 tron
1831 1.1 tron <li> These simplifications, and their modular implementation, helped
1832 1.1 tron to develop further insights into the different roles that positive
1833 1.1 tron and negative concurrency feedback play, and helped to identify some
1834 1.1 tron worst-case scenarios.
1835 1.1 tron
1836 1.1 tron </ul>
1837 1.1 tron
1838 1.1 tron </body>
1839 1.1 tron
1840 1.1 tron </html>
1841