Monthly Archives: July 2017

Send messages in batch without exceeding a limit

Service Bus allows user to send messages in batches, which is great what it comes to performance. Differences sending messages in batches and separately can be huge. Actually, lets look at an example. This is a very simple send:

private static void SimpleSendBatch()
{
    var client = GetQueueClient();
    client.SendBatch(GetALotOfMessages());
}

If we compare it to sending messages sequential, we get:

batch_sending_times

So sending 200 messages sequential can take up to 20 seconds, but that will differ depending on a cpu, internet connection, etc. However, from chart above you get the point – when we send one message – use Send, when sending more then one – use SendBatch.

When collection of messages is too big

However when you attempt to send a rather huge batch, you might exceed a limit, that is 256KB per batch, not only per message itself. If you exceed this limit, you can get an error like this:

A request from the client instance has exceeded the maximum message size, and the underlying channel will be recreated. Validate the content size before retrying.

So what can we check to chop messages into smaller chunks? There is a Size property in the BrokeredMessage object, but that relates only to content and there are still standard and custom properties. If we don’t use it that much, we can implement a simple solution of checking batch size:

private static void SimpleAndSmartSendBatch()
{
    var client = GetQueueClient();
    var messages = GetALotOfMessages();

    const int maxBatchSizeInBytes = 230000;
    var i = 0;
    long currentBatchSize = 0;
    var listToSend = new List<BrokeredMessage>();
    while (i < messages.Count)
    {
        if (currentBatchSize + messages[i].Size < maxBatchSizeInBytes)
        {
            listToSend.Add(messages[i]);
            currentBatchSize += messages[i].Size;
        }
        else
        {
            client.SendBatch(listToSend);
            listToSend.Clear();
            listToSend.Add(messages[i]);
            currentBatchSize = messages[i].Size;
        }

        i++;
    }

    if (listToSend.Any())
    {
        client.SendBatch(listToSend);
    }
}

In the code above I set 230000 bytes as a limit instead of 256000. This is just a small margin that can prevent errors in the future. That solution should be sufficient for most of the scenarios.

The best way to calculate message size

First of all – there is no way to know for sure how big is the message. However, if you really want to use sending batch to maximum, you need to calculate message size more precisely.

While investigating the topic I came across a github discussion and a post from Sean Feldman – explains it very good and there’s code as well:

https://weblogs.asp.net/sfeldman/asb-batching-brokered-messages

Based on that solution I created my own that uses BrokeredMessage.

public static long GetEstimatedMessageSize(BrokeredMessage message)
{
    var standardPropertiesSize =
        GetStringSizeInBytes(message.MessageId) + // MessageId
        GetStringSizeInBytes(message.ContentType) + // ContentType
        GetStringSizeInBytes(message.CorrelationId) + // CorrelationId
        4 + // DeliveryCount
        8 + // EnqueuedSequenceNumber
        8 + // EnqueuedTimeUtc
        8 + // ExpiresAtUtc
        1 + // ForcePersistence
        1 + // IsBodyConsumed
        GetStringSizeInBytes(message.Label) + // Label
        8 + // LockedUntilUtc
        16 + // LockToken
        GetStringSizeInBytes(message.PartitionKey) + // PartitionKey
        8 + // ScheduledEnqueueTimeUtc
        8 + // SequenceNumber
        GetStringSizeInBytes(message.SessionId) + // SessionId
        4 + // State
        8 + // TimeToLive
        GetStringSizeInBytes(message.To) + // To
        GetStringSizeInBytes(message.ViaPartitionKey);  // ViaPartitionKey;

    var customPropertiesSize = message.Properties.Sum(p => GetStringSizeInBytes(p.Key) + GetObjectSize(p.Value));

    return message.Size + customPropertiesSize + standardPropertiesSize;
}

private static int GetStringSizeInBytes(string value) => value != null ? Encoding.UTF8.GetByteCount(value) : 0;

private static long GetObjectSize(object o)
{
    using (Stream s = new MemoryStream())
    {
        var formatter = new BinaryFormatter();
        formatter.Serialize(s, o);
        return s.Length;
    }
}

We can calculate how big are standard properties and custom properties, but there are still properties inside of BrokeredMessage like dates and timespans. To be safe it’s better to keep 5-10% of margin.