Everything you need to know about Windows Azure queue storage to build disconnected and reliable systems

In my attempt to cover most of the features of the Microsoft Cloud Computing  platform Windows Azure, I’ve been recently covering Windows Azure blob and table storage. Today I’m reviewing the Windows Azure queue storage, which is often used to build disconnected and reliable systems:

Windows Azure Service Bus messaging with Publish/ Subscribe pattern with topic and subscription

The information about Windows Azure Blob storage:
Everything you need to know about Windows Azure Blob Storage including permissions, signatures, concurrency

The information about Windows Azure Table storage:
Everything you need to know about Windows Azure Table Storage to use a scalable non-relational structured data store 

Why using Windows Azure storage:

  • Fault-tolerance: Windows Azure Blobs, Tables and Queues stored on Windows Azure are replicated three times in the same data center for resiliency against hardware failure. No matter which storage service you use, your data will be replicated across different fault domains to increase availability
  • Geo-replication: Windows Azure Blobs and Tables are also geo-replicated between two data centers 100s of miles apart from each other on the same continent, to provide additional data durability in the case of a major disaster, at no additional cost.
  • REST and availability: In addition to using Storage services for your applications running on Windows Azure, your data is accessible from virtually anywhere, anytime.
  • Content Delivery Network: With one-click, the Windows Azure CDN (Content Delivery Network) dramatically boosts performance by automatically caching content near your customers or users.
  • Price: It’s insanely cheap storage

The only reason you would not be interested in the Windows Azure storage platform would be if you’re called Chuck Norris …
Now if you are still reading this line it means you aren’t Chuck Norris, so let’s get on with it.

Windows Azure Queue storage is a service for storing large numbers of messages that can be accessed from anywhere in the world via authenticated calls using HTTP or HTTPS. A single queue message can be up to 64KB in size, a queue can contain millions of messages, up to the 100TB total capacity limit of a storage account.

The concept behind the Windows Azure queue storage is as following:

Windows Azure Queues to build disconnected and reliable systems

There are 3 things you need to know about to use Windows Azure Queue storage:

  1. Account: All access to Windows Azure Storage is done through a storage account. The total size of blob, table, and queue contents in a storage account cannot exceed 100TB.
  2. Queue: A queue contains a set of messages. All messages must be in a queue.
  3. Message: A message, in any format, of up to 64KB max. You can serialize information into the message

1. Creating and using the Windows Azure Storage Account

To be able to store data in the Windows Azure platform, you will need a storage account. To create a storage account, log in the Windows Azure portal with your subscription and go to the Hosted Services, Storage Accounts & CDN service:

Storing data with Windows Azure Blob Storage with permissions and metadata

Select the Storage Accounts service and hit the Create button to create a new storage account:

Storing data with Windows Azure Blob Storage with permissions and metadata

Define a prefix for your storage account you want to create:

Storing data with Windows Azure Blob Storage with permissions and metadata

After the Windows Azure storage account is created, you can view the storage account properties by selecting the storage account:

Using Windows Azure queues to build disconnected and reliable systems

The storage account can be used to store data in the blob storage, table storage or queue storage. In this post, we will only cover queue storage. One of the properties of the storage account is the primary and secondary access key. You will need one of these 2 keys to be able to execute operations on the storage account. Both the keys are valid and can be used as an access key.

When you have an active Windows Azure storage account in your subscription, you’ll have a few possible operations:

Storing data with Windows Azure Blob Storage with permissions and metadata

  • Delete Storage: Delete the storage account, including all the related data to the storage account
  • View Access Keys: Shows the primary and secondary access key
  • Regenerate Access Keys: Allows you to regenerate one or both of your access keys. If one of your access keys is compromised, you can regenerate it to revoke access for the compromised access key
  • Add Domain: Map a custom DNS name to the storage account blob storage. For example map the robbincremers.blob.core.windows.net to static.robbincremers.me domain. Can be interesting for storage accounts which directly expose data to customers through the web. The mapping is only available for blob storage, since only blob storage can be publicly exposed.

Now that we created our Windows Azure storage account, we can start by getting a reference to our storage account in our code. To do so, you will need to work with the CloudStorageAccount, which belongs to Microsoft.WindowsAzure namespace:
Windows Azure Blob Storage Container with permissions and metadata

We create a CloudStorageAccount by parsing a connection string. The connection string takes the account name and key, which you can find in the Windows Azure portal. You can also create a CloudStorageAccount by passing the values as parameters instead of a connection string, which could be preferable. You need to create an instance of the StorageCredentialsAccountAndKey and pass it into the CloudStorageAccount constructor:

Storing data with Windows Azure Blob Storage with permissions and metadata

The boolean that the CloudStorageAccount takes is to define whether you want to use HTTPS or not. In our case we chose to use HTTPS for our operations on the storage account. The storage account only has a few operations, like exposing the storage endpoints, the storage account credentials and the storage specific clients:

Windows Azure Table Storage, a scalable NoSQL data store with OData support

The storage account exposes the endpoint of the blob, queue and table storage. It also exposes the storage credentials by the Credentials operation. Finally it also exposes 4 important operations:

  • CreateCloudBlobClient: Creates a client to work on the blob storage
  • CreateCloudDrive:  Creates a client to work on the drive storage
  • CreateCloudQueueClient:  Creates a client to work on the queue storage
  • CreateCloudTableClient:  Creates a client to work on the table storage

You won’t be using the CloudStorageAccount much, except for creating the service client for a specific storage type.

2. Basic operations for managing Windows Azure queues

There are 2 levels to be working with the windows azure queue storage, which is the queue and the messages being passed on to the queue.

To manage the windows azure queues, you need to create an instance of the CloudQueueClient through the CreateCloudQueueClient operation on the CloudStorageAccount:

Using Windows Azure queues to build disconnected and reliable systems

There are not many interesting operations exposed on the CloudQueueClient, except for these:

  • GetQueueReference: Get a CloudQueue instance through the name or the absolute uri of the queue
  • ListQueues: List all queues present in the storage account or list all queues present that match a defined prefix

We use the GetQueueReference operation on the CloudQueueClient to get a CloudQueue reference returned. The CloudQueue exposes all the operations we need to manage our Windows Azure queues and the queue messages we will be working with:

Using Windows Azure queue storage to build disconnected and reliable systems

The CloudQueue exposes a few operations to manage the queue:

  • Clear: Clear all messages from the queue
  • Create: Create a Windows Azure storage queue. If the queue already exists, an exception will be thrown
  • CreateIfNotExists: Create a Windows Azure storage queue if it does not exists yet. This does not throw any exception if the queue already exists
  • Delete: Delete the queue from the storage account
  • Exists: Check whether a queue exists in the storage account
  • SetMetadata: Set the queue metadata. The queue metadata can be specified through the Metadata property
  • FetchAttributes: Load the attributes and metadata to the CloudQueue

Creating a queue and setting metadata for the queue looks like this:

Using Windows Azure queues to build disconnected and reliable systems

We get the CloudQueue through the GetQueueReference operation, which gets a CloudQueue instance for the queue with the name “orders”. Even if the queue does not exist, this is the default way of working with Windows Azure storage. It will create an object with the necessary information, which allows you to execute operations on. Since the CloudQueue object contains the needed information about the queue, it’s able to build the REST requests that are executed on the Windows Azure storage. We start by checking whether the queue exists and if it does not exist, we will create the queue through the Create or CreateIfNotExists operation.

We store a NameValueCollection in the metadata collection and we store the metadata changes to the storage service through the SetMetadata operation. Finally we fetch the attributes from storage to our local CloudQueue instance through the FetchAttributes. It’s highly advised to invoke the FetchAttributes operation before trying to retrieve attributes or metadata on the CloudQueue.

For almost every synchronous operation there is an asynchronous operation exposed. Asynchronous operations is exposed through the Begin/End operations:

Using Windows Azure queue storage to build disconnected and reliable systems

You could achieve the same by using lambda expressions:

Using Windows Azure queue storage to build disconnected and reliable systems

However we will not be covering the asynchronous operations anymore, but we’ll go by the synchronous operations throughout this post. However it is highly recommended to use the asynchronous operations to provide better a better user experience and better throughput / performance.

To explore my storage accounts, I use a free tool called Azure Storage Explorer which you can download on codeplex:
http://azurestorageexplorer.codeplex.com/

After having created the Windows Azure queue, it should be visible in the storage explorer:

Using Windows Azure queue storage to build disconnected and reliable systems

Managing messages on the queue through the CloudQueue:

  • AddMessage: Add a message to the queue
  • DeleteMessage: Delete a message from the queue
  • GetMessage: Get the next message from the queue. The message is being pulled from the queue and will be invisible for some duration for other receivers
  • GetMessages: Get a specified number of messages from the queue. The messages are being pulled from the queue in a single request and will be invisible for some duration for other receivers
  • PeekMessage: Get the next message from the queue in peek mode, meaning the message will stay visible to other receivers
  • PeekMessages: Get a specified number of messages from the queue, meaning the messages will stay visible to other receivers
  • UpdateMessage: Update the content or visibility time of a message in the queue

Adding a message to the queue is done by creating a CloudQueueMessage and adding it with the AddMessage operation. For each order that is being made, we will post the order to the orders queue. A processing service will be picking the orders off the queue and process them. Our Order class looks like this:

Using Windows Azure queue storage to build disconnected and reliable systems

Since we will be serializing the object instance to binary format, it has to be marked with the Serializable attribute. You could also serialize it to XML, but that would make the payload of the message bigger and considering the maximum message size is 64 KB, we want our payload to be as small as possible.

I also have some extension methods for the Order class to serialize it to binary:

Using Windows Azure queue storage to build disconnected and reliable systems

If you do not know what extension methods are and what they are used for, you can find information here:
Implementing and executing C# extension methods

Adding a message to the queue is done like this:

Using Windows Azure queue storage to build disconnected and reliable systems

We create a new CloudQueueMessage which either takes a binary array or a string as content. Adding the message to the Windows Azure queue is done by the AddMessage operation, which takes a few parameters in some operation overloads:

  1. Content: Take either a byte array or string as content
  2. TimeToLive: Allows you to define Timespan to define how long the image can stay on the queue before it expires. A message can only live for a maximum of 7 days on the Windows Azure queue
  3. InitialVisibilityDelay: Allows you to define how long the message will be invisible for receivers after the message has been added to the queue. If you do not pass this value, the message is immediately visible for the receivers. If you define a Timespan, the message can only be received by a receiver after the delay has expired. This is a great feature when you add a message to a queue but you want it to be processed somewhere in the future instead of immediately. You specify a visibility delay of 1 day for example, and the message will only be processed after 1 day.

After adding the message to the queue, it’ll be visible in the storage explorer:

Using Windows Azure queue storage to build disconnected and reliable systems

The message expiration time is set to 5 min from now, which I specified in the AddMessage operation.

I also added an extension method to serialize a CloudQueueMessage content back to an Order instance:

Using Windows Azure queue storage to build disconnected and reliable systems

Reading a Order message from the queue:

Using Windows Azure queue storage to build disconnected and reliable systems

We use the GetMessage operation to get a message from the queue. Retrieving the message with the GetMessage operation will pull the message from the queue and set it invisible for other receivers, so that other receivers can not retrieve the message during that time. The GetMessage has one operation overload with a visibility timeout you can pass along:

  • VisibilityTimeout: Allows you to define a Timespan to define how long the message will be invisible after you retrieved it from the queue. If you do not pass this parameter,the default visibility timeout is 30 seconds. The behavior allows you to keep the message from being processed by other receivers while you are processing it.
The default visibility timeout is 30 seconds, meaning that after you get the message through the GetMessage operation it will be invisible for 30 seconds to the other receivers. If the message has not been deleted through the DeleteMessage operation within that time span, it will reappear on the queue and the Dequeue Count will be updated:

Using Windows Azure queue storage to build disconnected and reliable systems

It will stay on the queue until some receiver receives the message or the message expires. The Dequeue Count comes into play so that you can avoid retrieving and attempting to process the message unlimited. This behavior allows you to receive a message from the queue, attempt to process it and after processing it, deleting it from the queue. If an error would occur during the processing of the message, the deletion of the message will not be invoked and the message will reappear in the queue after the visibility timeout expires, with an increased dequeue count.

Receiving a message properly with the GetMessage and DeleteMessage operations:

Using Windows Azure queue storage to build disconnected and reliable systems

PS: In forgot to check whether the cloudQueueMessage is not null before I access the DequeueCount. You might want to check that in your production code obviously.

We retrieve the message from the queue with an invisibility timeout of 5 minutes. Through the DequeueCount property on the CloudQueueMessage we check whether the dequeue count of the message is less or equal then 3. We do this to make sure we don’t keep endlessly processing the same message over and over. If we attempted to process the message 3 times and it has not been processed yet it would mean there is an issue, so we would have to notify the administrator about this message and remove the message from the queue so it does not keep getting processed.

If we processed the message, we delete the message from the queue through the DeleteMessage operation. If you do not do this, the message will reappear when the 5 minutes of the invisibility timeout are passed and the message would be processed again by some receiver.

The ToXML operation on the order is an extension method again:

Using Windows Azure queue storage to build disconnected and reliable systems

Ideally you would write this operation in a generic extension method, to be working with a type T instead of a fixed type.

I set the check of the dequeue count to less or equal to 3, so the message at least gets 3 attempts to be processed. As soon as you retrieve the message from the queue, the dequeue count will immediately be set to 1. That means if you would want to check whether a message can only be processed once, even if it would fail, you would have to check whether the dequeue count is equal to 1 and not 0.

Using the GetMessages operation is pretty much equal to the GetMessage operation. The GetMessages operation takes an additional parameter, which is an integer defining how many messages you want to retrieve in a single operation. The concepts behind processing and deleting the messages are the same. For example if you retrieve 10 messages at once with the GetMessages operation and you do not pass an invisibility timeout, the 10 messages will be retrieved and will be invisible on the queue for 30 seconds after retrieval time. This means if you do not manage to process the 10 messages within the next 30 seconds, some messages will reappear on the queue and might get processed again by another receiver, which could result in duplicating the workflow. The invisibility timeout has to be properly considered when retrieving messages from the queue.

One of the possibilities is to peek messages on the queue. When you retrieve a message from the queue with PeekMessagethe message is not dequeued and the visibility of the message remains unchanged. The DequeueCount is also not increased when retrieving a message with PeekMessage or PeekMessages. Using Peek mode to retrieve messages results that you can not delete the message, because you are basically peeking and not retrieving. The message remains available to other clients until a client retrieves the message with a call to GetMessage.

Using PeekMessage or PeekMessages could be particularly interesting if you want to look or search for a message in the queue, without changing the messages dequeue count and popreceipt values.

3. Saving storage transaction costs by using smart queue polling

By default, when polling messages from a queue you’ll be writing some code like this:

Using Windows Azure queue storage to build disconnected and reliable systems

You will write an endless loop where we queue for messages. If there is a message present, we will process it and loop again to get the next message. If there is no message present anymore, we will wait for some period of time to attempt to check the queue again for a message. You’ll be using Thead.Sleep or a Timer to build your interval to wait to query the queue again.

However one thing to keep in mind with Windows Azure storage is that you pay for each transaction to the storage. This means every transaction to the queue, to receive a message, is billed. This comes from the windows azure pricing information:

Using Windows Azure queue storage to build disconnected and reliable systems

The price for the storage transactions is cheap, but it’s pointless to pay for something that is wasted. Let’s suppose you have 3 worker roles which are processing orders from the queue. Every worker role is attempting to retrieve a message from the queue every 1 second, which is a common scenario. In 1 day that are 86.400 storage transactions for 1 worker role to the queue, even if there isn’t a single message in the queue. For 3 worker roles that ends up at 259.200 storage transactions in 1 day, while there was nothing in the queue at all. After all this is only 0,25$ for those transactions, but why waste the money and the bandwith while it’s not necessary. Please note when processing messages the transactions amount might increase since you’ll be using more operations like PeekMessage, DeleteMessage and so forth.

One of the common scenario’s to solve this is using a polling mechanism which will gradually increase every time you poll the queue and no message is found, until it reaches the maximum polling interval:

Using Windows Azure queue storage to build disconnected and reliable systems

You basically specify a maximum interval time to poll the queue. When we receive a message from the queue, the current interval polling time is set to 0 and there will be no wait used. Immediately after the message is retrieved from the queue, we will try to get the next message from the queue. If no message is retrieved, which means there are no messages on the queue, we will increase the current interval every time we poll until it’s equal to the max interval we specified. We will start by polling with a 1 second interval, then 2 seconds, then 3 seconds until we reach the maximum of 15 seconds on which it will keep polling as long there are no messages in the queue. If all day no orders would be put in the queue, the storage transactions would already be divided by 15. As soon a message is received, the interval is set to 0 again, meaning that on the next request no message is received, the next polling request will be after 1 second again and it’ll start building up again to the maximum interval.

The polling interval would look like this:

Using Windows Azure queue storage to build disconnected and reliable systems

As soon as  a message is received, it’ll start polling the queue again on a shorter interval, which will increase more over time as long no message is retrieved. You can use any interval and any interval increase you want, as long as you keep in mind that storage transactions are billed and saving money where possible is advisable. Cutting down on storage transactions with queue polling is an easy effort.

4. Handling messages that exceed the maximum 64 KB message size

At some point you might want to transfer messages over the queue that are larger then 64 KB, for example every time a user uploads an image on the website, we want to drop a message in the queue for this image, which a background worker will pick up and process the image to create a few different size thumbnails.

However adding the image binary into the message will pass the maximum message size, so it would fail. A solution to this is to store the image in blob storage and pass the blob name or blob uri in the message we sent in the queue. The receiver then has to retrieve the image from blob storage and process the image to create the thumbnails.

If you are working with structured data, like the Order class, you could easily store the order in Windows Azure table storage and pass the partition and row key in the message send over the queue. Then the background worker processing the orders would receive the order again from the table storage through the partition and row key.

If you do not know about table or blob storage, you can find all the information here:
Everything you need to know about Windows Azure Blob Storage including permissions, signatures, concurrency
Everything you need to know about Windows Azure Table Storage to use a scalable non-relational structured data store 

A simple example to upload the image to blob storage and send a queue message. Imagine if we want to handle an image of 262 KB, which is too big to send within the message on the queue:

Using Windows Azure queue storage to build disconnected and reliable systems

Using Windows Azure queue storage to build disconnected and reliable systems

We upload the image to blob storage with a specified name, which in our case is a generated guid. Then we create a new CloudQueueMessage and send it on to the queue with as content the guid of the blob image we uploaded.

The code for the background worker which processed the messages:

Using Windows Azure queue storage to build disconnected and reliable systems

We retrieve the CloudQueueMessage content, which is the blob name of the image that got uploaded. In the background worker we simply retrieve the image again from blob storage then to process it to create the thumbnails.

Executing these operations after each other:

Using Windows Azure queue storage to build disconnected and reliable systems

5. Extending the invisibility timeout of a message on the queue

A scenario that could be is that you retrieve a message from the queue with an invisibility timeout of 1 minute. After you processed the information of the message, it appears this is a message that needs to take a few extra steps to be processed, compared to the default message. This message could take up to 5 or 10 min to process instead of the 20 or 30 seconds it takes to process a normal message.

However since we retrieved this message with an invisibility timeout of 1 minute, the message will reappear in the queue after 1 minute, even though it takes a lot longer to process the message. So during processing the message, it will reappear for the other receivers, which could retrieve and start processing the message as well, which would end up in duplicate workflow and a lot of issues.

This is where the ability to extend the invisibility timeout of a message on the queue comes in, which can be done by the UpdateMessage operation on the CloudQueue object:

Using Windows Azure queue storage to build disconnected and reliable systems

The UpdateMessage operation takes 3 parameters:

  • CloudQueueMessage: The message we want to update in the queue
  • InvisibilityTimeout: Allows us to define a new timespan for how long the message should be invisible
  • MessageUpdateFields: An enumeration with possible values of Visibility or Content. You always need to pass Visibility, but you can also pass Content together to also update the content of the message

We update the CloudQueueMessage and we set the invisibility timeout to 60 minutes from now, so our worker has enough time to process this message. That way the message would not become visible again on the queue while we are processing it.

6. Storage queues versus service bus queues

There is also a possibility to use Windows Azure service bus queues, which is also a queuing mechanism provided by the service bus. However there are some important differences between Windows Azure storage queues and Windows Azure service bus queues. I’ll cover only some of the more important differences between both to base your decision on for what type of queue to use.

You can find the differences in detail here:
http://msdn.microsoft.com/en-us/library/windowsazure/hh767287(v=vs.103).aspx

Using Windows Azure queue storage to build disconnected and reliable systems

The service bus queue supports the following features the storage queue does not:

  • It supports order delivery guarantee
  • It supports duplicate detection, which is the At-Most-Once delivery guarantee
  • It supports long polling requests for receiving a message
  • It allows to send multiple messages in a single batch to the queue, saving on transaction costs

Using Windows Azure queue storage to build disconnected and reliable systems

The service bus queue supports the following features the storage queue does not:

  • It supports automatic dead lettering, which is very useful
  • It supports to relate messages to each other through sessions
  • It supports duplicate detection
  • It supports WF and WCF integration

The storage queue has few features the service bus queue does not have:

  • You can request storage metrics for the queue
  • There is a function to clear the entire queue from all messages

Using Windows Azure queue storage to build disconnected and reliable systems

This is where things get interesting. The storage queue supports a maximum message size of 64 KB while the service bus queue supports 256 KB. The storage queue can have a maximum queue size of 100 TB, while the service bus queue can only have a maximum queue size of 5 GB. However, the storage queue only allows messages to live for a maximum of 7 days, while this is unlimited for the service bus queue.

Using Windows Azure queue storage to build disconnected and reliable systems

The service bus queue has a lot more possibility to authenticate users or applications against, while the storage queue does not.

Deciding between what queue you want to use is depending on the requirements of the application. But the Windows Azure service bus queue is a queue designed for messaging integration.

When to use the Windows Azure storage queue:

  • Your application needs to store over 5 GB worth of messages in a queue, where the messages have a lifetime shorter than 7 days.
  • Your application requires flexible leasing to process its messages. This allows messages to have a very short lease time, so that if a worker crashes, the message can be processed again quickly. It also allows a worker to extend the lease on a message if it needs more time to process it, which helps deal with non-deterministic processing time of messages.
  • Your application wants to track progress for processing a message inside of the message. This is useful if the worker processing a message crashes. A subsequent worker can then use that information to continue where the prior worker left off.
  • You require server side logs of all of the transactions executed against your queues.

However as soon as you need some features like messages with a lifetime longer then 7 days, duplicate detection or dead lettering, then service bus queues are the way to go. If you can manage with the storage queue, then use the storage queue. In general, using a service bus queue will be more expensive then using a storage queue, but it provides you with more features for reliable messaging and messaging integration.

You can find an example of Windows Azure topic/subscription queuing mechanism here:
Windows Azure service bus messaging with publish/subscribe pattern using topics and subscriptions

Any suggestions, remarks or improvements are always welcome.

If you found this information useful, make sure to support me by leaving a comment.

Cheers and have fun,

Robbin

11 comments on “Everything you need to know about Windows Azure queue storage to build disconnected and reliable systems

  1. Pingback: Windows Azure and Cloud Computing Posts for 3/7/2012 - Windows Azure Blog

  2. Pingback: Windows Azure Community News Roundup (Edition #9) - Windows Azure - Site Home - MSDN Blogs

  3. Pingback: Windows Azure Community News Roundup (Edition #9) - Windows Azure Blog

  4. Pingback: Episode 72 – New Tools for Windows Identify Foundation and ACS - Windows Azure Blog

  5. Great article. One wrinkle for people pushing the 64KB limit: A message can be up to 64 KB in size, and its content must be in a format that can be encoded with UTF-8. The storage client library encodes message content using Base64 when CloudQueue.EncodeMessage is set to true, which is the default. This incurs a ~25% overhead on content.

    • Thank you for the feedback John. That’s actually an interesting issue you’re adding here, which I will definately add to the content! I actually had no clue about this whatsoever!

      What’ your name dude ?

  6. Pingback: Episode 72 – New Tools for Windows Identity Foundation and ACS - Windows Azure Blog

  7. Pingback: De Olho no Azure – 24/03/2012 « Pensando Azure

  8. An extraordinary share, Personally i think strongly over it and love reading on this topic.This can be a great post and never hesistate to get more update with the blog with an increase of details! It is highly ideal for me. Big thumb up due to this post!

  9. Your comment about geo-replicating data comes at no additional cost is not correct. Enabling geo-replication on storage accounts in Windows Azure for durability purposes is on by default, but this feature does incurr additional costs over and beyond costs to your local data centre. You can turn this off via the new Windows Azure portal should you want to.

    For pricing see here: http://www.windowsazure.com/en-us/pricing/calculator/?scenario=data-management

  10. Great article with lots of information I need. Just starting to look a possibility of using Azure queue and your article give me a lot of info about how to start, thanks!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s