In building large scale mobile apps, the common issues faced by developers are:
- Performance of web services delivering application data to massive number of devices
- Caching of objects on cloud servers with memory constraints
- Reducing compute costs
- Global distribution of application objects
So, how should an application deliver applications objects to massive number of clients and at the same time optimize on the compute costs in the cloud?
If I had to rank the top 3 design features an architect must focus on when designing a cloud application, they would be:
- Data Storage
- Security & Identity
Typically, software developers are used to programming with two basic assumptions:
- Their website will have access to the local file system
- They can scale-up and scale-out the database by adding more hardware.
In the cloud, you don’t have any control over the hardware and have to operate within the constraints of the cloud provider’s environment. For example, when you run compute (or VM) instances in Azure, you are constrained by the following at the minimum:
- The disk space available to you is limited by the size of the VM instance
- The network and disk IO is constrained by the size of the VM
- Azure SQL database size is limited to 150GB
- Azure SQL infrastructure is shared and your performance may be affected by some else’s poorly designed database running on the same server as yours
In Azure Web Sites (WAWS), you don’t have a local file system to store application data
Note: Even though I have implemented the pattern in Azure Blob storage, any cloud storage with similar capabilities can be used for implementing this pattern.
For building real-world applications, these constraints are significant, but flexibility offered by Azure (and other cloud compute solutions) sometimes outweighs these constraints. For example, you automatically get a highly available Azure SQL database and Azure Storage. Azure provides you with virtually unlimited storage capacity for storing data in Blobs and Tables.
Compute services are expensive even if you cache objects in memory
As an architect, you are constantly challenged to think beyond traditional methodologies and patterns to take advantage of services offered by cloud platforms such as Azure. For example, in Azure Web Sites (WAWS), there is no access to local file system and the network IO performance also depends on size of the VM your website is running. As a result, to get the most out of your compute instances, you have to run the website in the costliest available plan (i.e. Standard Plan with Large VM). As a traditional architect, you would quickly think of increasing VM size for performance gains, but in today’s world of a billion-plus mobile devices, your scale is challenged even with largest of the VMs available in the cloud.
While building a Windows Store app for http://www.dynamicdeploy.com , the client app developer was constantly complaining about the time it took for the data to load on the screen because the web service was hosted in US and then developer was in India. That is where I started thinking of de-coupling compute web services for some of the method invocations.
“The products are taking longer to load in the Windows 8 Store app”
For building high-performing websites, I am introducing a pattern called Storage Object Distribution pattern that would provide you with scale and efficiency in delivering objects to a large volume of devices. The Storage Object Distribution pattern allows you to leverage the scale and bandwidth of storage services, yet access application objects like if you were calling a REST API to retrieve the object. The pattern allows you to distribute application objects to a storage service such as Azure Blob storage, and enables convention-based URL routing on each object. The client application accesses the object using the HTTP URI similar to a REST URI. For example, a client will access a customer object with the URI http://www.dynamicdeploy.com/customers/[customer id]. The customer object, instead of loading from a web service, will be retrieved from the blob storage.
The Storage Object Distribution pattern allows you to leverage the scale and bandwidth of storage services, yet access application objects like if you were calling a REST API to retrieve the object.
Scenario: You want to build a highly-scalable website (gaming, advertising, or social) to support millions of mobile devices.
Figure 1.1 below illustrates the approach that a traditional architect will take for building such a large volume website on Azure Web Sites.
Figure 1.1: Traditional Architecture
In Figure 1.1, WAWS hosts all the web services and websites for supporting the high-volume website. Considering the volume, you will need to run it in the Standard Plan with Large instances. Even if your compute is able to manage the load, your database will be the bottleneck in handling these requests. To reduce the load on the database, you will typically implement a caching layer for caching some of the data in Redis Cache. As a result, you will face a compute-cost bottleneck. A compute-cost bottleneck is where your compute costs rises proportionately with the user volume until it reaches a point of diminishing return.
SOD pattern leverages blob capabilities (or Azure Blob Storage) for delivering high-volume responses to mobile devices. Azure Blob storage is a static repository, but it also doesn’t have any constraints based on the plan you use. What that means is that if you are experiencing 10000 transactions/seconds for your objects stored in Azure Storage, unlike compute service (e.g. WAWS), you will not be throttled based on your plan. Though throttling is still in place, it applies to all the storage accounts, not dependent on the plan you use.
SOD Pattern Architecture
SOD pattern periodically distributes live static or dynamic objects to cloud blob storage, such as Azure Blob Storage, for client applications to consume directly based on pre-configured URI schemes.
For constantly changing objects, it is better to use compute + data services directly instead of SOD. Figure 1.2 illustrates the architecture of SOD pattern.
Figure 1.2: SOD Architecture
In SOD architecture in Figure 1.2, the Object Distributor is a scalable batch process that reads custom objects (i.e. C# or Entity Framework) from varied data sources such as database(s) or web services, and then distributes them into the appropriate Azure Blob containers. Each container consists of objects belonging to its type. For example, the Customers container will hold all the individual customer objects as well as objects representing pre-defined queries such as, Customers by Location, or Products by Category. When you store these objects in the Blob storage, you can set several HTTP header properties such as Cache-Control and Content Type, that will help you scale up to the volume you desire. From the mobile browsers or other client devices, you can access the JSON objects directly without going through the compute VM. For example, for retrieving the list of all products, you can access it using the following URL
The Object Distributor retrieves native objects from data sources (databases or web services), serializes them as JSON or XML objects, and then copies them to the Blob containers. The Object Distributor can itself run as a Cloud Service or a Windows Service in a Virtual Machine. If your architecture demands, you may even run it on-premises as a Windows Service. The clients requiring fresher data can bypass the Blob storage and call the data source web services directly (see dotted line Figure 1.2). If the object in the client-cache becomes stale, the HTTP Cache-Control options max-age and must-revalidate will make sure a fresh object is retrieved when the max-age expires.
In a scaled-out environment, the Object Distributor can create a lease-lock and verify the Last Modified Time property on a blob to avoid multiple instances of Object Distributor updating the same blob within the update cycle.
Listing 1.1 shows the code-snippets for quickly building an Object Distributor that retrieves objects from a WebApi and then copies them to a Blob container.
1: //Create Blob Extensions
3: //Extension to the Blob Class
5: public static class BlobExtensions
9: //A convenience method to set the Cache-Control //header.
11: public static void SetCacheControl
13: (this ICloudBlob blob, string value)
17: blob.Properties.CacheControl = value;
23: public static void SetContentType
25: (this ICloudBlob blob, string value)
29: blob.Properties.ContentType = value;
35: //Set both the properties from a single method
37: public static void
41: (this ICloudBlob blob, string cacheControl, string contentType)
45: blob.Properties.ContentType =
49: blob.Properties.CacheControl =
61: //Distribute the object to Azure Blob storage
63: public static string DistributeObject(
65: string containerName,
67: string blobName,
69: string restUrl,
71: string cacheControl = "public, max-age=3600, must-revalidate",
73: string contentType="application/json")
77: using (WebClient w = new WebClient())
81: if (StorageHelperNew.DoesBlobExist(containerName, blobName))
85: ICloudBlob blob = StorageHelperNew.GetBlob
(containerName, blobName, fetchAttributes:true);
89: if the different between now and blob's last modified is greater than 45 mins, then only modify
93: if ((DateTime.UtcNow – blob.Properties.LastModified) <
97: return "";
103: //Download the JSON blob from Web Api and upload //it to Blob Storage
105: string ret = StorageHelperNew.PutBlob
(containerName, blobName, w.DownloadData(restUrl), true);
107: //Set the Cache Control and the Content Type of the blob
111: //return the Blob Uri
113: return ret;
117: return "";
Listing 1.1: Distribute Object Method
In Listing 1.1 note the Cache Control and Content Type HTTP header parameters set as Blob properties. The Cache Control header instructs the client browser to cache the blob for 1 hour and must revalidate the blob again after it expires. The public parameter instructs the browser to cache authenticated responses. When the browser retrieves the file for the first time, the caching instructions are embedded in the header as illustrated in Figure 1.3.
Figure 1.3: Cache Control on First Time Blob Load
Second time the client loads the blob within 1 hour, the response looks like Figure 1.4.
Figure 1.4: Cached response
In Figure 1.4, the HTTP 304 (Not Modified) status code and the Content-Length=0 confirms hat the blob is loaded from the client’s cache and not from the Azure blob storage. Thus, with SOD, you can leverage the client’s caching abilities and reduce the load on the valuable and costly compute server resources. If the object is loaded from the client’s cache, you will not be paying any bandwidth (egress) costs for loading the object, which is not true for server-side caching. Even if you don’t cache the response, it might still be worth loading the objects from the Blob storage instead of web service to take advantage of the higher I/O and scale of the Azure Storage service.
Real World Apps using this pattern
SOD pattern, though not directly related to Azure as such, is an invaluable design pattern to complement cloud compute services for high-scale and high-performing apps. For the right scenario, the scale and performance you can achieve with SOD will seldom be met by the compute services.
References and Further Reading
Save Money by Setting Cache-Control on Azure Blobs
IIS7 + Azure Blob Storage Cache-Control
ASP.NET Web API and Azure Blob Storage
A guide to asynchronous file uploads in ASP.NET Web API RTM
Dealing with large files in ASP.NET Web API
Asynchronously streaming video with ASP.NET Web API
Async Streaming in ASP.NET Web API