Search code examples
msmqmessage-queuedistributed-transactionsdenormalization

Looking for message bus implementations that offer something between full ACID and nothing


Anyone know of a message bus implementation which offers granular control over consistency guarantees? Full ACID is too slow and no ACID is too wrong.

We're currently using Rhino ESB wrapping MSMQ for our messaging. When using durable, transactional messaging with distributed transactions, MSMQ can block the commit for considerable time while it waits on I/O completion.

Our messages fall into two general categories: business logic and denormalisation. The latter account for a significant percentage of message bus traffic.

Business logic messages require the guarantees of full ACID and MSMQ has proven quite adequate for this.

Denormalisation messages:

  1. MUST be durable.
  2. MUST NOT be processed until after the originating transaction completes.
  3. MAY be processed multiple times.
  4. MAY be processed even if the originating transaction rolls back, as long as 2) is adhered to.

(In some specific cases the durability requirements could probably be relaxed, but identifying and handling those cases as exceptions to the rule adds complexity.)

All denormalisation messages are handled in-process so there is no need for IPC.

If the process is restarted, all transactions may be assumed to have completed (committed or rolled back) and all denormalisation messages not yet processed must be recovered. It is acceptable to replay denormalisation messages which were already processed.

As far as I can tell, messaging systems which deal with transactions tend to offer a choice between full ACID or nothing, and ACID carries a performance penalty. We're seeing calls to TransactionScope#Commit() taking as long as a few hundred milliseconds in some cases depending on the number of messages sent.

Using a non-transactional message queue causes messages to be processed before their originating transaction completes, resulting in consistency problems.

Another part of our system which has similar consistency requirements but lower complexity is already using a custom implementation of something akin to a transaction log, and generalising that for this use case is certainly an option, but I'd rather not implement a low-latency, concurrent, durable, transactional messaging system myself if I don't have to :P

In case anyone's wondering, the reason for requiring durability of denormalisation messages is that detecting desyncs and fixing desyncs can be extremely difficult and extremely expensive respectively. People do notice when something's slightly wrong and a page refresh doesn't fix it, so ignoring desyncs isn't an option.


Solution

  • It turns out that MSMQ+SQL+DTC don't even offer the consistency guarantees we need. We previously encountered a problem where messages were being processed before the distributed transaction which queued them had been committed to the database, resulting in out-of-date reads. This is a side-effect of using ReadCommitted isolation to consume the queue, since:

    1. Start transaction A.
    2. Update database table in A.
    3. Queue message in A.
    4. Request commit of A.
    5. Message queue commits A
    6. Start transaction B.
    7. Read message in B.
    8. Read database table in B, using ReadCommitted <- gets pre-A data.
    9. Database commits A.

    Our requirement is that B's read of the table block on A's commit, which requires Serializable transactions, which carries a performance penalty.

    It looks like the normal thing to do is indeed to implement the necessary constraints and guarantees oneself, even though it sounds like reinventing the wheel.

    Anyone got any comments on this?