Azure Function CosmosDB Trigger Bindings With Redis Cache
By: David Maman
Tags: Azure Functions, Azure, NoSQL, Cosmosdb, Redis

Azure Function CosmosDB Trigger Bindings With Redis Cache

Refresh your cache from CosmosDB data automatically all with function bindings; considering cache reads are cheaper than the RUs used for Cosmos.

Azure CosmosDB… I love this logo :D

Why is this necessary?

I am a big fan of using CosmosDb, specifically the out of the box SQL implementation. It’s fast, easy to use, handles batching, robust client library and has turnkey distribution built in. I’ve hosted a meetup a couple years back with a fellow colleague to discuss a lot of the benefits at length. As much as I love using it, the price adds up fast. Cosmos is billed in Request Units (RU/s) that cost a certain allotment per read and write. Pricing page can be found here. Here’s a quick summary of how it works per Microsoft:

Provisioned throughput is expressed in Request Units per second (RU/s), which can be used for various database operations (e.g., inserts, reads, replaces, upserts, deletes, queries, etc.). For example, 1 RU/s is sufficient for processing one eventually consistent read per second of 1K item, and 5 RU/s is sufficient for processing one write per second of 1K item.

One thing to note, with the SQL API, unless things have changed, Documents can be no larger than 2MB. This will be hard to do but can happen depending on how you setup your data. Be sure to partition well! Partitions are super powerful here.

So if I cut down my reads throughput then I can focus on keeping the data most up to date and rely on another service to read and cache to eliminate duplicate costs for the same data retrieval. For this article, I will use a simple example, some data that doesn’t change often and may not be the best use case, but I will go into great detail on implementations so you can find perhaps a more suitable scenario.

Since I love functions and never had a chance to use the Cosmos bindings, I wanted to give it a shot to see how it worked. Also, since Redis is so cheap, I figured why not use Redis as our caching service and data store? It may be cheaper to host Redis elsewhere, but I’ll let Microsoft manage the cache for me. Here is the pricing page for details on hosting a Redis instance on Azure.

Use Case

For the sake of this article, I will discuss using Cosmos as our main data store that gets updated by an external service. NOTE: This is a completely hypothetical use case and may not be feasible as a long term solution. For legal reasons, I cannot share how this was implemented at my previous company.

In the example diagram I am using a service triggered by events (code won’t be included in this article for that piece). However, it can be updated via any method you choose (timer trigger, queue message, API call etc), the bindings will be triggered the same.

Potential Infrastructure diagram

Lets say we are a food delivery app. For any one restaurant there are many different locations that can fulfill the requests that come in. At any given point a restaurant can become inactive and should no longer be pooled into the group of what’s in stock/available for pickup. We can use this infrastructure to serve this data in almost realtime but cutdown on the amount of R/Us that we have on our RestaurantsLocations database.

For this demo I will only demonstrate caching for one restaurant. You will see the key is static in the function class but I would recommend deciding how you want to cache different restaurant data in redis using unique keys.

Implementation

I will skip initial setup but I can provide more information on how the setup works if needed.

Upsert CosmosDB with latest data

Here we have our first HTTP Endpoint that we will use to update the data from our said service (in this case im using HTTP trigger binding, diagram has event driven).

We can combine HTTPTrigger bindings with our CosmosDB binding to establish our DB connection and add data. Line 30 we will query our database and see if there is an existing documents by restaurantid. We do this so we can get the id GUID thats stored in the database and update/insert via the DocumentClient’s UpsertDocumentAsync . Upsert is smart enough not to insert duplicate documents with the same GUID and if it doesn’t exist, will generate a new GUID for you. You can disable auto Guid generation if you want to pass your own by setting the disableAutomaticIdGeneration to true . Technically lines 30–41 aren’t required if you enforce the Guid to be passed in the request.

CosmosDB Trigger Fired

This binding is where all the magic happens. What happens is the trigger creates a lease on the collection that returns all the data that has been updated and allows you to either parse from the beginning of the lease or stream from the lease. In this case we start from the beginning to make sure we have the cache fully in sync. After this function is done the lease gets disposed till a new trigger is fired. This also ensures there aren’t two listeners holding onto the same lease and cause data collision.

Now the logic of this method is up to how you are organizing your cache and what you would like to change. If we have items in the cache we want to make sure that all the items in the cache have the latest data by searching by Guid ID and then adding any items that don’t exist in the cache (nothing in the cache with that guid). We will set the cache and the data will be in sync. You can set the cache to expire, for now I have an arbitrary number of 365 days.

Load Data from the Cache w/ Fallback

Here is where we will actually pull the data from to display on our front-end or return from an API call. I’ve included the CosmosDB provider in case for some reason there aren’t any items in the cache (expired, resource moved etc), we can call to Cosmos directly as a fallback and refill the cache with the latest data.

Takeaways

Overall this is a fairly simple solution to get data out of a cache and reduce costs per request when it comes to using a Cosmos data store. Each trigger you add can serve a variety of purposes. It can update a cache, send emails, trigger metrics, or any other functionality you see fit.

The main idea here is that you can leverage built in hooks from different cloud services to keep our distributed systems communicating with just a few lines of code. I’ve included some handy extensions for the IDistributedCache that can help convert byte data to your models.

I hope this was informative and as always feel free to poke around the github repo if you have any comments or suggestions.

Thanks again!