24 July 2009

Storing objects as compressed messages in the Windows Azure Queue

For my current private R&D project I wanted to store 'task set' objects (in code samples below as shown type "Request") on the Windows Azure Queue. To prevent serialization issues I opted for XML serialization, like this:
private void Enqueue(Request tr)
{
  var queueStorage = QueueStorage.Create(
  StorageAccountInfo.GetDefaultQueueStorageAccountFromConfiguration());
  var queue = queueStorage.GetQueue("MyQueue");
  if (!queue.DoesQueueExist())
  {
    queue.CreateQueue();
  }

  var xmlSerializer = new XmlSerializer(tr.GetType());
  using (var stringWriter = new StringWriter())
  {
    xmlSerializer.Serialize(stringWriter, tr);
    queue.PutMessage(new Message(stringWriter.ToString()));
  }
}
I would let the worker role deserialize the Request object and then execute it. It annoyed me to no end to learn that the Azure Queue limits message sizes to 8192 bytes. I could have redesigned my task sets to smaller units, but that would hurt the efficiency of the process I had in mind. Based upon the StringZipExtensions class I blogged about that can serialize and deserialize any old object to and from compressed XML (which you can download here) I created the following extension methods, which enable you to store objects as a GZip compressed set of bytes on the Azure queue and retrieve them again:
using LocalJoost.Utilities.Compression;
using Microsoft.Samples.ServiceHosting.StorageClient;

namespace LocalJoost.Utilities.Azure
{
  public static class MessageQueueExtensions
  {
    /// <summary>
    /// Decompresses the specified queue message.
    /// </summary>
    /// <param name="message">The message.</param>
    /// <returns></returns>
    public static string Decompress(this Message message)
    {
      return message != null ?
       message.ContentAsBytes().DecompressToString() : null;
    }

    /// <summary>
    /// Decompresses the specified queue message.
    /// to an object
    /// </summary>
    /// <param name="message">The message.</param>
    /// <returns></returns>
    public static T Decompress <T>(this Message message) where T:class
    {
      return message != null ?
       message.ContentAsBytes().DecompressToObject<T>() : null;
    }
  }
}
Adhering to my first code sample, you can now simply put an object to the queue like this
private void Enqueue(Request tr)
{
  var queueStorage = QueueStorage.Create(
    StorageAccountInfo.GetDefaultQueueStorageAccountFromConfiguration());
  var queue = queueStorage.GetQueue("MyQueue");
  if (!queue.DoesQueueExist())
  {
    queue.CreateQueue();
  }

  queue.PutCompressedObject(tr);
}
and let the worker role use the Decompress extension method on the Message itself:
var queueStorage =
  QueueStorage.Create(
  StorageAccountInfo.GetDefaultQueueStorageAccountFromConfiguration());
var queue = queueStorage.GetQueue("MyQueue");
if (queue.DoesQueueExist())
{
  var message = queue.GetMessage(600);
  if (message != null)
  {
    var request = message.Decompress<Request>();
    request.Execute();
    queue.DeleteMessage(message);
  }
}
And there you go. It is as simple as that. Although the Window Azure Queue message queue size still is limited to 8192, the amount of data that fits in that space is increased dramatically.

No comments: