Friday, January 9, 2009

Message Selectors and ActiveMQ

Having just completed a consultancy on FUSE Message Broker (Enterprise Apache ActiveMQ), I just thought I'd write some notes about the use of message selectors in JMS, and, in particular, how FUSE Message Broker implements them.

Message selectors are easy to use, allowing a consumer to specify a filter, using an SQL-like syntax, describing the messages of interest. This filter, example "customerStatus = 'Gold'" instructs the broker to only send messages to the consumer that have a customerStatus header equal to "Gold".

While message selectors are attractive and conceptually very nice, they do place a burden on the broker: consider the case where you have a large number of consumers, say n, on a queue: for each message the broker must evaluate between 1 and n message selectors before delivering the message. In the case of topic, all n message selectors will have to be evaluated for each message. Ouch: that's a lot of work to be putting on the broker, particularly if you're looking for high-throughput.

But apart from this, ActiveMQ's memory paging architecture has an optimisation that can flummox you when using message selectors. When messages are sent persistently, ActiveMQ will only page a portion of the messages in memory, but leave the rest on disk. This is very reasonable: however, one side effect of this is that selectors are only evaluated on those messages in memory, not those in the store. So, if your page size is 100, and you have 100 messages for which there is no current matching consumer, you end up with a "hung" queue. For example, consider the case where you have two consumers, selecting on "color = 'red'" and "color = 'blue'" respectively - here, "color" is a custom JMS message header. Let's say that the first (red) consumer is currently not in operation, and we receive 100 messages for red. Blue is waiting, waiting, waiting, but does not get any messages as there are no matching ones present. Now, we get message 101 - a nice blue message. However, our selectors are only matching against the first 100 messages (all red!) so blue still hangs, waiting, despite the fact that there's a perfectly matching message.

That example is perhaps contrived, but it does demonstrate a potential "hanging consumer" problem. For me though it's a natural side-effect of wanting to limit the broker-side overhead of having to evaluate message selectors. You can of course modify the page size to reduce the chance of the problem happening, but you're just reducing the probability of the lock happening.

My customer had come up with a conceptually elegant callback design, whereby as messages are forwarded through a server, a consumer is created (for each message) to listen on a queue for a subsequent response, using a unique message identifier within a message selector. While conceptually nice as a design, the overhead (in terms of dynamic consumer creation and message selector processing) just isn't worth it. We proposed an alternative architecture using named queues and a fixed pool of response consumers that's going to work way, way better.


James Strachan said...

BTW have you seen the FAQ entry on implement request-response efficiently?

Ade said...

Nice one James! The FAQ dovetails nicely with my experience: however, in the FAQ article it talks about creating the temporary reply consumers on startup. In the architecture under investigation the pattern was to create a new consumer for each incoming call at runtime, and use a message selector on a shared response queue to ensure the message gets sent to the client. This puts a lot of unnecessary load on the broker and means lots of blocking calls on the broker :(

Scott said...

The fix AMQ-1112 resolves this issue. I reproduced the bug then applied the AMQ-1112 fix and verified it does work.