Skip to content

Commit

Permalink
Fix/overwrite old index files (#79)
Browse files Browse the repository at this point in the history
* feat(sln): remove all obsolete api references

* fix(npm): update versions

* fix(admin): delete old index files on delete index

* feat(Lucene.Core): add comments to Index storage strategy

* fix(GenerationStorageStrategy): add error logging on invalid delete

* fix(GenerationStorageStrategy): remove inaccessible catch

* fix(Lucene.Core): Add error message on unsuccessful index deletion

* fix(Delete index files): Retry policy

* fix(overwrite old index files): increase delay on each delete retry
  • Loading branch information
bkapustik authored Oct 29, 2024
1 parent f0656d6 commit 49cc111
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 4 deletions.
21 changes: 19 additions & 2 deletions src/Kentico.Xperience.Lucene.Admin/UIPages/IndexListingPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,13 +178,30 @@ public async Task<ICommandResponse<RowActionResult>> Rebuild(int id, Cancellatio
}

[PageCommand(Permission = SystemPermissions.DELETE)]
public Task<ICommandResponse> Delete(int id, CancellationToken _)
public async Task<ICommandResponse> Delete(int id, CancellationToken _)
{
var index = indexManager.GetIndex(id);
var result = new RowActionResult(false);

if (index is null)
{
return await Task.FromResult<ICommandResponse>(ResponseFrom(result)
.AddErrorMessage(string.Format("Error loading Lucene index with identifier {0}.", id)));
}

bool indexDeleted = await luceneClient.DeleteIndex(index!);

if (!indexDeleted)
{
return await Task.FromResult<ICommandResponse>(ResponseFrom(result)
.AddErrorMessage(string.Format("Errors occurred while deleting the '{0}' index. Please check the Event Log for more details.", index.IndexName)));
}

configurationStorageService.TryDeleteIndex(id);

var response = NavigateTo(pageLinkGenerator.GetPath<IndexListingPage>());

return Task.FromResult<ICommandResponse>(response);
return await Task.FromResult<ICommandResponse>(response);
}

private static void AddMissingStatistics(ref ICollection<LuceneIndexStatisticsModel> statistics, ILuceneIndexManager indexManager)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ public Task<int> UpsertRecords(IEnumerable<Document> documents, string indexName
return UpsertRecordsInternal(documents, indexName);
}

public async Task<bool> DeleteIndex(LuceneIndex luceneIndex) =>
await luceneIndex.StorageContext.DeleteIndex();

private Task<int> DeleteRecordsInternal(IEnumerable<string> itemGuids, string indexName)
{
var index = indexManager.GetIndex(indexName);
Expand Down
8 changes: 7 additions & 1 deletion src/Kentico.Xperience.Lucene.Core/Indexing/ILuceneClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ public interface ILuceneClient
/// <returns>The number of records deleted.</returns>
Task<int> DeleteRecords(IEnumerable<string> itemGuids, string indexName);

/// <summary>
/// Removes Lucene index.
/// </summary>
/// <param name="luceneIndex">The index to be deleted.</param>
Task<bool> DeleteIndex(LuceneIndex luceneIndex);

/// <summary>
/// Gets the indices of the Lucene application with basic statistics.
/// </summary>
Expand All @@ -29,7 +35,7 @@ public interface ILuceneClient
Task<ICollection<LuceneIndexStatisticsModel>> GetStatistics(CancellationToken cancellationToken);

/// <summary>
/// Updates the Lucene index with the dynamic data in each object of the passed <paramref name="documents"/>.
/// Updates the Lucene index with the dynamic data in each object of the passed.<paramref name="documents"/>.
/// </summary>
/// <remarks>Logs an error if there are issues loading the node data.</remarks>
/// <param name="documents">The document to upsert into Lucene.</param>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,66 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;

using CMS.Core;

namespace Kentico.Xperience.Lucene.Core.Indexing;

public interface ILuceneIndexStorageStrategy
{
/// <summary>
/// Gets all existing indices.
/// </summary>
/// <param name="indexStoragePath">Path root of the index storage.</param>
IEnumerable<IndexStorageModel> GetExistingIndices(string indexStoragePath);

/// <summary>
/// Formats path of an index in a specified generation.
/// </summary>
/// <param name="indexRoot">Root path of the index.</param>
/// <param name="generation">Indexing generation.</param>
/// <param name="isPublished">Parameter specifying whether the index has been published.</param>
string FormatPath(string indexRoot, int generation, bool isPublished);

/// <summary>
/// Formats path of a taxonomy of an index in a specified generation.
/// </summary>
/// <param name="indexRoot">Root path of the index.</param>
/// <param name="generation">Indexing generation.</param>
/// <param name="isPublished">Parameter specifying whether the taxonomy has been published.</param>
string FormatTaxonomyPath(string indexRoot, int generation, bool isPublished);

/// <summary>
/// Publishes the index.
/// </summary>
/// <param name="storage">Index storage model.</param>
void PublishIndex(IndexStorageModel storage);

/// <summary>
/// Schedules removal of files of an index.
/// </summary>
/// <param name="storage">Index storage model.</param>
bool ScheduleRemoval(IndexStorageModel storage);

/// <summary>
/// Performs cleanup of files of an index.
/// </summary>
/// <param name="indexStoragePath">Path root of the index storage.</param>
bool PerformCleanup(string indexStoragePath);

/// <summary>
/// Deletes all files associated with an index.
/// </summary>
/// <param name="indexStoragePath">Path root of the index storage.</param>
Task<bool> DeleteIndex(string indexStoragePath);
}

internal class GenerationStorageStrategy : ILuceneIndexStorageStrategy
{
private const string IndexDeletionDirectoryName = ".trash";
private readonly IEventLogService eventLogService;

public GenerationStorageStrategy() =>
eventLogService = Service.Resolve<IEventLogService>();

public IEnumerable<IndexStorageModel> GetExistingIndices(string indexStoragePath)
{
Expand Down Expand Up @@ -107,6 +152,50 @@ public bool ScheduleRemoval(IndexStorageModel storage)
return true;
}

public async Task<bool> DeleteIndex(string indexStoragePath)
{
var deleteDir = new DirectoryInfo(indexStoragePath);

if (!deleteDir.Exists)
{
return true;
}

int numberOfRetries = 10;
int millisecondsRetryDelay = 100;
int millisecondsAddedToRetryPerRequest = millisecondsRetryDelay * 2;

for (int i = 0; i < numberOfRetries; i++)
{
try
{
Trace.WriteLine($"D={deleteDir.Name}: delete *.*", $"GenerationStorageStrategy.DeleteIndex");
deleteDir.Delete(true);
return true;
}
catch
{
// Do nothing with exception and retry.
// The directory may be locked by another process, but we can not know about it without trying to delete it.
// The exact exception is not known and is not written in .NET documentation.

await Task.Delay(millisecondsRetryDelay + (millisecondsAddedToRetryPerRequest * numberOfRetries));
}
}
try
{
Trace.WriteLine($"D={deleteDir.Name}: delete *.*", $"GenerationStorageStrategy.DeleteIndex");
deleteDir.Delete(true);
}
catch (Exception ex)
{
eventLogService.LogError(nameof(GenerationStorageStrategy), nameof(DeleteIndex), ex.Message);
return false;
}

return true;
}

public bool PerformCleanup(string indexStoragePath)
{
string toDeleteDir = Path.Combine(indexStoragePath, IndexDeletionDirectoryName);
Expand Down Expand Up @@ -159,7 +248,6 @@ private sealed record IndexStorageModelParsingResult(
bool Success,
[property: MemberNotNullWhen(true, "Success")] IndexStorageModelParseResult? Result
);

private IndexStorageModelParsingResult ParseIndexStorageModel(string directoryPath)
{
if (string.IsNullOrWhiteSpace(directoryPath))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,4 +148,7 @@ public void EnforceRetentionPolicy()

storageStrategy.PerformCleanup(indexStoragePathRoot);
}

public async Task<bool> DeleteIndex() =>
await storageStrategy.DeleteIndex(indexStoragePathRoot);
}

0 comments on commit 49cc111

Please sign in to comment.