Sujit Senapati
Oct 28, 2024
  270
(3 votes)

Mastering clearing Cache in Optimizely CMS with ISynchronizedObjectInstanceCache & MemoryCache

In the fast-paced world of web development, efficient memory management is essential for optimizing application performance and enhancing user experience. Among the many optimization techniques available, clearing memory cache using a cache key is a fundamental approach. When storing objects in cache, developers often set an expiration policy, such as an absolute or sliding cache eviction policy, to control how long the data remains. While cache clearing is manageable in lower CMS environments, production environments can be challenging, especially when sliding cache policies make it nearly impossible to expire cached items in real time.

In Optimizely, most cache management is handled by ISynchronizedObjectInstanceCache. However, Optimizely doesn’t offer a straightforward method to retrieve all cache items in memory.

Understanding Memory Cache and Cache Keys

  • Memory Cache: This is where your web application stores data that can be accessed more quickly than if it were stored in a database or on a disk. It's a portion of RAM reserved for temporarily holding data.
  • Cache Key: This is a unique identifier used to store and retrieve specific data from the cache. Using cache keys ensures you're managing the right data.

Why Clear the Cache?

  • Performance: Over time, the cache can become cluttered with outdated data, slowing down access speeds.
  • Data Integrity: Clearing cache ensures your application uses the latest data, vital for dynamic content.
  • Memory Management: Freeing up memory can prevent memory leaks and improve overall performance.

Implementation Steps in Optimizely CMS

Step 1: Identify Your Cache System

First, identify the caching system your application uses. In Optimizely CMS, the common choice is ISynchronizedObjectInstanceCache, which relies on MemoryCache internally.

Step 2: Determine the Cache Key

  • Static Keys: For fixed data, like user profile information, a static key pattern like "user_" + userID can be effective. In Optimizely CMS and Commerce, most caches utilize static keys, making cache management straightforward.
  • Dynamic Keys: For frequently changing data (e.g., session-based), keys might incorporate timestamps or session IDs.

Step 3: Clearing the Cache

With ISynchronizedObjectInstanceCache, you can only clear individual items by specifying their cache key. However, if dynamic values such as user IDs, class names, content IDs, or language IDs are used, determining the exact key can be challenging.

To overcome this, you can retrieve the private property containing all cached items in MemoryCache using BindingFlags. This allows you to access cached entries, making it possible to locate and manage items based on a keyword search within the cache keys.

Here’s an example approach to find all cache keys matching a keyword (e.g., "cacheKey") and then remove them from the cache using ISynchronizedObjectInstanceCache.

var field = typeof(MemoryCache).GetProperty("StringKeyEntriesCollection", BindingFlags.NonPublic | BindingFlags.Instance);
//_memoryCache is an instance of IMemoryCache
var collection = field?.GetValue(_memoryCache) as ICollection;
var items = new List<string>();
if (collection != null)
{
	foreach (var item in collection)
	{
		var methodInfo = item.GetType().GetProperty("Key");
		var val = methodInfo?.GetValue(item);
		if (val != null)
			items.Add(val.ToString());
	}
}

var cacheKeysTobeCleaned =
	items.Where(key => key.StartsWith(cacheKey, StringComparison.OrdinalIgnoreCase)).ToList(); //cacheKey is the keyword we are searching for in cacheKey

if (!cacheKeysTobeCleaned.Any())
	return NotFound();

foreach (string cacheKeyToBeCleaned in cacheKeysTobeCleaned)
{
//_cache is an instance of ISynchronizedObjectInstanceCache
	this._cache.RemoveLocal(cacheKeyToBeCleaned);
	this._cache.RemoveRemote(cacheKeyToBeCleaned);
}
			
This method uses reflection to access the MemoryCache entries, finds keys matching a prefix, and clears them using ISynchronizedObjectInstanceCache.
UPDATE: Although the above approach was mostly used only for urgent scenarios rather than regular cache management. Alternative and the most effective method to clear the cache for a specific list of objects is to use a master key when adding items to the cache, which serves as an additional feature of the CacheEvictionPolicy. For more information, refer to cache objects documentation. While standard dependency keys within a CacheEvictionPolicy create dependencies between existing cache items, a master dependency enables grouping multiple cache entries. When a master key is specified, the cache either confirms the existence of an entry with this key or inserts a placeholder object with an indefinite timeout. This approach allows for the entire group of entries to be removed simultaneously.

Conclusion

Managing cache in Optimizely CMS requires strategic approaches, especially when dealing with a large number of cache entries or dynamic keys. By understanding how to interact with ISynchronizedObjectInstanceCache and leveraging reflection to access MemoryCache, you can maintain a clean, efficient cache system. This not only enhances performance but also ensures your web application remains responsive and up-to-date with the latest data. 
Oct 28, 2024

Comments

valdis
valdis Oct 29, 2024 06:12 AM

nice, this might become handy for my plugin. +1

Sanjay Kumar
Sanjay Kumar Oct 29, 2024 12:27 PM

Really nice article and helpful for me.

David Ortiz
David Ortiz Oct 31, 2024 12:20 AM

Great info! 

Magnus Rahl
Magnus Rahl Oct 31, 2024 10:13 AM

I don't think this is a great idea. First of all you shouldn't be poking around our internal implementation details with reflection because those may of course change and it will break your application. Secondly, iterating over cache keys is likely costly.

On memory management, keep in mind that we have a mechanism in place for monitoring the application memory usage and we will trim the cache if this goes above thresholds. You can configure the thresholds, see Configure in-memory cache limits

When thresholds are met, we trim the cache using the MemoryCache's Compact method with a certain target percentage depending on the cache size and growth rate. Which items are removed follows an algorithm I think you can find i the MemoryCache docs at Microsoft, but essentially it will trim expired items first, then (if needed) other items based on things like how frequenty they are used.

For custom cache items, make sure to at least set a cache timeout, and they will expire and get cleaned out (I think expired items are also not returned, even if they are technically still in cache waiting for the next trim).

A better strategy than to iterate keys and compare patterns is to utilize cache dependencies (which you specify in the CachePolicy item, same as with timeout). For example, if you want a way to clear out all your user cache keyes, use the same dependency key ("user-cache-master-key" etc) for all of them when you add them, and then call ISynchronizedObjectInstanceCache.Remove on the dependency key and all of them will be evicted from the cache (side note: Remove corresponds to RemoveLocal+RemoveRemote so you don't have to call both).

Magnus Rahl
Magnus Rahl Oct 31, 2024 10:16 AM

Clarification: I mixed dependency key and master key here. A master key is a special type of dependency key (removed the same way) but it is just a marker, there is not another cache items you depend on. A master key is what you want to follow the example I gave. It is just in another parameter when creating the CacheEvictionPolicy: Cache objects

Sujit Senapati
Sujit Senapati Oct 31, 2024 02:37 PM

Thank you, @Magnus, for providing valuable feedback.

I agree that iterating through memory cache keys is resource-intensive. However, this approach is intended only for urgent scenarios rather than regular cache management. For instance, our site settings are cached for 15 minutes, but occasionally we need to clear the cache promptly to reflect immediate changes—something that does arise periodically in real-world use.

An alternative and more efficient solution would be to implement a master key for cache management, allowing for rapid clearing on demand when necessary. I will update the blog detailing about using master key.

valdis
valdis Oct 31, 2024 03:27 PM

tinckering with internals via reflection has always been fun :)

ok, I'm lazy enough - so I'll just wait for an blog update to go back to my plugin and redo some stuff :|

PuneetGarg
PuneetGarg Nov 1, 2024 03:44 PM

Good information 

Mark Stott
Mark Stott Nov 6, 2024 12:41 PM

I use ISynchronizedObjectInstanceCache a lot, but I always set a masterkey for data I'm caching and purge based on that masterkey really easily.  It doesn't require me to track what keys I've added to the cache or to look it up in such a way.

I do however find the above very interesting, thank you for sharing :)

Please login to comment.
Latest blogs
Recraft's image generation with AI-Assistant for Optimizely

Recraft V3 model is outperforming all other models in the image generation space and we are happy to share: Recraft's new model is now available fo...

Luc Gosso (MVP) | Nov 8, 2024 | Syndicated blog

ExcludeDeleted(): Prevent Trashed Content from Appearing in Search Results

Introduction In Optimizely CMS, content that is moved to the trash can still appear in search results if it’s not explicitly excluded using the...

Ashish Rasal | Nov 7, 2024

CMS + CMP + Graph integration

We have just released a new package https://nuget.optimizely.com/package/?id=EPiServer.Cms.WelcomeIntegration.Graph which changes the way CMS fetch...

Bartosz Sekula | Nov 5, 2024

Block type selection doesn't work

Imagine you're trying to create a new block in a specific content area. You click the "Create" link, expecting to see a CMS modal with a list of...

Damian Smutek | Nov 4, 2024 | Syndicated blog