Move cache instructions pruning to background job (#19598)

* Remove pruning logic from `CacheInstructionService.ProcessInstructions()`

* Add and register `CacheInstructionsPruningJob` background job

* Add unit tests

* Remove breaking change in ICacheInstructionService

* Adjust some obsoletion messages to mention v17

* Added missing scope

* Update tests

* Fix obsoletion messages version

* Update ProcessInstructions methods summary
This commit is contained in:
Laura Neto
2025-07-01 11:17:59 +02:00
committed by GitHub
parent 3b4639de08
commit dcd8b42522
9 changed files with 269 additions and 108 deletions

View File

@@ -0,0 +1,61 @@
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Persistence.Repositories;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Infrastructure.Scoping;
namespace Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs;
/// <summary>
/// A background job that prunes cache instructions from the database.
/// </summary>
public class CacheInstructionsPruningJob : IRecurringBackgroundJob
{
private readonly IOptions<GlobalSettings> _globalSettings;
private readonly ICacheInstructionRepository _cacheInstructionRepository;
private readonly ICoreScopeProvider _scopeProvider;
private readonly TimeProvider _timeProvider;
/// <summary>
/// Initializes a new instance of the <see cref="CacheInstructionsPruningJob"/> class.
/// </summary>
/// <param name="scopeProvider">Provides scopes for database operations.</param>
/// <param name="globalSettings">The global settings configuration.</param>
/// <param name="cacheInstructionRepository">The repository for cache instructions.</param>
/// <param name="timeProvider">The time provider.</param>
public CacheInstructionsPruningJob(
IOptions<GlobalSettings> globalSettings,
ICacheInstructionRepository cacheInstructionRepository,
ICoreScopeProvider scopeProvider,
TimeProvider timeProvider)
{
_globalSettings = globalSettings;
_cacheInstructionRepository = cacheInstructionRepository;
_scopeProvider = scopeProvider;
_timeProvider = timeProvider;
Period = globalSettings.Value.DatabaseServerMessenger.TimeBetweenPruneOperations;
}
/// <inheritdoc />
public event EventHandler PeriodChanged
{
add { }
remove { }
}
/// <inheritdoc />
public TimeSpan Period { get; }
/// <inheritdoc />
public Task RunJobAsync()
{
DateTimeOffset pruneDate = _timeProvider.GetUtcNow() - _globalSettings.Value.DatabaseServerMessenger.TimeToRetainInstructions;
using (ICoreScope scope = _scopeProvider.CreateCoreScope())
{
_cacheInstructionRepository.DeleteInstructionsOlderThan(pruneDate.DateTime);
scope.Complete();
}
return Task.CompletedTask;
}
}

View File

@@ -122,43 +122,30 @@ namespace Umbraco.Cms
/// <inheritdoc />
public ProcessInstructionsResult ProcessInstructions(
CacheRefresherCollection cacheRefreshers,
ServerRole serverRole,
CancellationToken cancellationToken,
string localIdentity,
DateTime lastPruned,
int lastId)
{
using (!_profilingLogger.IsEnabled(Core.Logging.LogLevel.Debug) ? null : _profilingLogger.DebugDuration<CacheInstructionService>("Syncing from database..."))
using (ICoreScope scope = ScopeProvider.CreateCoreScope())
{
var numberOfInstructionsProcessed = ProcessDatabaseInstructions(cacheRefreshers, cancellationToken, localIdentity, ref lastId);
// Check for pruning throttling.
if (cancellationToken.IsCancellationRequested || DateTime.UtcNow - lastPruned <=
_globalSettings.DatabaseServerMessenger.TimeBetweenPruneOperations)
{
scope.Complete();
return ProcessInstructionsResult.AsCompleted(numberOfInstructionsProcessed, lastId);
}
var instructionsWerePruned = false;
switch (serverRole)
{
case ServerRole.Single:
case ServerRole.SchedulingPublisher:
PruneOldInstructions();
instructionsWerePruned = true;
break;
}
scope.Complete();
return instructionsWerePruned
? ProcessInstructionsResult.AsCompletedAndPruned(numberOfInstructionsProcessed, lastId)
: ProcessInstructionsResult.AsCompleted(numberOfInstructionsProcessed, lastId);
return ProcessInstructionsResult.AsCompleted(numberOfInstructionsProcessed, lastId);
}
}
/// <inheritdoc />
[Obsolete("Use the non-obsolete overload. Scheduled for removal in V17.")]
public ProcessInstructionsResult ProcessInstructions(
CacheRefresherCollection cacheRefreshers,
ServerRole serverRole,
CancellationToken cancellationToken,
string localIdentity,
DateTime lastPruned,
int lastId) =>
ProcessInstructions(cacheRefreshers, cancellationToken, localIdentity, lastId);
private CacheInstruction CreateCacheInstruction(IEnumerable<RefreshInstruction> instructions, string localIdentity)
=> new(
0,
@@ -486,21 +473,6 @@ namespace Umbraco.Cms
return jsonRefresher;
}
/// <summary>
/// Remove old instructions from the database
/// </summary>
/// <remarks>
/// Always leave the last (most recent) record in the db table, this is so that not all instructions are removed which
/// would cause
/// the site to cold boot if there's been no instruction activity for more than TimeToRetainInstructions.
/// See: http://issues.umbraco.org/issue/U4-7643#comment=67-25085
/// </remarks>
private void PruneOldInstructions()
{
DateTime pruneDate = DateTime.UtcNow - _globalSettings.DatabaseServerMessenger.TimeToRetainInstructions;
_cacheInstructionRepository.DeleteInstructionsOlderThan(pruneDate);
}
}
}
}

View File

@@ -16,12 +16,41 @@ namespace Umbraco.Cms.Infrastructure.Sync;
/// </summary>
public class BatchedDatabaseServerMessenger : DatabaseServerMessenger
{
private readonly IRequestAccessor _requestAccessor;
private readonly IRequestCache _requestCache;
/// <summary>
/// Initializes a new instance of the <see cref="BatchedDatabaseServerMessenger" /> class.
/// </summary>
public BatchedDatabaseServerMessenger(
IMainDom mainDom,
CacheRefresherCollection cacheRefreshers,
ILogger<BatchedDatabaseServerMessenger> logger,
ISyncBootStateAccessor syncBootStateAccessor,
IHostingEnvironment hostingEnvironment,
ICacheInstructionService cacheInstructionService,
IJsonSerializer jsonSerializer,
IRequestCache requestCache,
LastSyncedFileManager lastSyncedFileManager,
IOptionsMonitor<GlobalSettings> globalSettings)
: base(
mainDom,
cacheRefreshers,
logger,
true,
syncBootStateAccessor,
hostingEnvironment,
cacheInstructionService,
jsonSerializer,
lastSyncedFileManager,
globalSettings)
{
_requestCache = requestCache;
}
/// <summary>
/// Initializes a new instance of the <see cref="BatchedDatabaseServerMessenger" /> class.
/// </summary>
[Obsolete("Use the non-obsolete constructor instead. Scheduled for removal in V18.")]
public BatchedDatabaseServerMessenger(
IMainDom mainDom,
CacheRefresherCollection cacheRefreshers,
@@ -35,11 +64,18 @@ public class BatchedDatabaseServerMessenger : DatabaseServerMessenger
IRequestAccessor requestAccessor,
LastSyncedFileManager lastSyncedFileManager,
IOptionsMonitor<GlobalSettings> globalSettings)
: base(mainDom, cacheRefreshers, serverRoleAccessor, logger, true, syncBootStateAccessor, hostingEnvironment,
cacheInstructionService, jsonSerializer, lastSyncedFileManager, globalSettings)
: this(
mainDom,
cacheRefreshers,
logger,
syncBootStateAccessor,
hostingEnvironment,
cacheInstructionService,
jsonSerializer,
requestCache,
lastSyncedFileManager,
globalSettings)
{
_requestCache = requestCache;
_requestAccessor = requestAccessor;
}
/// <inheritdoc />

View File

@@ -31,11 +31,9 @@ public abstract class DatabaseServerMessenger : ServerMessengerBase, IDisposable
*/
private readonly IMainDom _mainDom;
private readonly IServerRoleAccessor _serverRoleAccessor;
private readonly ISyncBootStateAccessor _syncBootStateAccessor;
private readonly ManualResetEvent _syncIdle;
private bool _disposedValue;
private DateTime _lastPruned;
private DateTime _lastSync;
private bool _syncing;
@@ -45,7 +43,6 @@ public abstract class DatabaseServerMessenger : ServerMessengerBase, IDisposable
protected DatabaseServerMessenger(
IMainDom mainDom,
CacheRefresherCollection cacheRefreshers,
IServerRoleAccessor serverRoleAccessor,
ILogger<DatabaseServerMessenger> logger,
bool distributedEnabled,
ISyncBootStateAccessor syncBootStateAccessor,
@@ -59,7 +56,6 @@ public abstract class DatabaseServerMessenger : ServerMessengerBase, IDisposable
_cancellationToken = _cancellationTokenSource.Token;
_mainDom = mainDom;
_cacheRefreshers = cacheRefreshers;
_serverRoleAccessor = serverRoleAccessor;
_hostingEnvironment = hostingEnvironment;
Logger = logger;
_syncBootStateAccessor = syncBootStateAccessor;
@@ -67,7 +63,7 @@ public abstract class DatabaseServerMessenger : ServerMessengerBase, IDisposable
JsonSerializer = jsonSerializer;
_lastSyncedFileManager = lastSyncedFileManager;
GlobalSettings = globalSettings.CurrentValue;
_lastPruned = _lastSync = DateTime.UtcNow;
_lastSync = DateTime.UtcNow;
_syncIdle = new ManualResetEvent(true);
globalSettings.OnChange(x => GlobalSettings = x);
@@ -84,6 +80,36 @@ public abstract class DatabaseServerMessenger : ServerMessengerBase, IDisposable
_initialized = new Lazy<SyncBootState?>(InitializeWithMainDom);
}
/// <summary>
/// Initializes a new instance of the <see cref="DatabaseServerMessenger" /> class.
/// </summary>
[Obsolete("Use the non-obsolete constructor. Scheduled for removal in V18.")]
protected DatabaseServerMessenger(
IMainDom mainDom,
CacheRefresherCollection cacheRefreshers,
IServerRoleAccessor serverRoleAccessor,
ILogger<DatabaseServerMessenger> logger,
bool distributedEnabled,
ISyncBootStateAccessor syncBootStateAccessor,
IHostingEnvironment hostingEnvironment,
ICacheInstructionService cacheInstructionService,
IJsonSerializer jsonSerializer,
LastSyncedFileManager lastSyncedFileManager,
IOptionsMonitor<GlobalSettings> globalSettings)
: this(
mainDom,
cacheRefreshers,
logger,
distributedEnabled,
syncBootStateAccessor,
hostingEnvironment,
cacheInstructionService,
jsonSerializer,
lastSyncedFileManager,
globalSettings)
{
}
public GlobalSettings GlobalSettings { get; private set; }
protected ILogger<DatabaseServerMessenger> Logger { get; }
@@ -146,17 +172,10 @@ public abstract class DatabaseServerMessenger : ServerMessengerBase, IDisposable
{
ProcessInstructionsResult result = CacheInstructionService.ProcessInstructions(
_cacheRefreshers,
_serverRoleAccessor.CurrentServerRole,
_cancellationToken,
LocalIdentity,
_lastPruned,
_lastSyncedFileManager.LastSyncedId);
if (result.InstructionsWerePruned)
{
_lastPruned = _lastSync;
}
if (result.LastId > 0)
{
_lastSyncedFileManager.SaveLastSyncedId(result.LastId);