Map of Strict Ordered Queues
The problem
There is a real-life requirement that sometimes appears on systems using queues to deliver or receive asynchronous notifications: a certain local ordering is needed, so certain notifications must be delivered strictly after others, all of this while respecting asynchronicity on the larger scale
A good example of that would be delivering state-change notifications on entities such as phone calls: one would rather keep strict order on events within a single call, so you would not receive, for example, a 'caller-pushed-#7' event after receiving 'call-ended'. Also, any block of delay enforced by the ordering on call A should not impact, delay or block call B or any other call
Possible solutons
This is a complex problem, since it is generally difficult - if not impossible - to ascertain whether you should wait longer for delayed events yet to appear. The general approach is usually a combination of any of these:
- retrofit delayed events arriving out of order, thus modifying state post hoc
- use strict sequence counters when the events are produced, so spotting a hole becomes easy
- add the needed wait-and-reorder of events at the consumer end; this usually involves setting a set of strict-order queues, one per emitting entity
None of those approaches is perfect, every each of them has its own problems:
- #1 : It may not be possible to go back in time and anyway it'll imply keeping state or history of such past
- #2 : if the events can come from more than one source it may be next to impossible to produce a common, strict monotonical sequence counter
- #3: this is usually just another way to reformulate #1, by holding all the state received until it can be assumed it can be emitted and used without risk for post hoc modifications later on
So, assuming we're dealing with a problem with no practical an complete solution, we might as well try to produce a partial solution embedded in the queuing middleware
A partial solution in Keuss: Map Of Queues
The implementation comes in the form of a queue backend named intraorder
, backed by mongodb
and works as follows:
- All items in a queue will provide a grouping identifier, which is expected at the field
iid
- When an item is pushed in a queue where other items already exist with the same
iid
, it is guaranteed that this item will not be served until the already-existing items are gone (popped, committed or moved to deadletter) - The former is true also in the presence of retries and delays: if an item is rolled back, it will block
any other item with the same
iid
that happened to be pushed after - Also, delays on insertion of elements are honored (as they are on retries), but they will also block further elements
with the same
iid
until the element is removed from the queue
This provides a partial solution to the problem, in the sense that a strict ordering within same iid
is imposed for elements not extracted from the queue
Caveats
- It is not possible to remove elements by id in a queue: items can only be removed by means of commit, pop or move-to-deadletter
- An external, constant cleanup process is needed to remove exhausted artifacts from the mongodb collection modelling the queue. A complete cleanup cannot be performed at the pop/commit without risking loss of items