diff --git a/src/Umbraco.Core/Services/IdkMap.cs b/src/Umbraco.Core/Services/IdkMap.cs index 65d03d6e2d..668e3f4b09 100644 --- a/src/Umbraco.Core/Services/IdkMap.cs +++ b/src/Umbraco.Core/Services/IdkMap.cs @@ -3,8 +3,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; using Umbraco.Core.Models; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Scoping; namespace Umbraco.Core.Services @@ -30,38 +28,22 @@ namespace Umbraco.Core.Services // to each other, then the id will never map to another guid, and the guid will never map // to another id // - // - LeeK's solution in 7.7 was to look for the id/guid in the content cache "on demand" via - // XPath, which is probably fast enough but cannot deal with media ids + it maintains a - // separate, duplicate cache - // see https://github.com/umbraco/Umbraco-CMS/pull/2398 - // - // - Andy's solution in a package was to prefetch all by sql; it cannot prefecth reserved ids - // as we don't know the corresponding object type, but that's not a big issue - but then we - // have a full database query on startup - // see https://github.com/AndyButland/UmbracoUdiToIdCache - // - // - the original IdkMap implementation that was used by services, did a database lookup on - // each cache miss, which is fine enough for services, but would be really slow at content - // cache level - // // - cache is cleared by MediaCacheRefresher, UnpublishedPageCacheRefresher, and other // refreshers - because id/guid map is unique, we only clear to avoid leaking memory, 'cos // we don't risk caching obsolete values - and only when actually deleting // - // so... - // - // - there's a single caching point, and it's idkMap - // - there are no "helper methods" - the published content cache itself knows about Guids - // - when the published content cache is instanciated, it populates the idkMap with what it knows - // and it registers a way for the idkMap to look for id/keys in the published content cache // - we do NOT prefetch anything from database + // + // - NuCache maintains its own id/guid map for content & media items + // it does *not* populate the idk map, because it directly uses its own map + // still, it provides mappers so that the idk map can benefit from them + // which means there will be some double-caching at some point ?? + // // - when a request comes in: - // the published content cache uses the idkMap to map id/key // if the idkMap already knows about the map, it returns the value - // else it tries the published cache via XPath + // else it tries the published cache via mappers // else it hits the database - private readonly ConcurrentDictionary id2key, Func key2id)> _dictionary = new ConcurrentDictionary id2key, Func key2id)>(); diff --git a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs index 16ff6cb46c..474f15d29b 100644 --- a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs @@ -85,6 +85,7 @@ namespace Umbraco.Tests.Scoping runtimeStateMock.Object, ServiceContext, contentTypeFactory, + null, publishedSnapshotAccessor, Logger, ScopeProvider, diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs index 43fdb342c7..a9bb379cdc 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs @@ -1,12 +1,16 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Xml.XPath; using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration; +using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Services; using Umbraco.Core.Xml; using Umbraco.Core.Xml.XPath; using Umbraco.Web.PublishedCache.NuCache.Navigable; diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs index 6915baf60a..74613509ba 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs @@ -23,6 +23,7 @@ namespace Umbraco.Web.PublishedCache.NuCache private readonly ConcurrentDictionary> _contentRootNodes; private readonly ConcurrentDictionary> _contentTypesById; private readonly ConcurrentDictionary> _contentTypesByAlias; + private readonly ConcurrentDictionary _xmap; private readonly ILogger _logger; private BPlusTree _localDb; @@ -52,6 +53,7 @@ namespace Umbraco.Web.PublishedCache.NuCache _contentRootNodes = new ConcurrentDictionary>(); _contentTypesById = new ConcurrentDictionary>(); _contentTypesByAlias = new ConcurrentDictionary>(StringComparer.InvariantCultureIgnoreCase); + _xmap = new ConcurrentDictionary(); _genRefRefs = new ConcurrentQueue(); _genRefRef = null; // no initial gen exists @@ -477,6 +479,8 @@ namespace Umbraco.Web.PublishedCache.NuCache RemoveFromParentLocked(existing); AddToParentLocked(kit.Node); } + + _xmap[kit.Node.Uid] = kit.Node.Id; } finally { @@ -504,6 +508,8 @@ namespace Umbraco.Web.PublishedCache.NuCache SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); if (_localDb != null) RegisterChange(kit.Node.Id, kit); AddToParentLocked(kit.Node); + + _xmap[kit.Node.Uid] = kit.Node.Id; } } finally @@ -537,6 +543,8 @@ namespace Umbraco.Web.PublishedCache.NuCache SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); if (_localDb != null) RegisterChange(kit.Node.Id, kit); AddToParentLocked(kit.Node); + + _xmap[kit.Node.Uid] = kit.Node.Id; } } finally @@ -576,8 +584,7 @@ namespace Umbraco.Web.PublishedCache.NuCache private void ClearBranchLocked(int id) { - LinkedNode link; - _contentNodes.TryGetValue(id, out link); + _contentNodes.TryGetValue(id, out var link); if (link?.Value == null) return; ClearBranchLocked(link.Value); @@ -588,6 +595,8 @@ namespace Umbraco.Web.PublishedCache.NuCache SetValueLocked(_contentNodes, content.Id, null); if (_localDb != null) RegisterChange(content.Id, ContentNodeKit.Null); + _xmap.TryRemove(content.Uid, out _); + foreach (var childId in content.ChildContentIds) { if (_contentNodes.TryGetValue(childId, out LinkedNode link) == false || link.Value == null) continue; @@ -597,8 +606,7 @@ namespace Umbraco.Web.PublishedCache.NuCache private LinkedNode GetParentLink(ContentNode content) { - LinkedNode link; - _contentNodes.TryGetValue(content.ParentContentId, out link); // else null + _contentNodes.TryGetValue(content.ParentContentId, out var link); // else null //if (link == null || link.Value == null) // throw new Exception("Panic: parent not found."); return link; @@ -709,6 +717,13 @@ namespace Umbraco.Web.PublishedCache.NuCache return GetValue(_contentNodes, id, gen); } + public ContentNode Get(Guid uid, long gen) + { + return _xmap.TryGetValue(uid, out var id) + ? GetValue(_contentNodes, id, gen) + : null; + } + public IEnumerable GetAtRoot(long gen) { // look ma, no lock! @@ -1099,8 +1114,7 @@ namespace Umbraco.Web.PublishedCache.NuCache { if (_gen < 0) throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/); - // fixme - optimize with an index - getAll/iterating is expensive - return _store.GetAll(_gen).FirstOrDefault(x => x.Uid == id); + return _store.Get(id, _gen); } public IEnumerable GetAtRoot() @@ -1110,6 +1124,13 @@ namespace Umbraco.Web.PublishedCache.NuCache return _store.GetAtRoot(_gen); } + public IEnumerable GetAll() + { + if (_gen < 0) + throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/); + return _store.GetAll(_gen); + } + public PublishedContentType GetContentType(int id) { if (_gen < 0) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/NuCacheComponent.cs b/src/Umbraco.Web/PublishedCache/NuCache/NuCacheComponent.cs index 2be36b2903..3482a6cf2c 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/NuCacheComponent.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/NuCacheComponent.cs @@ -16,18 +16,9 @@ namespace Umbraco.Web.PublishedCache.NuCache base.Compose(composition); // register the NuCache published snapshot service - composition.SetPublishedSnapshotService(factory => new PublishedSnapshotService( - new PublishedSnapshotService.Options(), - factory.GetInstance(), - factory.GetInstance(), - factory.GetInstance(), - factory.GetInstance(), - factory.GetInstance(), - factory.GetInstance(), - factory.GetInstance(), - factory.GetInstance(), - factory.GetInstance(), - factory.GetInstance())); + // must register default options, required in the service ctor + composition.Container.Register(factory => new PublishedSnapshotService.Options()); + composition.SetPublishedSnapshotService(); // add the NuCache health check (hidden from type finder) // todo - no NuCache health check yet diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index df264ac51c..2ffd21425e 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -77,7 +77,7 @@ namespace Umbraco.Web.PublishedCache.NuCache //private static int _singletonCheck; public PublishedSnapshotService(Options options, MainDom mainDom, IRuntimeState runtime, - ServiceContext serviceContext, IPublishedContentTypeFactory publishedContentTypeFactory, + ServiceContext serviceContext, IPublishedContentTypeFactory publishedContentTypeFactory, IdkMap idkMap, IPublishedSnapshotAccessor publishedSnapshotAccessor, ILogger logger, IScopeProvider scopeProvider, IDocumentRepository documentRepository, IMediaRepository mediaRepository, IMemberRepository memberRepository) : base(publishedSnapshotAccessor) @@ -148,6 +148,12 @@ namespace Umbraco.Web.PublishedCache.NuCache _domainStore = new SnapDictionary(); LoadCaches(); + + if (idkMap != null) + { + idkMap.SetMapper(UmbracoObjectTypes.Document, id => _contentStore.LiveSnapshot.Get(id).Uid, uid => _contentStore.LiveSnapshot.Get(uid).Id); + idkMap.SetMapper(UmbracoObjectTypes.Media, id => _mediaStore.LiveSnapshot.Get(id).Uid, uid => _mediaStore.LiveSnapshot.Get(uid).Id); + } } private void LoadCaches()