2

I need to read and process messages from an Azure Service Bus Queue by an "Azure function". The messages should be handled in the right order so I need to avoid concurrent calls.

I use an Azure Function service bus trigger for this (it's the only subscriber to the queue). According to the documentation I configured the "servicebus/maxConcurrentCalls" (in host.json) setting to 1. On top of this I decorated the function with the "Singleton" attribute. Besides all this the messages seem to be treated in a random order by different threads. What do I miss here? Or did I misunderstand something?

Documentation I used: https://github.com/Azure/azure-webjobs-sdk/wiki/Singleton

host.json:

{
  "serviceBus": {
    "maxConcurrentCalls": 1
  }
}

Azure Function:

using System;
using System.Threading.Tasks;
using Microsoft.ServiceBus.Messaging;

[Singleton]
public static void Run(BrokeredMessage myQueueItem, TraceWriter log)
{
    Stream stream = myQueueItem.GetBody<Stream>();
    StreamReader reader = new StreamReader(stream);
    string messageContentStr = reader.ReadToEnd();

    log.Info($"New TEST message: {messageContentStr} on thread {System.Threading.Thread.CurrentThread.ManagedThreadId}");   

    System.Threading.Thread.Sleep(2000);     
}

Here is an excerpt of the logging. As you can see there are different threads. And, for example, "Message 19" comes before "Message 10". And yes, I'm sure I put the messages in the right order in the queue.

....
2018-05-09T09:09:33.686 [Info] New TEST message: Message 19 on thread 33
2018-05-09T09:09:35.702 [Info] Function completed (Success, Id=007eccd0-b5db-466a-91c1-4f53ec5a7b3a, Duration=2013ms)
2018-05-09T09:09:36.390 [Info] Function started (Id=b7160487-d10d-47a6-bab3-78da68a93498)
2018-05-09T09:09:36.420 [Info] New TEST message: Message 10 on thread 39
...
Reno
  • 542
  • 3
  • 13
  • Have you tried configuring your Queue to `Enforce Message Ordering`? I use this tool to configure service bus: [Service Bus Explorer](https://github.com/paolosalvatori/ServiceBusExplorer) – Mike May 09 '18 at 09:56
  • "Enforce Message Ordering" is only available for Session-based messaging which is not an option in my case. In fact, I'm not too worried about the order but more about the concurrent calls. Thanks for sharing this tool, btw. – Reno May 09 '18 at 11:05
  • Could you check if you had one or multiple instances running those concurrent requests? Accept ExecutionContext and log Instance ID. – Mikhail Shilkov May 09 '18 at 11:13
  • As per this thread the singleton attribute wont work: https://github.com/Azure/azure-functions-host/issues/912. It seems that the only option is to go with a dedicated app service plan, dont know if it is an option for you ? – Thomas May 09 '18 at 11:13
  • @Mikhail InvocationId of ExecutionContext is unique per call. Is that what you mean? InvocationId is the only ID that I can find on ExecutionContext – Reno May 09 '18 at 12:04
  • Sorry, not from context, you can get machine id from Environment variable – Mikhail Shilkov May 09 '18 at 12:08
  • @Mikhail I did a new test with 20 messages and I can confirm that they all came in the same machine. Got the machine name from Environment.MachineName. Thomas, I guess that a dedicated compute service plan won't help neither in this case? – Reno May 09 '18 at 12:13
  • 2
    Is your Service Bus Queue partitioned or not? If it's partitioned, you will not be able to guarantee ordering. – Rob Reagan May 09 '18 at 12:51
  • @RobReagan I think you hit the nail on the head. I indeed enabled partitioning; however "without partition key" to enhance the availability like described here: https://azure.microsoft.com/en-us/blog/partitioned-service-bus-queues-and-topics/ But it looks like this partitioning makes the ordering uncertain. Just did a test with an un-partitioned queue and it seems to enter in the right order, now. However I still see different thread ID's per message... but I guess that the operations are synchronized with my config? I still need to clarify this. – Reno May 09 '18 at 13:46
  • Order w/o Sessions is not guaranteed. At some point in time you _will_ get messages out of order. Functions don't support sessions as far as I know. LogicApps does support sessions. Perhaps Function is not the right tool for this job. – Sean Feldman May 09 '18 at 15:30
  • @SeanFeldman thank you for pointing this out. However, in my case I can live with the uncertain order. I just want to be sure that the messages will be treated one after each other synchronously. Looks like I'm fine with an unpartitioned queue and the maxConcurrentCalls set to 1. – Reno May 09 '18 at 15:46

1 Answers1

5

Take a look and make sure that your Service Bus Queue is NOT partitioned. If it is partitioned, you have multiple message brokers servicing requests, and the order of messaging is not guaranteed. You can read more about it here: https://learn.microsoft.com/en-us/azure/service-bus-messaging/service-bus-partitioning#not-using-a-partition-key

Specifically:

In the absence of a partition key, Service Bus distributes messages in a round-robin fashion to all the fragments of the partitioned queue or topic. If the chosen fragment is not available, Service Bus assigns the message to a different fragment. This way, the send operation succeeds despite the temporary unavailability of a messaging store. However, you will not achieve the guaranteed ordering that a partition key provides.

Rob Reagan
  • 7,313
  • 3
  • 20
  • 49
  • 2
    For the sake of completeness. after some testing I can say that: 1/ setting the "servicebus/maxConcurrentCalls" to 1 in the "host.json" does make the function treat them one after each other synchronously. Otherwise a message still can finish before the previous one 2/ The [Singleton] attribute has no influence on these service bus trigger functions. – Reno May 09 '18 at 14:29
  • 1
    W/o limiting Function to the concurrency of 1, ordered delivery can be only done with [Message Sessions](https://learn.microsoft.com/en-us/azure/service-bus-messaging/message-sessions). Using non-partitioned queue on a Standard tier has a significant downside - availability suffers. With message sessions, a queue can be partitioned or non-partitioned, it won't matter. – Sean Feldman May 31 '19 at 16:17