Cosmos DB is Microsoft’s new NoSQL store, available through Azure. Unlike relational databases, it is scalable as a hosted database service, making it popular among high transaction .NET and .NET Core applications. However, using Cosmos DB, you need to be wary of performance bottlenecks and cost overhead for accessing the database, as Microsoft charges you for each transaction to the database.
While this database is scalable in terms of transaction capacity, it is not as fast because the database service is in a separate VNet or subscription compared to the applications. So even if your applications run on Azure cloud, accessing the database across the VNet will be a massive blow to its performance. Thus, it is ideal to introduce caching into your Cosmos DB application for drastic performance improvement and a significant operational cost reduction – because 80-90% of the time, your application will be fetching data from the cache instead of the database.
Using Caching with Cosmos DB
The following code snippet explains how to use caching with Cosmos DB. This snippet assumes that a Cosmos DB instance contains a collection of customers, and it searches for the specified customer in the cache based on the cache key. If the item is not in the cache, it queries the Cosmos DB for the customer. If the item exists in the database collection, it retrieves the items and adds it to the cache with a 5-minute expiration to ensure data consistency.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
// Generate a unique cache key string key = "Customer:ALFKI"; // First look for data in cache var retrievedItem = cache.Get(key); // If not found in the cache, fetch data from the database and add it to the cache if (retrievedItem == null) { // New instance of CosmosClient class using a primary connection string CosmosClient client = new CosmosClient("connection-string-from-portal"); // Get container object Container container = client.GetContainer("Northwind", "Customer"); // Create partition key and id // Here, the id is CustomerID and Partition Key is City in the Customer class PartitionKey partitionKey = new PartitionKey("Seattle"); String id = "ALFKI"; // Read item from the container using the id and partitionKey var itemResponse = container.ReadItemAsync(id, partitionKey).Result; // get customer object from database response Customer customer = itemResponse.Resource; // Initialize cache item with customer data and set expiration var cacheItem = new CacheItem(customer); cacheItem.Expiration = new Expiration(ExpirationType.Absolute, TimeSpan.FromMinutes(5)); // Insert cacheItem in cache against the given key cache.Insert(key, cacheItem); |
Using NCache as a Distributed Cache with Cosmos DB
When working with Cosmos DB, it is most likely that your application is a high transaction application running in a multi-server environment through a load balancer, making abundant database calls. Moreover, a standalone cache will not be possible in this environment, so you need a distributed cache like NCache to effectively bridge the gap.
A distributed cache allows you to add more cache servers as your transactional load grows, preventing the database from becoming a bottleneck. This flexibility means that, the number of application servers doesn’t matter because you can have sufficient cache servers between the application and the database to handle the load, unlike a relational database, which is a scalability choke point.
Although Cosmos DB offers superior scalability compared to relational databases, it cannot match the performance of an in-memory distributed cache like NCache, which operates within your application VNet. NCache provides exceptional speed using client-side caching, i.e., by keeping a portion of the cache within the application process.
Caching Collection of Database Items
Using a distributed cache, you can enhance your Cosmos DB application’s performance exceedingly by reducing database trips across the network, especially for read operations. This approach not only reduces the load on your database but also decreases the cost associated with Request Units (RUs) for retrieving entire collections. You can efficiently fetch single entities from the database, apply operations at the caching tier, and update the database only when necessary.
NCache facilitates this by allowing you to cache both individual elements and entire collections as single items each against its own designated cache key. Any changes to the collection state can be pushed to the database at the end of the operations.
Cache Collection as Single Item
You can cache the collection as a single item if you want to load the collection items collectively. For example, if you need to retrieve all customers from Germany. You can query Cosmos DB for all German customers and return the results as a single list, which can then be added to the cache for further use. This approach allows you to load and access the entire collection efficiently without querying the database multiple times as shown below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
string key = $"CustomersFromGermany"; // First look for data in cache IDistributedList<Customer>germanCustomers = cache.DataTypeManager.GetList<Customer>(key); // If not found in the cache, fetch data from the database and add it to the cache if (germanCustomers == null) { // Create a query var query = new QueryDefinition("SELECT * FROM Customers c WHERE c.Country = 'Germany'"); // Get query iterator FeedIterator<Customer>feedIterator = container.GetItemQueryIterator<Customer>(query); // Specify expiration attribute for List var attributes = new DataTypeAttributes(); attributes.Expiration = new Expiration(ExpirationType.Absolute, TimeSpan.FromMinutes(5)); // Create a list to store customers as a collection germanCustomers = cache.DataTypeManager.CreateList<Customer>(key, attributes); // Read DB data using feed while (feedIterator.HasMoreResults) { var response = feedIterator.ReadNextAsync().Result.Resource; foreach (Customer customer in response) { germanCustomers.Add(customer); } } } |
Cache Collection Items Separately
You can associate metadata with cache items in NCache to categorize data through unique identifiers such as tags. So, you can retrieve multiple items from the cache against a single identifier.
To achieve this, you can query for German customers in Cosmos DB and associate a tag such as a Customer: Country: Germany against the resultant items. Caching these items separately will make them available for various query combinations and even faster fetches of a single customer.
Using the previous example, we first search the cache for customers with the tag Customer: Country: Germany. If items do not exist in the cache, query Cosmos DB for items in the Customer collection- having their “Country” attribute specified as Germany. However, we fetch the items from the database, since we want to cache the collection items separately. Set an expiration value for the items along with the tag Customer: Country: Germany against each cache item and add items to the cache in bulk.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
// Create a dictionary to add bulk items in the cache IDistributedDictionary<string, customer> collectionItems = new Dictionary<string, Customer>(key); CacheItem cacheItem = null; var expiration = new Expiration(ExpirationType.Absolute, TimeSpan.FromMinutes(5)); // Read DB data using feed while (feedIterator.HasMoreResults) { var response = feedIterator.ReadNextAsync().Result.Resource; foreach (Customer customer in response) { cacheItem = new CacheItem(customer); cacheItem.Expiration = expiration; //Create a unique key for each item in collection string itemKey = $"Customer:{customer.CustomerID}"; // Add cacheItem to dictionary collectionItems.Add(itemKey, cacheItem); } } // Insert customer collection items separately using bulk operation if (collectionItems.Count > 0) { cache.InsertBulk(collectionItems); } |
NCache Deployment in Azure
Major cloud marketplaces offer NCache, such as Azure and AWS, and downloading for on-site use. For all other cloud systems, you can download and install NCache on a virtual machine in a model of Bring Your Own License (BYOL) deployment. NCache is deployed in Azure in the following ways:
- Deploy NCache Cloud in Azure
- Deploy NCache as Virtual Machines
- Using NCache in a Platform-as-a-Service (PaaS) offering in Azure.
For more detail on these options, have a look at Cloud Deployment Options for NCache.
Conclusion
To sum it up, introducing caching in your Cosmos DB application enhances speed, reliability, and availability. There is a major boost in application performance whenever you use NCache with a database because the cache resides within the application process. Secondly, a drastic reduction in cost as 80-90% of your data is accessible without making costly database trips to Cosmos DB.