Merge remote-tracking branch 'origin/v8/dev' into v8/feature/nucache-perf

# Conflicts:
#	src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs
#	src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs
This commit is contained in:
Shannon
2020-08-14 00:49:03 +10:00
234 changed files with 29390 additions and 4157 deletions

View File

@@ -13,7 +13,7 @@ using Umbraco.Web.PublishedCache.NuCache.Navigable;
namespace Umbraco.Web.PublishedCache.NuCache
{
internal class ContentCache : PublishedCacheBase, IPublishedContentCache, INavigableData, IDisposable
internal class ContentCache : PublishedCacheBase, IPublishedContentCache2, INavigableData, IDisposable
{
private readonly ContentStore.Snapshot _snapshot;
private readonly IAppCache _snapshotCache;
@@ -384,15 +384,11 @@ namespace Umbraco.Web.PublishedCache.NuCache
#region Content types
public override IPublishedContentType GetContentType(int id)
{
return _snapshot.GetContentType(id);
}
public override IPublishedContentType GetContentType(int id) => _snapshot.GetContentType(id);
public override IPublishedContentType GetContentType(string alias)
{
return _snapshot.GetContentType(alias);
}
public override IPublishedContentType GetContentType(string alias) => _snapshot.GetContentType(alias);
public override IPublishedContentType GetContentType(Guid key) => _snapshot.GetContentType(key);
#endregion

View File

@@ -37,9 +37,14 @@ namespace Umbraco.Web.PublishedCache.NuCache
private readonly IVariationContextAccessor _variationContextAccessor;
private readonly ConcurrentDictionary<int, LinkedNode<ContentNode>> _contentNodes;
private LinkedNode<ContentNode> _root;
private readonly ConcurrentDictionary<int, LinkedNode<IPublishedContentType>> _contentTypesById;
// We must keep separate dictionaries for by id and by alias because we track these in snapshot/layers
// and it is possible that the alias of a content type can be different for the same id in another layer
// whereas the GUID -> INT cross reference can never be different
private readonly ConcurrentDictionary<int, LinkedNode<IPublishedContentType>> _contentTypesById;
private readonly ConcurrentDictionary<string, LinkedNode<IPublishedContentType>> _contentTypesByAlias;
private readonly ConcurrentDictionary<Guid, int> _xmap;
private readonly ConcurrentDictionary<Guid, int> _contentTypeKeyToIdMap;
private readonly ConcurrentDictionary<Guid, int> _contentKeyToIdMap;
private readonly ILogger _logger;
private BPlusTree<int, ContentNodeKit> _localDb;
@@ -73,7 +78,8 @@ namespace Umbraco.Web.PublishedCache.NuCache
_root = new LinkedNode<ContentNode>(new ContentNode(), 0);
_contentTypesById = new ConcurrentDictionary<int, LinkedNode<IPublishedContentType>>();
_contentTypesByAlias = new ConcurrentDictionary<string, LinkedNode<IPublishedContentType>>(StringComparer.InvariantCultureIgnoreCase);
_xmap = new ConcurrentDictionary<Guid, int>();
_contentTypeKeyToIdMap = new ConcurrentDictionary<Guid, int>();
_contentKeyToIdMap = new ConcurrentDictionary<Guid, int>();
_genObjs = new ConcurrentQueue<GenObj>();
_genObj = null; // no initial gen exists
@@ -136,7 +142,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
Monitor.Enter(_wlocko, ref lockInfo.Taken);
lock(_rlocko)
lock (_rlocko)
{
// see SnapDictionary
try { }
@@ -152,7 +158,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
_nextGen = true;
}
}
}
}
}
private void Release(WriteLockInfo lockInfo, bool commit = true)
@@ -291,8 +297,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
foreach (var type in types)
{
SetValueLocked(_contentTypesById, type.Id, type);
SetValueLocked(_contentTypesByAlias, type.Alias, type);
SetContentTypeLocked(type);
}
}
@@ -318,8 +323,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
foreach (var type in index.Values)
{
SetValueLocked(_contentTypesById, type.Id, type);
SetValueLocked(_contentTypesByAlias, type.Alias, type);
SetContentTypeLocked(type);
}
foreach (var link in _contentNodes.Values)
@@ -354,8 +358,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
// set all new content types
foreach (var type in types)
{
SetValueLocked(_contentTypesById, type.Id, type);
SetValueLocked(_contentTypesByAlias, type.Alias, type);
SetContentTypeLocked(type);
}
// beware! at that point the cache is inconsistent,
@@ -419,8 +422,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
// perform update of refreshed content types
foreach (var type in refreshedTypesA)
{
SetValueLocked(_contentTypesById, type.Id, type);
SetValueLocked(_contentTypesByAlias, type.Alias, type);
SetContentTypeLocked(type);
}
// perform update of content with refreshed content type - from the kits
@@ -638,7 +640,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
kit.Node.PreviousSiblingContentId = existing.PreviousSiblingContentId;
}
_xmap[kit.Node.Uid] = kit.Node.Id;
_contentKeyToIdMap[kit.Node.Uid] = kit.Node.Id;
return true;
}
@@ -734,7 +736,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
// this node becomes the previous node
previousNode = thisNode;
_xmap[kit.Node.Uid] = kit.Node.Id;
_contentKeyToIdMap[kit.Node.Uid] = kit.Node.Id;
}
return ok;
@@ -757,7 +759,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
EnsureLocked();
var ok = true;
ClearLocked(_contentNodes);
ClearRootLocked();
@@ -778,7 +780,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
if (_localDb != null) RegisterChange(kit.Node.Id, kit);
AddTreeNodeLocked(kit.Node, parent);
_xmap[kit.Node.Uid] = kit.Node.Id;
_contentKeyToIdMap[kit.Node.Uid] = kit.Node.Id;
}
return ok;
@@ -807,7 +809,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
EnsureLocked();
var ok = true;
// get existing
_contentNodes.TryGetValue(rootContentId, out var link);
var existing = link?.Value;
@@ -833,7 +835,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
if (_localDb != null) RegisterChange(kit.Node.Id, kit);
AddTreeNodeLocked(kit.Node, parent);
_xmap[kit.Node.Uid] = kit.Node.Id;
_contentKeyToIdMap[kit.Node.Uid] = kit.Node.Id;
}
return ok;
@@ -885,11 +887,11 @@ namespace Umbraco.Web.PublishedCache.NuCache
// This should never be null, all code that calls this method is null checking but we've seen
// issues of null ref exceptions in issue reports so we'll double check here
if (content == null) throw new ArgumentNullException(nameof(content));
SetValueLocked(_contentNodes, content.Id, null);
if (_localDb != null) RegisterChange(content.Id, ContentNodeKit.Null);
_xmap.TryRemove(content.Uid, out _);
_contentKeyToIdMap.TryRemove(content.Uid, out _);
var id = content.FirstChildContentId;
while (id > 0)
@@ -913,10 +915,10 @@ namespace Umbraco.Web.PublishedCache.NuCache
{
if (_contentNodes.TryGetValue(id, out var link))
{
link = GetLinkedNodeGen(link, gen);
link = GetLinkedNodeGen(link, gen);
if (link != null && link.Value != null)
return link;
}
}
throw new PanicException($"failed to get {description} with id={id}");
}
@@ -929,13 +931,13 @@ namespace Umbraco.Web.PublishedCache.NuCache
{
if (content.ParentContentId < 0)
{
var root = GetLinkedNodeGen(_root, gen);
var root = GetLinkedNodeGen(_root, gen);
return root;
}
if (_contentNodes.TryGetValue(content.ParentContentId, out var link))
link = GetLinkedNodeGen(link, gen);
return link;
}
@@ -1154,6 +1156,15 @@ namespace Umbraco.Web.PublishedCache.NuCache
}
}
private void SetContentTypeLocked(IPublishedContentType type)
{
SetValueLocked(_contentTypesById, type.Id, type);
SetValueLocked(_contentTypesByAlias, type.Alias, type);
// ensure the key/id map is accurate
if (type.TryGetKey(out var key))
_contentTypeKeyToIdMap[key] = type.Id;
}
// set a node (just the node, not the tree)
private void SetValueLocked<TKey, TValue>(ConcurrentDictionary<TKey, LinkedNode<TValue>> dict, TKey key, TValue value)
where TValue : class
@@ -1211,14 +1222,14 @@ namespace Umbraco.Web.PublishedCache.NuCache
public ContentNode Get(Guid uid, long gen)
{
return _xmap.TryGetValue(uid, out var id)
return _contentKeyToIdMap.TryGetValue(uid, out var id)
? GetValue(_contentNodes, id, gen)
: null;
}
public IEnumerable<ContentNode> GetAtRoot(long gen)
{
var root = GetLinkedNodeGen(_root, gen);
var root = GetLinkedNodeGen(_root, gen);
if (root == null)
yield break;
@@ -1274,13 +1285,20 @@ namespace Umbraco.Web.PublishedCache.NuCache
return GetValue(_contentTypesByAlias, alias, gen);
}
public IPublishedContentType GetContentType(Guid key, long gen)
{
if (!_contentTypeKeyToIdMap.TryGetValue(key, out var id))
return null;
return GetContentType(id, gen);
}
#endregion
#region Snapshots
public Snapshot CreateSnapshot()
{
lock(_rlocko)
lock (_rlocko)
{
// if no next generation is required, and we already have one,
// use it and create a new snapshot
@@ -1606,6 +1624,13 @@ namespace Umbraco.Web.PublishedCache.NuCache
return _store.GetContentType(alias, _gen);
}
public IPublishedContentType GetContentType(Guid key)
{
if (_gen < 0)
throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/);
return _store.GetContentType(key, _gen);
}
// this code is here just so you don't try to implement it
// the only way we can iterate over "all" without locking the entire cache forever
// is by shallow cloning the cache, which is quite expensive, so we should probably not do it,

View File

@@ -218,7 +218,7 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource
if (dto.EditData == null && dto.EditDataRaw == null)
{
if (Debugger.IsAttached)
throw new Exception("Missing cmsContentNu edited content for node " + dto.Id + ", consider rebuilding.");
throw new InvalidOperationException("Missing cmsContentNu edited content for node " + dto.Id + ", consider rebuilding.");
Current.Logger.Warn<DatabaseDataSource>("Missing cmsContentNu edited content for node {NodeId}, consider rebuilding.", dto.Id);
}
else
@@ -247,7 +247,7 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource
if (dto.PubData == null && dto.PubDataRaw == null)
{
if (Debugger.IsAttached)
throw new Exception("Missing cmsContentNu published content for node " + dto.Id + ", consider rebuilding.");
throw new InvalidOperationException("Missing cmsContentNu published content for node " + dto.Id + ", consider rebuilding.");
Current.Logger.Warn<DatabaseDataSource>("Missing cmsContentNu published content for node {NodeId}, consider rebuilding.", dto.Id);
}
else
@@ -288,7 +288,7 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource
private ContentNodeKit CreateMediaNodeKit(ContentSourceDto dto)
{
if (dto.EditData == null && dto.EditDataRaw == null)
throw new Exception("No data for media " + dto.Id);
throw new InvalidOperationException("No data for media " + dto.Id);
var nested = _contentNestedDataSerializer is IContentNestedDataByteSerializer byteSerializer
? byteSerializer.DeserializeBytes(dto.EditDataRaw)

View File

@@ -11,7 +11,7 @@ using Umbraco.Web.PublishedCache.NuCache.Navigable;
namespace Umbraco.Web.PublishedCache.NuCache
{
internal class MediaCache : PublishedCacheBase, IPublishedMediaCache, INavigableData, IDisposable
internal class MediaCache : PublishedCacheBase, IPublishedMediaCache2, INavigableData, IDisposable
{
private readonly ContentStore.Snapshot _snapshot;
private readonly IVariationContextAccessor _variationContextAccessor;
@@ -155,15 +155,11 @@ namespace Umbraco.Web.PublishedCache.NuCache
#region Content types
public override IPublishedContentType GetContentType(int id)
{
return _snapshot.GetContentType(id);
}
public override IPublishedContentType GetContentType(int id) => _snapshot.GetContentType(id);
public override IPublishedContentType GetContentType(string alias)
{
return _snapshot.GetContentType(alias);
}
public override IPublishedContentType GetContentType(string alias) => _snapshot.GetContentType(alias);
public override IPublishedContentType GetContentType(Guid key) => _snapshot.GetContentType(key);
#endregion