Hybrid Caching in Xperience

2025/01/07 9:20 AM

Hey guys, I'm keen to use the new HybridCache API with Xperience and am looking for a bit of guidance and to gauge the consensus around multilevel caching in general.

For those unaware: HybridCache library in ASP.NET Core | Microsoft Learn

HybridCaching combines an in-memory cache (L1) with a distributed caching solution (L2). This has a number of benefits over a traditional single tier cache:

  • Fast cache item access via L1 in memory store
  • High availability and resilience to server restarts, deployments etc...
  • Scalability across multiple nodes

This is all great, but the only problem is HybridCache is still in preview, and is missing a lot of key features which would be required for a successful integration, in particular:

  • A way to invalidate cache

  • A way to synchronise changes between nodes

    • We would also need a way of synchronizing L1 cache changes between each node in a scaled out scenario

I have a custom solution for tagging cache entries in place already, so that leaves the synchronization issue, which I believe I can solve using either of these approaches:

  • Custom web farm task

    • Create a custom web farms task which is pushed in response to some CMS action such as updating a page or object
    • The task would contain a collection of dummy keys specific to that object
    • Each running node would then receive the dummy keys and handle invalidation of its own L1 cache
    • Only issue is it appears custom web farms tasks are not officially supported in Xperience, although the API for them still exists
  • Use redis pub/sub

    • We could use redis pub/sub pattern to synchronise changes between nodes
    • We would designate a specific instance (could be the CMS) as the publisher, who publishes to a cache sync channel
    • The subscribers (frontend instances) would listen to this channel for messages containing dummy keys to invalidate

These are the challenges I'm currently faced with if I opt to use the new HybridCache API, however I've also come across an alternative package which is more mature, and offers many of the same features as HybridCache and more.

FusionCache:

https://github.com/ZiggyCreatures/FusionCache

For a comparison with HybridCache:

https://github.com/ZiggyCreatures/FusionCache/blob/main/docs/Comparison.md

This appears to do everything needed and has some other useful features such as automatic failsafes and fallback to stale content in the event of errors.

In particular, it implements it's own 'RemoveByTag' functionality as well as a cache 'backplane' for syncing changes to each nodes L1 cache (which I believe uses redis pub/sub under the hood), so this solves the primary issues I'm facing with HybridCache.

I will just summarise my question with the below points:

  • Do you have any thoughts on Hybrid Caching in general to share, or on either of the approaches I've outlined above (.NET HybridCache or FusionCache)?
  • Is this something Kentico are considering integrating into Xperience themselves and perhaps they’re waiting for HybridCache to mature? I do know that Umbraco have already added it to their latest version.
  • Would you use Hybrid Caching at all or just stick with a single in memory cache and keep things simple?

Thanks

Answers

2025/01/07 2:51 PM

Hybrid caching would be awesome for scaling out. Overall if you code your site right then the speed of .net usually won't make too much of an impact on individual site caches, but this isn't always the case. There is also the consideration of when a site gets deployed and recycled, you may NOT want the cache to persist until all nodes are replaced, in case you cache something like HTML output that is now different with a different view.

Overall though, depending on how it goes, I would be very interested in what you are working on! Are you on the community slack channel? If not, shoot me an email ( `$"{{firstInitial}}{{LastName}}@gmail.com"` ) and i'll add you, then we can chat!

2025/01/07 3:09 PM

TLDR

Use Xperience's IProgressiveCache and move on to higher value work for your client.

HybridCache

HybridCache was created to solve problems when switching from IMemoryCache to IDistributedCache. You can basically think of it as a replacement for both with additional features.

In Xperience we have IProgressiveCache, which was created before HybridCache existed, but has many of the same capabilities.

Xperience does distributed caching through our web farm support, which coordinates cache eviction through web farm tasks synchronized through the database. This requires more database resources than an external cache, like Redis, but with much less complexity and doesn't require the cost of an additional external service.

Web farms also help with server restarts since a proxy server can send traffic to a different web farm server while one is restarting. Yes, the cache would need to be regenerated on the server after the restart, but externally cached data (ex: Redis) needs to be retrieved via HTTP request in this scenario anyway! You are effectively trading a SQL request for an HTTP request, but the point of a cache is to make these kinds of trades since most requests will be handled by cached data.

So, IProgressiveCache already supports L1 and L2 caching without Xperience developers needing to think about how it works.

What is the benefit of a Redis cache vs in-memory cache synchronized through SQL? Scalability - you can scale Redis independent of your SQL Server. But this is only a benefit if you need that scalability.

I have a custom solution for tagging cache entries in place already ... the only problem is HybridCache is still in preview

I have a few questions:

  1. What problem are you trying to solve by creating a custom cache solution?
  2. Is this something your solution requires?
  3. Are you sure that IProgressiveCache won't work?
  4. Does the custom cache solution truly help your client?
  5. is using an "in preview" technology worth it given the engineering cost?

Umbraco adopted HybridCache because their existing caching solution was old and didn't handle their customer's needs - they couldn't really continue to use what they had.

If we see a benefit in switching to HybridCache in the future (I had a discussion with one of our architects about it in June 2024) then we'll probably do that in a way that is transparent to developers to avoid breaking changes, but as of now we just don't see the need because we already designed a good API and caching architecture.

FusionCache

Yep, it's an awesome library, but the same argument applies here - IProgressiveCache already does everything needed in most Xperience solutions with 0 extra engineering cost.

I would recommend sticking with Xperience's caching architecture for now.

2025/01/08 9:08 AM

Thanks so much for weighing in on this topic, there are definitely some points to take away and digest :)

Re; IProgressiveCache , correct me if I'm wrong but I believe it's not a truly distributed caching solution in the sense that cache items are being stored out of process. My understanding is that it still stores its items in the memory of each running instance, with web farms (SQL tables under the hood) being used to synchronise changes between them. I may be wrong on that though, I have not delved too deeply into the internals.

Does it store cache items in memory and in SQL, so that it doesn't have to regenerate the cache on a cold start/restart or when provisioning additional nodes?

If it does only store items in memory then I believe it can only be considered an L1 cache.

  1. What problem are you trying to solve by creating a custom cache solution?

    1. To give a bit of background context, the project in question will be running in a kubernetes cluster and uses a horizontal pod autoscaler which can spin up/down new pods in response to increased requests or memory. Due to these scalability demands, we will need to maintain a high degree of availability and fast response times.
    2. Using this tiered cache approach gives us the benefits of a fast low latency in-memory cache, with the resiliency of a distributed cache store like redis, making it that much quicker to spin up new pods
  2. Is this something your solution requires?

    1. This hasn't been strictly mandated by the client and I do believe we can get by using the out of the box IProgressiveCache although I can't ignore the benefits of a hybrid cache solution
  3. Are you sure that IProgressiveCache won't work?

    1. I believe it will be a workable solution, although for scaling out, it will not result in faster uptime vs a hybrid cache
  4. Does the custom cache solution truly help your client?

    1. In the grand scheme of things, it is unlikely to be something the client would notice. It's sometimes difficult to not fall into the trap of wanting to use the 'new shiny' things from a Dev perspective and whilst I can justify it myself, knowing the technical implications pros/cons etc... could we justify the added complexity, and possible tech debt, to the client is another question :)
  5. is using an "in preview" technology worth it given the engineering cost?

    1. This is great question and is something I've raised internally. With HybridCache in particular, we don't know when it will be out of preview and it's missing tag support so I feel this API in particular is out of the question. FusionCache is also in preview, but is expected to be GA very soon: Release v2.0.0-preview-4 · ZiggyCreatures/FusionCache · GitHub
    2. I think if we were going to choose one of these, it will need to be FusionCache due to the tagging feature and the cache backplane

I've been playing around with FusionCache since I opening this thread, and have a rough working solution with these features:

  • Cache invalidation for all content types via cache dependencies (tags in FusionCache)

    • Pages
    • Content items
    • Media files
    • Headless items
    • Settings key items
    • General object infos (via a custom 'provider' pattern)
  • A custom <cache> tag helper which is backed by hybrid cache, again with support for cache invalidation via dependencies

  • Custom output cache policy/store backed by FusionCache which also allows for setting dependencies

I think even if we decide to not go down this route, the code may be useful to others, even if it's just to experiment with.

If you or @Trevor Fayas would like to take a look I can pull it out of the project and into a public repo? :)

To answer this question, you have to login first.