Problem: nqmgr's fairness breaks down for single-recipient bulk mail 

Solution: schedule a delivery request depending on how many other
pending requests that sender already has in the scheduler's in-memory
queues. This metric does not depend on whether bulk mail is sent as
multi-recipient or single-recipient mail.

Alternative 1: modify nqmgr's job/peer grouping

Nqmgr ensures a form of fairness, by interleaving multi-recipient
mail deliveries with single-recipient mail. This relies on a clever
way of grouping things in jobs and peers, but fails to enforce
fairness down when bulk mail is sent as single-recipient mail.

To achieve fairness regardless of whether bulk mail is sent as
single-recipent mail, one would adapt nqmgr's scheduler, such that
the job and peer structures are populated based on the number of
pending delivery requests from that sender. This would be a non-trivial
change to a non-trivial scheduler.

Alternative 2: multiple mostly independent scheduling classes

This alternative blows new life into the old queue manager.

The idea is to introduce three classes: one class for small senders
(senders that have a small number of delivery requests in the
scheduler's in-memory queues), one class for medium senders, and
one class for large senders.

In the updated scheduler, each (transport, per-destination FIFO queue)
becomes (transport, class, per-destination FIFO queue). Thus, there
can be three same-destination queues under the same transport,
instead of just one. And everything else just follows from that.

As usual, the scheduler reads envelope information from a queue
file, and creates a delivery request with a limited number of
recipients that have the same next-hop destination domain.

The updated scheduler then computes the class for the delivery
request, based on the number of delivery requests that that sender
already has in the scheduler's in-memory queues (something like
int(10log(n)) would do).

The scheduler then assigns the delivery request to the FIFO queue
for that transport, class, and destination.

During delivery, the scheduler round-robin selects a transport,
round-robin selects a non-empty class, round-robin selects a
destination queue within that class, and then selects the first
delivery request within that FIFO queue.

Additionally, we arrange that same-destination queues under a
transport will have shared info for concurrency, throttling, and
rate delay.

The implementation effort is limited: during delivery request
creation, also compute the class; during delivery, an additional
round-robin selection stage to select a non-empty class; and sharing
the concurrency and rate-delay info among same-destination queues
under the same transport. We keep all the logic that old queue
manager already has; data-wise, we just insert one extra level of
indirection.

Main benefits: within a class, all deliveries to the same destination
are still made in FIFO order; there is no starvation as each class
is guaranteed at least 1/3 of delivery resources, or all resources
if the other classes are empty; and finally, the scheduler remains
brain-dead simple: going from one to three FIFO queues is not a
fundamental change.

Other benefits: we can choose the class based on other properties,
for example message size, or both size and number of pending delivery
requests per sender, or something else. It seems unlikely that we
will need more than three classes: small, medium, large.

        Wietse

Reply via email to