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);
}
nice, this might become handy for my plugin. +1
Really nice article and helpful for me.
Great info!
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).
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
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.
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 :|
Good information
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 :)