Sending Messages at Scale - Cannot allocate more handles

September 25, 2022

When sending many messages (thousands or tens of thousands) you may find that you run into errors that you may not see while testing with lower numbers of messages. One such error that I’ve encountered recently is this:

Azure.Messaging.ServiceBus.ServiceBusException: ‘Cannot allocate more handles. The maximum number of handles is 4999. (QuotaExceeded)’

Some code to replicate the issue:



for (int i = 1; i <= 10000; i++)
{
    var sender = serviceBusClient.CreateSender("topic-name");

    string messageText = $"Test{i}--{DateTime.Now.ToString("yy-MM-dd")}";
    var msg = new ServiceBusMessage(Encoding.UTF8.GetBytes(messageText));
    await sender.SendMessageAsync(msg);
}    

There is a limit on concurrent connections to the service bus, set at 4,999 at the time of writing; that is, you cannot have 5,000 connections at the same time. Every time a call to CreateSender is called, a new connection is made to the service bus. Because this is a tight loop, the connections cannot be cleaned up in time, and so it overwhelms the service.

There are three mechanisms for avoiding this. By far the best and easiest is to send the messages as a batch.

Sending Messages as a Batch



List<ServiceBusMessage> serviceBusMessages = new();    
for (int i = 1; i <= 10000; i++)
{
    string messageText = $"Test{i}--{DateTime.Now.ToString("yy-MM-dd")}";
    serviceBusMessages.Add(
       new ServiceBusMessage(Encoding.UTF8.GetBytes(messageText)));        
}
await sender.SendMessagesAsync(serviceBusMessages);

This is far faster, and avoids 10,000 calls to the service. The caveat here is the size of the message - if the 10,000 messages exceed a single message size, then it may be necessary to batch the messages into groups of 1000, or even 100 - depending on the size of each message.

Cleaning up the Client

This mechanism is to forcibly dispose of the message factory each loop:



for (int i = 1; i <= count; i++)
{
    var sender = serviceBusClient.CreateSender("topic-name");

    string messageText = $"Test{i}--{DateTime.Now.ToString("yy-MM-dd")}";
    var msg = new ServiceBusMessage(Encoding.UTF8.GetBytes(messageText));
    await sender.SendMessageAsync(msg);

    await sender.CloseAsync();
}    

Change the Scope of the Message Factory

The final method is to simply move the creation of the message factory outside the loop:



    var sender = serviceBusClient.CreateSender("topic-name");
    for (int i = 1; i <= count; i++)
    {
        string messageText = $"Test{i}--{DateTime.Now.ToString("yy-MM-dd")}";
        var msg = new ServiceBusMessage(Encoding.UTF8.GetBytes(messageText));
        await sender.SendMessageAsync(msg);

    }    

Summary

If you’re dealing with large quantities of messages, then the batch send is by far the best option, as it not only avoids the issue with too many handles, but speeds up the whole process.



Profile picture

A blog about one man's journey through code… and some pictures of the Peak District
Twitter

© Paul Michaels 2024