Caching is a technique for storing data in the server's memory. It will enhance the application's performance by reducing database calls. Indirectly, it will improve the efficacy of the database by delivering higher throughput and lower latency than most databases. This blog will cover caching techniques/patterns such as full Cache, cache-aside, and read-through using the NCache cache service provider.

What exactly is NCache?
NCache is an open-source distributed cache for.NET, Java, and Node.js that operates in memory. NCache is incredibly quick and scalable, and it caches application data to minimize database access. NCache is utilized to address the performance issues associated with data storage, databases, and the scalability of.NET, Java, and Node.js applications.

Learn how to use NCache here. Please complete the NCache installation process.

Full Cache

A full cache is a technique that loads all datasets at the outset of an application. This Technique is commonly used for applications with inert or infrequently changing data. It enhances application performance by decreasing data source calls. NCache provides the Cache Startup injector feature for implementing the Full Cache. In other terms, it populates it with all data beforehand.

By implementing the ICacheLoader interface, Cache Startup Loader can be simply integrated into our current and future applications. Enabling the Cache Loader/Refresher from NCache Web Manager is the next step. This capability is exclusive to the enterprise edition.

Launch NCache Web Manager, select the existing Clustered Cache or create a new clustered cache with the minimum number of nodes required to configure the full Cache, and then click View Details.

This link will transport you to the Cache Settings page, where you can enable Cache Loader/Refresher.

Please visit the aforementioned link and finalize the CacheLoader / Refresher configuration. Consider, for instance, a web application that imports hundreds of images on the initial page. In this instance, we should utilize a cache startup loader that pre-loads the Cache with all images at application startup, allowing all images to load from the Cache, which is extremely fast and also eliminates the data source request.

Any modifications to the images from the data source will render the Cache obsolete. Cache Refresher is another feature offered by NCache to prevent this invalidation.

public class LoaderRefresher: ICacheLoader {
  private string _connectionString;
  private static ICache _cache;
  public void Init(IDictionary < string, string > parameters, string cacheName) {

    if (parameters != null && parameters.Count > 0)
      _connectionString = parameters.ContainsKey("ConnectionString") ?
      parameters["ConnectionString"] as string : string.Empty;
      _cache = CacheManager.GetCache(cacheName);
  }

  public object LoadDatasetOnStartup(string dataSet) {

    IList < object > loadDatasetAtStartup;
    if (string.IsNullOrEmpty(dataSet))
      throw new InvalidOperationException("Invalid dataset.");

    switch (dataSet.ToLower()) {
      //call FetchProductsFromDataSouce based on dataset value
    }

    string[] keys = GetKeys(loadDatasetAtStartup);
    IDictionary < string, CacheItem > cacheData = GetCacheItemDictionary(keys, loadDatasetAtStartup);
    _cache.InsertBulk(cacheData);

    object userContext = DateTime.Now;
    return userContext;
  }

  public object RefreshDataset(string dataSet, object userContext) {
    if (string.IsNullOrEmpty(dataSet))
      throw new InvalidOperationException("Invalid dataset.");

    DateTime ? lastRefreshTime;

    switch (dataSet.ToLower()) {

      //call FetchUpdatedSuppliers based on dataset value and insert the update value in cache.

    }

    userContext = DateTime.Now;
    return userContext;
  }

  public IDictionary < string, RefreshPreference > GetDatasetsToRefresh(IDictionary < string, object > userContexts) {
   IDictionary < string, RefreshPreference > DatasetsNeedToRefresh = new Dictionary < string, RefreshPreference > ();
    DateTime ? lastRefreshTime;
    bool datasetHasUpdated;

    foreach(var dataSet in userContexts.Keys) {
    switch (dataSet.ToLower()) {
        // Based on switch input check the dataset to be updated, if it returns true call invoke DatasetsNeedToRefresh method for the dataset.
      }
    }

    return DatasetsNeedToRefresh;
  }
  p
ublic void Dispose() {
    // clean your unmanaged resources
  }
}

Local/Clustered Cache information and Database connection string information are set by the Init Function.

During program startup, the loadDatasetOnStartup function imports the dataset from the data source.

RefreshDataset
This function is invoked according to the Refresh Frequency specified during CacheLoader / Refresher configuration.  
GetDatasetsToRefresh – This function is used to obtain a new dataset in real-time via polling if the refresh-on-event property is enabled, as depicted in the following illustration. This configuration can be enabled on the NCache web manager cache details page under the Cache Loader/Refresher section.
Create a new class to support the IReadThruProvider interface.

The Cache keeps calling this method to update the data set based on the frequency/interval set for “Refresh-Interval”. By default, it is 900 sec.

You can refresh the cached dataset manually using NCache Web Manager or Manager.

NCache web Manager: The refresh now option for the dataset is available in the cache details page under Cache Loader/Refresher section from advanced settings.

PowerShell command: Use the below command to refresh the dataset.
Invoke-RefresherDataset -CacheName [your clustered cache name] -Server [server IP address] -Dataset [your dataset name] -RefreshPreference RefreshNow

Cache-Aside

In the Cache-aside technique, the database and Cache won’t interact. Let me elaborate on the step-by-step process involved in this caching Technique.
Step 1. The application tries to get data from the Cache.
Step 2. In this step, the flow will check whether the request data(Key) is available in the cache server.
Step 3. If the Key is unavailable, the application will directly talk to the database.
Step 4. The data from the database is transferred to the application.
Step 5. Add the data into the Cache.


NCache, one of the leading caching service providers, to implement the cache-aside functionality in our application, the below code snippet.
try
{
    // Pre-condition: Cache is already connected

    // Get key to fetch from cache
    string key = $"Product:{product.ProductID}";

    // Get item against key from cache in template type
    var retrievedItem = cache.Get<Product>(key);
    if (retrievedItem != null)
    {
        if (retrievedItem is Product)
        {
            // Perform operations according to business logic
        }
    }
    else
    {
        // block to query the datasource and write it to the Cache server
    }
}

So, in the process, if the data is not cached, the application will directly connect with the database to get new data. Each Technique in caching has its pros and cons.

Advantages
If the caching layer is unavailable, the application still works. Instead of querying the caching, you will have complete control of the database every time.

Disadvantages

The very first time, the access will be slower. Because the keys may not be available in the Cache, so initially, the application will update the Cache by fetching the data from the database. This process may slow down the performance of the application. On the other hand, you will have inconsistent data. A separate service is required to update the data in the Cache whenever there is an update in the database for a table.

Read-through technique
Read-thru is another technique where the cache layer is always synced with the database layer. The application will query the Cache directly to get the data. If the requested Key is not available in the Cache, the cache layer will directly talk with the database layer and update the Cache. The step-by-step process involved in this caching Technique.

Step 1. The application tries to get data from the Cache.
Step 2. Check the requested key availability in Cache.
Step 3. If the Key is not available, the cache layer will directly talk with the database layer.
Step 4. The new data will be added to the Cache.
Step 5. The cache layer will return the data to the application.


public class SqlReadThruProvider : Runtime.DatasourceProviders.IReadThruProvider
    {
        private SqlDatasource sqlDatasource;

        public void Init(IDictionary parameters, string cacheId)
        {
            object connString = parameters["connstring"];
            sqlDatasource = new SqlDatasource();
            sqlDatasource.Connect(connString == null ? "" : connString.ToString());
        }

        public void Dispose()
        {
            sqlDatasource.DisConnect();
        }

        public ProviderCacheItem LoadFromSource(string key)
        {
            ProviderCacheItem cacheItem = new ProviderCacheItem(sqlDatasource.LoadCustomer(key));
            cacheItem.ResyncOptions.ResyncOnExpiration = true;
            return cacheItem;
        }

        public IDictionary<string, ProviderCacheItem> LoadFromSource(ICollection<string> keys)
        {
            IDictionary<string, ProviderCacheItem> providerItems = new Dictionary<string, ProviderCacheItem>();
            foreach (string key in keys)
            {
                object data = sqlDatasource.LoadCustomer(key);
                ProviderCacheItem item = new ProviderCacheItem(data);
                item.Expiration = new Expiration(ExpirationType.DefaultAbsolute);
                item.Group = "customers";

                providerItems.Add(key, item);
            }
            return providerItems;
        }

        public ProviderDataTypeItem<IEnumerable> LoadDataTypeFromSource(string key, DistributedDataType dataType)
        {
            ProviderDataTypeItem<IEnumerable> providerItem = null;
            switch (dataType)
            {

                case DistributedDataType.Counter:
                    providerItem = new ProviderDataTypeItem<IEnumerable>(sqlDatasource.GetCustomerCountByCompanyName(key));
                    break;
                case DistributedDataType.Dictionary:
                    providerItem = new ProviderDataTypeItem<IEnumerable>(sqlDatasource.LoadCustomersByCity(key));
                    break;
                case DistributedDataType.List:
                    providerItem = new ProviderDataTypeItem<IEnumerable>(sqlDatasource.LoadCustomersFromCountry(key));
                    break;
                case DistributedDataType.Queue:
                    providerItem = new ProviderDataTypeItem<IEnumerable>(sqlDatasource.LoadCustomersByOrder(key));
                    break;
                case DistributedDataType.Set:

                    providerItem = new ProviderDataTypeItem<IEnumerable>(sqlDatasource.LoadOrderIDsByCustomer(key));
                    break;
            }

            return providerItem;
        }
    }

We have five methods implemented from the IReadThruProvider interface.

  • Init(): establish the connection to the data source.
  • LoadFromSource(string key): This method is used to load the data to the Cache from the source based on the Key.
  • LoadFromSource(ICollection<string> keys): This method is used to load multiple objects from the data source and returns the dictionary of keys with respective data contained in ProviderCacheItem
  • LoadDataTypeFromSource(string key, DistributedDataType dataType): This method load data structure from the external data source and returns the data Structure contained in ProviderCacheItem, which is enumerated.
  • Dispose(): This function will release the resource.

var ReadThruProviderName = "SqlReadThruProvider";
var customer = _cache.Get<Customer>(CustomerID, new ReadThruOptions(ReadMode.ReadThru, ReadThruProviderName));


The above statements are used to get data from the Cache with the read mode as ReadThru.

Pros
The data is always up to data in the Cache, the application code doesn’t query the database, and everything will take care of by caching layer.

Cons
Single point of failure -If the caching layer is not available, the application can’t retrieve data because it’s not connected to the database.

We have seen what is full Cache, Cache-aside, and Read-thru Cache and how NCache as a distributed cache provider, used to implement all these features or techniques. All these techniques have pros and cons, so based on the requirement, we need to understand which is the right choice to solve our business problem.