NCache introduces Entry Processors to allow us to execute a function (or “processor”) against a set of caching entries on the server side.
Usually, to update our cache entries, we have to “talk” to our cache server twice. Once to retrieve entries from the cache and another to update them back after processing them. But, with Entry Processors, we update our cache entries directly on the server, saving these network trips and unnecessary consumption of resources.
NCache runs Entry Processors regardless of the caching topology being used. In the case of partitioned topologies, Entry Processors run on the nodes containing our entries to process.
Once we invoke an Entry Processor, it processes a single item or a set of them based on the logic we defined. NCache runs our processor across the cluster. Inside an entry processor, we can:
- Return a cache entry without processing it,
- Modify an entry and save it into the cache or,
- Remove an entry.
If an entry is locked before running the processor, we can ignore the lock and access the locked entry to process it.
NCache Details NCache Entry Processor Entry Processor Implementation
Comparing Entry Processor with Other Cache Operations
Entry Processors help us to update or purge cache or entries directly on the server by saving the network trips of fetching and updating cache entries.
To see Entry Processors in action, let’s write a Console app to load some customers into a cache. Let’s store the name, reward points, and last purchase date of a customer. And to motivate our customers to buy, let’s double the reward points of all customers with a purchase in the past five days. Then, let’s recreate the same scenario using an entry processor and bulk cache operations to see the difference in the execution time between these approaches.
Before starting our sample application, we should have an NCache Enterprise edition instance up and running. Entry Processors are only available in the Enterprise edition.
1. Using Bulk Cache Operations
NCache supports Bulk operations to retrieve and update cache entries simultaneously in a single call. With Bulk operations, we have better performance since we reduce the number of network trips to our cache server.
To retrieve multiple cache entries in bulk, we need the GetCacheItemBulk()
with a list of keys. It returns the found items in a dictionary. And, to update items in bulk, we need the InsertBulk()
method with a dictionary of items to update.
To start our comparison, let’s use NCache GetCacheItemBulk()
and InsertBulk()
methods to retrieve and update cache entries in a single request.
In the Program.cs
file of our Console app, let’s write something like this,
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 33 34 35 |
using Alachisoft.NCache.Client; using RewardPoints; using RewardPoints.Shared; const string CacheName = "demoCache"; var customers = new List { new Customer(1, "Alice", DateTime.Today.AddDays(-1), 100), new Customer(2, "Bob", DateTime.Today.AddDays(-6), 5), new Customer(3, "Charlie", DateTime.Today.AddMonths(-1), 1), new Customer(4, "Daniel", DateTime.Today.AddDays(-3), 10), new Customer(5, "Earl", DateTime.Today, 20) }; var keys = customers.Select(c => c.ToCacheKey()); // Load customers into the cache ICache cache = CacheManager.GetCache(CacheName); PopulateCache(cache, customers); // Double customer points using NCache Bulk methods var retrievedItems = cache.GetCacheItemBulk(keys); var itemsToUpdate = new Dictionary<string, CacheItem>(); foreach (var item in retrievedItems) { var customer = item.Value.GetValue(); // Check if the customer has purchased anything in the last 5 days if (customer.LastPurchase >= DateTime.Today.AddDays(-5)) { var updated = customer with { Points = customer.Points * 2 }; itemsToUpdate.Add(updated.ToCacheKey(), updated.ToCacheItem()); } } cache.InsertBulk(itemsToUpdate); |
First, we loaded some customers into a cache. We used the default “demoCache” cache created during the NCache installation. Next, we retrieved our customers using the GetCacheItemBulk()
method. Then, after checking the last purchase date of every customer, we doubled his points and put all the updated customers back into the cache.
Instead of updating our customers individually, we used a dictionary to accumulate the customers who won the reward. Then we used the InsertBulk()
method with the dictionary.
With the GetCacheItemBulk()
and InsertBulk()
methods, we reduced our network trips to only two. One network call to retrieve all customers and another one to save them back into the cache.
For more details about these and other Bulk methods, check CRUD Operations: An Overview.
NCache Details Bulk Operations Bulk Operations Overview
2. Using an Entry Processor
Now that we have used Bulk methods, let’s double our customers’ rewards points using an Entry Processor. Something like this,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
using Alachisoft.NCache.Client; using RewardPoints; using RewardPoints.Shared; const string CacheName = "demoCache"; var customers = new List { new Customer(1, "Alice", DateTime.Today.AddDays(-1), 100), new Customer(2, "Bob", DateTime.Today.AddDays(-6), 5), new Customer(3, "Charlie", DateTime.Today.AddMonths(-1), 1), new Customer(4, "Daniel", DateTime.Today.AddDays(-3), 10), new Customer(5, "Earl", DateTime.Today, 20) }; var keys = customers.Select(c => c.ToCacheKey()); // Load customers ICache cache = CacheManager.GetCache(CacheName); PopulateCache(cache, customers); // Double customer points using Entry Processor var processor = new DoublePointsProcessor(); cache.ExecutionService.Invoke(keys, processor); |
Notice we didn’t have to use any method to update the cache entries. We only created a processor, passed a set of keys, and called the Invoke()
method. We have to update the customers inside the entry processor itself.
NCache Details NCache Entry Processor Entry Processor Implementation
Configuring Entry Processors
To write an entry processor, we need to create a class that inherits from IEntryProcessor
and uses the [Serializable]
attribute. This is our entry processor,
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 |
using Alachisoft.NCache.Runtime.Processor; namespace RewardPoints.Shared; [Serializable] public class DoublePointsProcessor : IEntryProcessor { public bool IgnoreLock() => true; public object Process(IMutableEntry entry, params object[] arguments) { // Check if the cache entry is a customer and // if the customer has purchased something in the last 5 days if (entry.Key.StartsWith(nameof(Customer)) && entry.Value is Customer { LastPurchase: var lastPurchase } customer && lastPurchase >= DateTime.Today.AddDays(-5)) { // Update the customer's points var updatedCustomer = customer with { Points = customer.Points * 2 }; entry.Value = updatedCustomer; return updatedCustomer; } return false; } } |
To update a cache entry in a processor, we need to overwrite the Value
property of the entry
parameter with the updated cache entry.
If a client application is locking an entry, we can run the Entry Processor over it using the IgnoreLock()
method. It will ignore the lock and access the entry to process it.
Before using our new processor, we need to deploy the DLL with our processor and its dependencies to NCache. We can use the Powershell Install-NCacheModule
cmdlet or NCache Web Manager. For instructions to deploy our entry processor using the Web Manager, check Deploy Providers.
For example, let’s stop our “demoCache” instance and run the Powershell Install-NCacheModule
cmdlet,
NCache Details NCache Entry Processor Entry Processor Implementation
How Do Entry Processors Deal with Operation Failures?
Once an entry processor runs, it returns a collection of results of type IEntryProcessorResult
. Each result contains an IsSuccessful
flag and a Value
with the result of our modifications. For our sample entry processor, the Value
property will contain either false
, if the customer didn’t have any recent purchase, or an updated customer, otherwise.
If an exception is thrown while the entry processor runs, the IsSuccessful
flag will be false
and the Exception
property will be populated with the exception thrown.
To make our entry processor fail-safe, we should handle any potential exceptions. For example, we could get an OperationFailedException
if there was any connection failure. Also, we could get an
EntryProcessorException
, in this case, the entry processor didn’t modify any cache entries.
This is how we could display the entry results and possible exception messages,
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 33 34 35 36 37 38 39 40 |
using Alachisoft.NCache.Client; using RewardPoints; using RewardPoints.Shared; const string CacheName = "demoCache"; var customers = new List { new Customer(1, "Alice", DateTime.Today.AddDays(-1), 100), new Customer(2, "Bob", DateTime.Today.AddDays(-6), 5), new Customer(3, "Charlie", DateTime.Today.AddMonths(-1), 1), new Customer(4, "Daniel", DateTime.Today.AddDays(-3), 10), new Customer(5, "Earl", DateTime.Today, 20) }; var keys = customers.Select(c => c.ToCacheKey()); // Load customers ICache cache = CacheManager.GetCache(CacheName); PopulateCache(cache, customers); try { // Double customer points using Entry Processor var processor = new DoublePointsProcessor(); var processedEntries = cache.ExecutionService.Invoke(keys, processor); foreach (IEntryProcessorResult entryResult in processedEntries) { DisplayEntryResult(entryResult); } } catch (EntryProcessorException e) { Console.WriteLine($"Error running the processor: {e.Message}"); } catch (OperationFailedException e) { Console.WriteLine($"Error connecting to the cache server: {e.Message}"); } |
Now, let’s run our three alternatives side by side. I ran them on my machine with NCache installed locally and using 20 random customers I generated using Bogus. And here it’s the result,
Conclusion
Definitively, the alternative with an Entry Processor was the fastest one. It finished in 12 milliseconds, while the one with Bulk methods finished in 72 milliseconds. With Entry Processors, we noticed a performance improvement since we saved all the excessive network trips by doubling the points of our customers directly on the server.
That’s what Entry Processors are and how they work. Even though we didn’t write a benchmark to test this NCache feature, this comparison gives us an idea of how to use Entry Processors. With them, we save some roundtrips to our NCache server by processing our cache entries directly in an NCache server. The next time you need to update some of your cache entries, give Entry Processors a try.
For more details on how Entry Processors work, check Working of Entry Processor in Cache.
To follow along with the code, we wrote in this post, check my NCache Demo Repository on GitHub.