Preface
By definition, synergy happens when the interaction between two elements produces an effect greater than the individual elements’ contribution.
Azure Cosmos DB is Microsoft’s globally distributed multi-model database, when we team it up with Azure Functions, Azure’s serverless compute service, the result is the ideal database for the serverless era.
In this series of posts I’ll explore different recipes, gotchas and examples of this integration targeting specific scenarios and act as building blocks for your architecture.
Using and scaling
With Azure Functions, we can create small pieces of code that will run based on events (time, HTTP calls, message queues, database operations, to name a few) and can scale independently to adjust to your computational needs. While we technically do not see nor worry about the servers hosting our Functions (it’s serverless after all!), there is a Runtime (or ScriptHost) that maintains the memory space (besides other things) where our Functions run.
When working with Azure Cosmos DB, we might have seen code that uses the using statement with the DocumentClient
(after all, it implements the IDisposable interface) so we might be tempted to do this in our Function’s code:
#r "Microsoft.Azure.Documents.Client"
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;
using System.Net;
private static string endpointUrl = ConfigurationManager.AppSettings["cosmosDBAccountEndpoint"];
private static string authorizationKey = ConfigurationManager.AppSettings["cosmosDBAccountKey"];
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
using (DocumentClient client = new DocumentClient(new Uri(endpointUrl), authorizationKey)){
string id = req.GetQueryNameValuePairs()
.FirstOrDefault(q => string.Compare(q.Key, "id", true) == 0)
.Value;
if (string.IsNullOrEmpty(id)){
return req.CreateResponse(HttpStatusCode.BadRequest);
}
Uri documentUri = UriFactory.CreateDocumentUri("name of database","name of collection",id);
Document doc = await client.ReadDocumentAsync(documentUri);
if (doc == null){
return req.CreateResponse(HttpStatusCode.NotFound);
}
return req.CreateResponse(HttpStatusCode.OK, doc);
}
}
The problem with this approach is that it is creating an instance of the DocumentClient
in each execution. We all know the woes of this approach for the HttpClient
(and if you don’t, please read it right after this article!), and it has the exact same effect here: If the Function is getting a high volume of triggers, we not only will be penalizing the performance of our database calls with the initialization overhead but the memory consumption will raise and we might even incur in socket exhaustion scenarios.
The static client
One of the first and easiest-to-achieve performance improvement we can implement when working with Azure Cosmos DB is to use a single DocumentClient instance for all the service calls (see the full performance article for more). When we are building applications, we could achieve this by Depedency Injection frameworks to maintain a Singleton instance, but how can we achieve this in our Functions’ code? Turns out it’s pretty easy! Just declare the DocumentClient
as static outside of your Function’s Run code:
#r "Microsoft.Azure.Documents.Client"
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;
using System.Net;
using System.Configuration;
private static string endpointUrl = ConfigurationManager.AppSettings["cosmosDBAccountEndpoint"];
private static string authorizationKey = ConfigurationManager.AppSettings["cosmosDBAccountKey"];
private static DocumentClient client = new DocumentClient(new Uri(endpointUrl), authorizationKey);
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
string id = req.GetQueryNameValuePairs()
.FirstOrDefault(q => string.Compare(q.Key, "id", true) == 0)
.Value;
if (string.IsNullOrEmpty(id)){
return req.CreateResponse(HttpStatusCode.BadRequest);
}
Uri documentUri = UriFactory.CreateDocumentUri("name of database","name of collection",id);
Document doc = await client.ReadDocumentAsync(documentUri);
if (doc == null){
return req.CreateResponse(HttpStatusCode.NotFound);
}
return req.CreateResponse(HttpStatusCode.OK, doc);
}
For security purposes, the code is reading the Azure Cosmos DB Endpoint and Key from the Application Settings. Alternatively you could also use Azure Key Vault to store and retrieve this information. For more information, see my other related article.
This will effectively maintain one instance of the DocumentClient
for all your Function’s executions within the same ScriptHost (instance) and improve the overall performance by reusing the same client for all service calls.
In an scenario where your Function App has multiple instances, each instance will maintain its own static client (it will not be shared among instances).
Stay tuned for more recipes!
Other posts in this series: