Fixes empty recycle bin performance with indexing

Currently when the recycle bin is empty, it is going to individually delete each item from the index. This is going to cause tons of allocations in Umbraco for DeferedDeleteIndex objects for each item and then down within Examine is going to process each one individually instead of just doing it in bulk. There will be a lot of allocations made there too along with a bunch of extra and unecessary threads.
This commit is contained in:
Shannon
2021-09-09 16:04:18 -06:00
parent dc0994a429
commit 71448eafe6
4 changed files with 109 additions and 10 deletions

View File

@@ -118,6 +118,20 @@ namespace Umbraco.Cms.Infrastructure.Examine
}
}
/// <inheritdoc />
public void DeleteIndexForEntities(IReadOnlyCollection<int> entityIds, bool keepIfUnpublished)
{
var actions = DeferedActions.Get(_scopeProvider);
if (actions != null)
{
actions.Add(new DeferedDeleteIndex(this, entityIds, keepIfUnpublished));
}
else
{
DeferedDeleteIndex.Execute(this, entityIds, keepIfUnpublished);
}
}
/// <inheritdoc />
public void ReIndexForContent(IContent sender, bool isPublished)
{
@@ -371,6 +385,7 @@ namespace Umbraco.Cms.Infrastructure.Examine
{
private readonly ExamineUmbracoIndexingHandler _examineUmbracoIndexingHandler;
private readonly int _id;
private readonly IReadOnlyCollection<int> _ids;
private readonly bool _keepIfUnpublished;
public DeferedDeleteIndex(ExamineUmbracoIndexingHandler examineUmbracoIndexingHandler, int id, bool keepIfUnpublished)
@@ -380,16 +395,42 @@ namespace Umbraco.Cms.Infrastructure.Examine
_keepIfUnpublished = keepIfUnpublished;
}
public override void Execute() => Execute(_examineUmbracoIndexingHandler, _id, _keepIfUnpublished);
public DeferedDeleteIndex(ExamineUmbracoIndexingHandler examineUmbracoIndexingHandler, IReadOnlyCollection<int> ids, bool keepIfUnpublished)
{
_examineUmbracoIndexingHandler = examineUmbracoIndexingHandler;
_ids = ids;
_keepIfUnpublished = keepIfUnpublished;
}
public override void Execute()
{
if (_ids is null)
{
Execute(_examineUmbracoIndexingHandler, _id, _keepIfUnpublished);
}
else
{
Execute(_examineUmbracoIndexingHandler, _ids, _keepIfUnpublished);
}
}
public static void Execute(ExamineUmbracoIndexingHandler examineUmbracoIndexingHandler, int id, bool keepIfUnpublished)
{
var strId = id.ToString(CultureInfo.InvariantCulture);
foreach (var index in examineUmbracoIndexingHandler._examineManager.Indexes.OfType<IUmbracoIndex>()
.Where(x => x.PublishedValuesOnly || !keepIfUnpublished)
.Where(x => x.EnableDefaultEventHandler))
{
index.DeleteFromIndex(strId);
index.DeleteFromIndex(id.ToString(CultureInfo.InvariantCulture));
}
}
public static void Execute(ExamineUmbracoIndexingHandler examineUmbracoIndexingHandler, IReadOnlyCollection<int> ids, bool keepIfUnpublished)
{
foreach (var index in examineUmbracoIndexingHandler._examineManager.Indexes.OfType<IUmbracoIndex>()
.Where(x => x.PublishedValuesOnly || !keepIfUnpublished)
.Where(x => x.EnableDefaultEventHandler))
{
index.DeleteFromIndex(ids.Select(x => x.ToString(CultureInfo.InvariantCulture)));
}
}
}

View File

@@ -25,7 +25,7 @@ namespace Umbraco.Cms.Infrastructure.Search
void DeleteDocumentsForContentTypes(IReadOnlyCollection<int> removedContentTypes);
/// <summary>
/// Remove items from an index
/// Remove an item from an index
/// </summary>
/// <param name="entityId"></param>
/// <param name="keepIfUnpublished">
@@ -33,5 +33,15 @@ namespace Umbraco.Cms.Infrastructure.Search
/// If false it will delete this from all indexes regardless.
/// </param>
void DeleteIndexForEntity(int entityId, bool keepIfUnpublished);
/// <summary>
/// Remove items from an index
/// </summary>
/// <param name="entityIds"></param>
/// <param name="keepIfUnpublished">
/// If true, indicates that we will only delete this item from indexes that don't support unpublished content.
/// If false it will delete this from all indexes regardless.
/// </param>
void DeleteIndexForEntities(IReadOnlyCollection<int> entityIds, bool keepIfUnpublished);
}
}

View File

@@ -44,13 +44,21 @@ namespace Umbraco.Cms.Infrastructure.Search
throw new NotSupportedException();
}
// Used to track permanent deletions so we can bulk delete from the index
// when needed. For example, when emptying the recycle bin, else it will
// individually update the index which will be much slower.
HashSet<int> deleteBatch = null;
foreach (var payload in (ContentCacheRefresher.JsonPayload[])args.MessageObject)
{
if (payload.ChangeTypes.HasType(TreeChangeTypes.Remove))
{
// delete content entirely (with descendants)
// false: remove entirely from all indexes
_umbracoIndexingHandler.DeleteIndexForEntity(payload.Id, false);
if (deleteBatch == null)
{
deleteBatch = new HashSet<int>();
}
deleteBatch.Add(payload.Id);
}
else if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshAll))
{
@@ -62,6 +70,15 @@ namespace Umbraco.Cms.Infrastructure.Search
}
else // RefreshNode or RefreshBranch (maybe trashed)
{
if (deleteBatch != null && deleteBatch.Contains(payload.Id))
{
// the same node has already been deleted, to ensure ordering is
// handled, we'll need to execute all queued deleted items now
// and reset the deleted items list.
_umbracoIndexingHandler.DeleteIndexForEntities(deleteBatch, false);
deleteBatch = null;
}
// don't try to be too clever - refresh entirely
// there has to be race conditions in there ;-(
@@ -132,6 +149,12 @@ namespace Umbraco.Cms.Infrastructure.Search
//
// BUT ... pretty sure it is! see test "Index_Delete_Index_Item_Ensure_Heirarchy_Removed"
}
if (deleteBatch != null)
{
// process the delete batch
_umbracoIndexingHandler.DeleteIndexForEntities(deleteBatch, false);
}
}
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Notifications;
@@ -37,12 +38,21 @@ namespace Umbraco.Cms.Infrastructure.Search
throw new NotSupportedException();
}
// Used to track permanent deletions so we can bulk delete from the index
// when needed. For example, when emptying the recycle bin, else it will
// individually update the index which will be much slower.
HashSet<int> deleteBatch = null;
foreach (var payload in (MediaCacheRefresher.JsonPayload[])args.MessageObject)
{
if (payload.ChangeTypes.HasType(TreeChangeTypes.Remove))
{
// remove from *all* indexes
_umbracoIndexingHandler.DeleteIndexForEntity(payload.Id, false);
if (deleteBatch == null)
{
deleteBatch = new HashSet<int>();
}
deleteBatch.Add(payload.Id);
}
else if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshAll))
{
@@ -52,6 +62,15 @@ namespace Umbraco.Cms.Infrastructure.Search
}
else // RefreshNode or RefreshBranch (maybe trashed)
{
if (deleteBatch != null && deleteBatch.Contains(payload.Id))
{
// the same node has already been deleted, to ensure ordering is
// handled, we'll need to execute all queued deleted items now
// and reset the deleted items list.
_umbracoIndexingHandler.DeleteIndexForEntities(deleteBatch, false);
deleteBatch = null;
}
var media = _mediaService.GetById(payload.Id);
if (media == null)
{
@@ -83,7 +102,13 @@ namespace Umbraco.Cms.Infrastructure.Search
}
}
}
}
}
}
if (deleteBatch != null)
{
// process the delete batch
_umbracoIndexingHandler.DeleteIndexForEntities(deleteBatch, false);
}
}
}