Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V15: Hybrid Caching #16938

Merged
merged 174 commits into from
Sep 9, 2024
Merged

V15: Hybrid Caching #16938

merged 174 commits into from
Sep 9, 2024

Conversation

Zeegaan
Copy link
Member

@Zeegaan Zeegaan commented Aug 21, 2024

Hybrid Caching

This PR introduces new hybrid cache implementations of the cache interfaces,
If you want to learn more about what hybrid caching is, read: https://learn.microsoft.com/en-us/aspnet/core/performance/caching/hybrid?view=aspnetcore-9.0

Second level cache

For how to implement a secondary cache with this, see: https://learn.microsoft.com/en-us/aspnet/core/performance/caching/distributed?view=aspnetcore-9.0#distributed-redis-cache

Additions to the cache interfaces

  • We have the original cache interfaces in the Core project:
  • IPublishedMemberCache
  • IPublishedMediaCache
  • IPublishedContentCache

And with this PR, we add some async methods to these. This way we break less code, but we can also modernize with some async methods.

What happened to PublishedSnapshot?

With nucache, we had a concept of a PublishedSnapshot, this was a cache snapshot, so that you would have the same entities during a request, so that if anything got changed /deleted during your request, that would not interfere / cause errors.
This is no longer a thing with the Hybrid Cache, as it has no concept of cache snapshots.
This also means that IPublishedSnapshot, IPublishedSnapshotAccessor & SnapshotCache is all obsolete.

And even the PropertyCacheLevel.Snapshot is also obsolete.

The Hybrid Cache project

Cache implementations

The cache implementations instead of having tons of logic inside them, now calls the cache service instead to get out items.

Note that theres a lot of not implemented methods here, as they are all obsolete, and no longer part of this caching.
Thus they will have to be removed when we remove NuCache.

What Caches do we have now then?

  • Content Cache
  • Media Cache
  • Member Cache
  • Elements Cache - This is used for all properties that have PropertyCacheLevel.Elements
  • Domain Cache - Caching of domains

The cache services

These cache services that the caches use, now use the new snazzy dotnet 9 feature HybridCache.

Cache service responsibility includes:

  • Cache responsibilities such as:
    • Getting entities from the hybrid cache ( if not in the cache, use the nucache content repository to fetch from database)
    • Refreshing a node in the cache (aka just deleting it when we know a node has updated)
    • Seeding the database with a given list of contenttype ids
  • Mapping the content cache node you get from the cache, over to IPublishedContent
  • Mapping keys to Ids and vice versa. We need both of them, as the given key is used to form the cache key, and the id is used to get the entity from the database.

PublishedContent changes

PublishedContent no longer has any Tree logic, this means, we no longer have children / parent.

PublishedProperty

The big change to PublishedProperty is that we no longer use snapshots, so in the GetCacheValues method, we can avoid using snapshot, and instead just fall back to the Element behavior.

Seeding

So for NuCache, it would load everything into memory, this is really performant when everything is finally loaded in. But causes issues like :

  • Long boot times on larger sites
  • Memory issues as it would take up too much memory
  • Corrupted cache

So we have strayed from this approach with this. We do however recognize that sometimes you might want to have some pages always in memory (stuff like the front page), this is where the seeding feature comes in.

You can now instead deine a list of content type ids in appsettings.json, which in turn will load every document that uses the given document types, into the cache, and will never evict them.

This new setting can be set like:

 "Umbraco": {
    "CMS": {
      "Cache": {
         "ContentTypeIds": [1068, 1069]
       },

Features still missing

  • When updating a seeded entity, it is no longer permanently in the cache, we need to figure out a way to re-get these on deletion and have the entry options set to a year again.

How to test

Note: We can't do a full proper tests before we can replace NuCache. And we can only replace NuCache once this and #16818 is merged 😁
For now we can instead test this with trying out some distributed cache implementation, and asserting entities actually are in the cache, both after getting the entity, but also seeding.

  • Create a document type named "RootSeed" (note down the integer id of this content type), with allow as root set to true.
  • Create another document type "Root" with allow as root set to true
  • Create one piece of content with each. please note down the id of the content you create with "Root"
  • Stop the site
  • Go to your appsettings, and put in the cache setting to enable seeding:
 "Umbraco": {
    "CMS": {
      "Cache": {
         "ContentTypeKeys": [Put Key of "RootSeed" here instead.]
       },

We will now use the SqlServer distributed cache, you can directly follow the documentation here: https://learn.microsoft.com/en-us/aspnet/core/performance/caching/distributed?view=aspnetcore-8.0#distributed-sql-server-cache
It would also be nice to tests this with other implementations of this, like Redis.

  • First we will have to create the SQL database, i used (localdb)\MSSQLLocalDB and created a database called DistCache
  • Creating the cache with this command: dotnet sql-cache create "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=DistCache;Integrated Security=True;" dbo TestCache
  • Now install the NuGet package: Microsoft.Extensions.Caching.SqlServer to the Web.UI project
  • Now that the nuget package is installed, we need to register the Distributed cache in program.cs, paste this on line 2:
builder.Services.AddDistributedSqlServerCache(options =>
{
    options.ConnectionString = "Data Source=(localdb)\\MSSQLLocalDB;Database=DistCache;Integrated Security=True;";
    options.SchemaName = "dbo";
    options.TableName = "TestCache";
});
  • Lastly to trigger the caching of the node you created with "root", we need a controller endpoint we can call (this could also be a notificationhandler or wherever you want to trigger this code)
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;

namespace Umbraco.Cms.Web.UI;

[ApiController]
[Route("[controller]")]
public class MyController : ControllerBase
{
    private readonly IPublishedContentCache _publishedContentCache;

    public MyController(IPublishedContentCache publishedContentCache)
    {
        _publishedContentCache = publishedContentCache;
    }

    [HttpGet]
    public ActionResult Get()
    {
        IPublishedContent? content = _publishedContentCache.GetById("PUT ID OF CONTENT CREATED WITH ROOT HERE");
        return Ok();
    }
}
  • Now that the setup is done, run your umbraco again
  • After umbraco has started up, go to the database and look at the dbo.TestCache table, this should now contain 1 entry, and that should be your content you created with the "RootSeed" contentype,
  • If you then call your controller (using postman, send a request to https://localhost:44339/My)
  • You should now see 2 entries in your database table.

@nikolajlauridsen nikolajlauridsen merged commit 2704d4a into v15/dev Sep 9, 2024
14 of 15 checks passed
@nikolajlauridsen nikolajlauridsen deleted the v15/feature/hybrid-caching branch September 9, 2024 15:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants