Skip to content

Commit c910f54

Browse files
ShazwazzaAndyButlandCopilot
authored
Improve deletion logic in UmbracoContentIndex (#21091)
* Improve deletion logic in UmbracoContentIndex When re-indexing a node it will first delete it, however in many cases the query to delete doesn't return anything, however a delete command is still executed with an empty integer collection. We can avoid allocating and iterating lists and avoid the deletion call all together if the collection is empty. * Apply suggestions from code review Co-authored-by: Copilot <[email protected]> * Clean-up and warning resolution whilst we are modifying class. --------- Co-authored-by: Andy Butland <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 0d599f6 commit c910f54

File tree

1 file changed

+39
-22
lines changed

1 file changed

+39
-22
lines changed

src/Umbraco.Examine.Lucene/UmbracoContentIndex.cs

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,16 @@
1212
namespace Umbraco.Cms.Infrastructure.Examine;
1313

1414
/// <summary>
15-
/// An indexer for Umbraco content and media
15+
/// An indexer for Umbraco content and media.
1616
/// </summary>
1717
public class UmbracoContentIndex : UmbracoExamineIndex, IUmbracoContentIndex
1818
{
1919
private readonly ISet<string> _idOnlyFieldSet = new HashSet<string> { "id" };
2020
private readonly ILogger<UmbracoContentIndex> _logger;
2121

22+
/// <summary>
23+
/// Initializes a new instance of the <see cref="UmbracoContentIndex"/> class.
24+
/// </summary>
2225
public UmbracoContentIndex(
2326
ILoggerFactory loggerFactory,
2427
string name,
@@ -31,12 +34,8 @@ public UmbracoContentIndex(
3134
LanguageService = languageService;
3235
_logger = loggerFactory.CreateLogger<UmbracoContentIndex>();
3336

34-
LuceneDirectoryIndexOptions namedOptions = indexOptions.Get(name);
35-
if (namedOptions == null)
36-
{
37-
throw new InvalidOperationException(
38-
$"No named {typeof(LuceneDirectoryIndexOptions)} options with name {name}");
39-
}
37+
LuceneDirectoryIndexOptions namedOptions = indexOptions.Get(name)
38+
?? throw new InvalidOperationException($"No named {typeof(LuceneDirectoryIndexOptions)} options with name {name}");
4039

4140
if (namedOptions.Validator is IContentValueSetValidator contentValueSetValidator)
4241
{
@@ -45,25 +44,25 @@ public UmbracoContentIndex(
4544
}
4645
}
4746

47+
/// <summary>
48+
/// Gets the <see cref="ILocalizationService"/>.
49+
/// </summary>
4850
protected ILocalizationService? LanguageService { get; }
4951

5052
/// <summary>
5153
/// Explicitly override because we need to do validation differently than the underlying logic
5254
/// </summary>
53-
/// <param name="values"></param>
5455
void IIndex.IndexItems(IEnumerable<ValueSet> values) => PerformIndexItems(values, OnIndexOperationComplete);
5556

5657
/// <summary>
57-
/// Special check for invalid paths
58+
/// Special check for invalid paths.
5859
/// </summary>
59-
/// <param name="values"></param>
60-
/// <param name="onComplete"></param>
6160
protected override void PerformIndexItems(IEnumerable<ValueSet> values, Action<IndexOperationEventArgs> onComplete)
6261
{
6362
// We don't want to re-enumerate this list, but we need to split it into 2x enumerables: invalid and valid items.
6463
// The Invalid items will be deleted, these are items that have invalid paths (i.e. moved to the recycle bin, etc...)
6564
// Then we'll index the Value group all together.
66-
var invalidOrValid = values.GroupBy(v =>
65+
IGrouping<ValueSetValidationStatus, ValueSet>[] invalidOrValid = values.GroupBy(v =>
6766
{
6867
if (!v.Values.TryGetValue("path", out IReadOnlyList<object>? paths) || paths.Count <= 0 || paths[0] == null)
6968
{
@@ -82,24 +81,24 @@ protected override void PerformIndexItems(IEnumerable<ValueSet> values, Action<I
8281
var hasDeletes = false;
8382
var hasUpdates = false;
8483

85-
// ordering by descending so that Filtered/Failed processes first
84+
// Ordering by descending so that Filtered/Failed processes first.
8685
foreach (IGrouping<ValueSetValidationStatus, ValueSet> group in invalidOrValid.OrderByDescending(x => x.Key))
8786
{
8887
switch (group.Key)
8988
{
9089
case ValueSetValidationStatus.Valid:
9190
hasUpdates = true;
9291

93-
//these are the valid ones, so just index them all at once
92+
// These are the valid ones, so just index them all at once.
9493
base.PerformIndexItems(group.ToArray(), onComplete);
9594
break;
9695
case ValueSetValidationStatus.Failed:
97-
// don't index anything that is invalid
96+
// Don't index anything that is invalid.
9897
break;
9998
case ValueSetValidationStatus.Filtered:
10099
hasDeletes = true;
101100

102-
// these are the invalid/filtered items so we'll delete them
101+
// These are the invalid/filtered items so we'll delete them
103102
// since the path is not valid we need to delete this item in
104103
// case it exists in the index already and has now
105104
// been moved to an invalid parent.
@@ -110,7 +109,7 @@ protected override void PerformIndexItems(IEnumerable<ValueSet> values, Action<I
110109

111110
if ((hasDeletes && !hasUpdates) || (!hasDeletes && !hasUpdates))
112111
{
113-
//we need to manually call the completed method
112+
// We need to manually call the completed method.
114113
onComplete(new IndexOperationEventArgs(this, 0));
115114
}
116115
}
@@ -133,26 +132,44 @@ protected override void PerformDeleteFromIndex(IEnumerable<string> itemIds, Acti
133132
{
134133
var nodeId = idsAsList[i];
135134

136-
//find all descendants based on path
135+
// Find all descendants based on path.
137136
var descendantPath = $@"\-1\,*{nodeId}\,*";
138137
var rawQuery = $"{UmbracoExamineFieldNames.IndexPathFieldName}:{descendantPath}";
139138
IQuery? c = Searcher.CreateQuery();
140139
IBooleanOperation? filtered = c.NativeQuery(rawQuery);
141140
IOrdering? selectedFields = filtered.SelectFields(_idOnlyFieldSet);
142141
ISearchResults? results = selectedFields.Execute();
143-
if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug))
142+
if (_logger.IsEnabled(LogLevel.Debug))
144143
{
145144
_logger.LogDebug("DeleteFromIndex with query: {Query} (found {TotalItems} results)", rawQuery, results.TotalItemCount);
146145
}
147146

147+
// Avoid unnecessary operations when we have no items to handle. This is necessary for ExamineX's Elastic implementation
148+
// which doesn't support making requests with an empty collection.
149+
if (results.TotalItemCount == 0)
150+
{
151+
continue;
152+
}
153+
148154
var toRemove = results.Select(x => x.Id).ToList();
149-
// delete those descendants (ensure base. is used here so we aren't calling ourselves!)
155+
156+
// Delete those descendants (ensure base. is used here so we aren't calling ourselves!).
150157
base.PerformDeleteFromIndex(toRemove, null);
151158

152-
// remove any ids from our list that were part of the descendants
159+
// Remove any ids from our list that were part of the descendants.
153160
idsAsList.RemoveAll(x => toRemove.Contains(x));
154161
}
155162

156-
base.PerformDeleteFromIndex(idsAsList, onComplete);
163+
// Avoid unnecessary operations when we have no items to handle. This is necessary for ExamineX's Elastic implementation
164+
// which doesn't support making requests with an empty collection.
165+
if (idsAsList.Count > 0)
166+
{
167+
base.PerformDeleteFromIndex(idsAsList, onComplete);
168+
}
169+
else
170+
{
171+
// Manually invoke the complete callback if provided, so we maintain consistency when there is or isn't anything to delete.
172+
onComplete?.Invoke(new IndexOperationEventArgs(this, 0));
173+
}
157174
}
158175
}

0 commit comments

Comments
 (0)