Compressing a Service Bus Message Using Gzip

September 10, 2022

I’ve recently been playing around at the edges of Azure Service Bus. One of the things that occurred to me was that, for a large message, you could improve speed and save money by simply compressing the message before sending.

The obvious downside to this approach is that your consumer needs to be aware that the message is compressed. The other issue is that there is an overhead to compression and decompression, both in size and in speed, so compressing a small message is pointless (or worse - it may actually increase the size and time to transmit).

That’s the why and why not, let’s move onto the how.

How to Send a Compressed Message

Let’s start with a re-cap on how to send a message without it being compressed:



async Task SendMessage(string connectionString, string topicName, string messageText)
{
    await using var serviceBusClient = new ServiceBusClient(connectionString);
    var sender = serviceBusClient.CreateSender(topicName);
    
    var message = new ServiceBusMessage(Encoding.UTF8.GetBytes(messageText));

    await sender.SendMessageAsync(message);
}

Since this sends a message, which is just a stream of text, all we need to do now is compress that message. An easy way is to use GZipStream:



byte[] CompressBytes(byte[] bytes)
{    
    using var memoryStream = new MemoryStream();
    using var gzipStream = new GZipStream(memoryStream, CompressionLevel.SmallestSize);
    gzipStream.Write(bytes, 0, bytes.Length);
    gzipStream.Close();

    return memoryStream.ToArray();
}

This method takes an array of bytes, and runs them through the GZipStream to apply the Gzip compression algorithm on it. All we now need to do is to convert our string to a byte array - we can use something like the following, which also converts the resulting string to Base64 - meaning we can transport it easier:



string CompressMessage(string toCompress)
{
    var bytes = Encoding.UTF8.GetBytes(toCompress);

    var returnBytes = CompressBytes(bytes);

    string returnValue = Convert.ToBase64String(returnBytes);
    return returnValue;
}

We now have all the building blocks, we can simply assemble them in the correct order:



var compressed = CompressMessage(largeMessage);
await SendMessage(connectionString, "compressed-message", compressed);

All that’s left is to receive the message.

How to Receive a Compressed Message

Again, let’s start with a basic method to receive the next message on the service bus:



async Task<string> ConsumeNextMessage(string topic)
{
    await using var serviceBusClient = new ServiceBusClient(connectionString);
    var receiver = serviceBusClient.CreateReceiver(topic, "sub-1");
    var message = await receiver.ReceiveMessageAsync();
    return message.Body.ToString();
}

This will simply read the next message for the given subscription and return its contents. As with the send, we simply need the other building blocks; we’ll start with decompressing the bytes:



byte[] DecompressBytes(byte[] bytes)
{    
    using var memoryStream = new MemoryStream(bytes);
    using var outputStream = new MemoryStream();
    using var decompressStream = new GZipStream(memoryStream, CompressionMode.Decompress);
    decompressStream.CopyTo(outputStream);
    var returnBytes = outputStream.ToArray();
    return returnBytes;
}

Again, we’re using GZipStream to return a decompressed version of the data. We’ll call that in the same manner as above:



string DecompressMessage(string compressed)
{
    var bytes = Convert.FromBase64String(compressed);
    
    var returnBytes = DecompressBytes(bytes);
    return Encoding.UTF8.GetString(returnBytes);
}

It’s worth remembering that we’re transporting the compressed data using Base64, so we need to convert it from that first. Finally, we can assemble the methods:



var message = await ConsumeNextMessage("compressed-message");
var decompressed = DecompressMessage(message);

What difference does it make?

Is it really worth all this message around? The answer is probably an emphatic no. However, in a very small subset of cases, this might mean that you can continue on the standard tier, rather than upgrade to premium.

This also helps with speed - sending a 70MB message takes around 50 - 55 seconds in my tests. The same message compressed was around 5MB and took only 5 seconds to send.

References

https://docs.microsoft.com/en-us/dotnet/api/system.io.compression.gzipstream?view=net-6.0

https://www.infoworld.com/article/3660629/how-to-compress-and-decompress-strings-in-c-sharp.html



Profile picture

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

© Paul Michaels 2024