Skip to content

Commit dcd8b42

Browse files
authored
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
1 parent 3b4639d commit dcd8b42

File tree

9 files changed

+269
-108
lines changed

9 files changed

+269
-108
lines changed

src/Umbraco.Core/Services/ICacheInstructionService.cs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,37 @@ public interface ICacheInstructionService
3434
void DeliverInstructionsInBatches(IEnumerable<RefreshInstruction> instructions, string localIdentity);
3535

3636
/// <summary>
37-
/// Processes and then prunes pending database cache instructions.
37+
/// Processes pending database cache instructions.
38+
/// </summary>
39+
/// <param name="cacheRefreshers">Cache refreshers.</param>
40+
/// <param name="cancellationToken">Cancellation token.</param>
41+
/// <param name="localIdentity">Local identity of the executing AppDomain.</param>
42+
/// <param name="lastId">Id of the latest processed instruction.</param>
43+
/// <returns>The processing result.</returns>
44+
ProcessInstructionsResult ProcessInstructions(
45+
CacheRefresherCollection cacheRefreshers,
46+
CancellationToken cancellationToken,
47+
string localIdentity,
48+
int lastId) =>
49+
ProcessInstructions(
50+
cacheRefreshers,
51+
ServerRole.Unknown,
52+
cancellationToken,
53+
localIdentity,
54+
lastPruned: DateTime.UtcNow,
55+
lastId);
56+
57+
/// <summary>
58+
/// Processes pending database cache instructions.
3859
/// </summary>
3960
/// <param name="cacheRefreshers">Cache refreshers.</param>
4061
/// <param name="serverRole">Server role.</param>
4162
/// <param name="cancellationToken">Cancellation token.</param>
4263
/// <param name="localIdentity">Local identity of the executing AppDomain.</param>
4364
/// <param name="lastPruned">Date of last prune operation.</param>
44-
/// <param name="lastId">Id of the latest processed instruction</param>
65+
/// <param name="lastId">Id of the latest processed instruction.</param>
66+
/// <returns>The processing result.</returns>
67+
[Obsolete("Use the non-obsolete overload. Scheduled for removal in V17.")]
4568
ProcessInstructionsResult ProcessInstructions(
4669
CacheRefresherCollection cacheRefreshers,
4770
ServerRole serverRole,

src/Umbraco.Core/Services/ProcessInstructionsResult.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@ private ProcessInstructionsResult()
1414

1515
public int LastId { get; private set; }
1616

17+
[Obsolete("Instruction pruning has been moved to a separate background job. Scheduled for removal in V18.")]
1718
public bool InstructionsWerePruned { get; private set; }
1819

1920
public static ProcessInstructionsResult AsCompleted(int numberOfInstructionsProcessed, int lastId) =>
2021
new() { NumberOfInstructionsProcessed = numberOfInstructionsProcessed, LastId = lastId };
2122

23+
[Obsolete("Instruction pruning has been moved to a separate background job. Scheduled for removal in V18.")]
2224
public static ProcessInstructionsResult AsCompletedAndPruned(int numberOfInstructionsProcessed, int lastId) =>
2325
new()
2426
{
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
using Microsoft.Extensions.Options;
2+
using Umbraco.Cms.Core.Configuration.Models;
3+
using Umbraco.Cms.Core.Persistence.Repositories;
4+
using Umbraco.Cms.Core.Scoping;
5+
using Umbraco.Cms.Infrastructure.Scoping;
6+
7+
namespace Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs;
8+
9+
/// <summary>
10+
/// A background job that prunes cache instructions from the database.
11+
/// </summary>
12+
public class CacheInstructionsPruningJob : IRecurringBackgroundJob
13+
{
14+
private readonly IOptions<GlobalSettings> _globalSettings;
15+
private readonly ICacheInstructionRepository _cacheInstructionRepository;
16+
private readonly ICoreScopeProvider _scopeProvider;
17+
private readonly TimeProvider _timeProvider;
18+
19+
/// <summary>
20+
/// Initializes a new instance of the <see cref="CacheInstructionsPruningJob"/> class.
21+
/// </summary>
22+
/// <param name="scopeProvider">Provides scopes for database operations.</param>
23+
/// <param name="globalSettings">The global settings configuration.</param>
24+
/// <param name="cacheInstructionRepository">The repository for cache instructions.</param>
25+
/// <param name="timeProvider">The time provider.</param>
26+
public CacheInstructionsPruningJob(
27+
IOptions<GlobalSettings> globalSettings,
28+
ICacheInstructionRepository cacheInstructionRepository,
29+
ICoreScopeProvider scopeProvider,
30+
TimeProvider timeProvider)
31+
{
32+
_globalSettings = globalSettings;
33+
_cacheInstructionRepository = cacheInstructionRepository;
34+
_scopeProvider = scopeProvider;
35+
_timeProvider = timeProvider;
36+
Period = globalSettings.Value.DatabaseServerMessenger.TimeBetweenPruneOperations;
37+
}
38+
39+
/// <inheritdoc />
40+
public event EventHandler PeriodChanged
41+
{
42+
add { }
43+
remove { }
44+
}
45+
46+
/// <inheritdoc />
47+
public TimeSpan Period { get; }
48+
49+
/// <inheritdoc />
50+
public Task RunJobAsync()
51+
{
52+
DateTimeOffset pruneDate = _timeProvider.GetUtcNow() - _globalSettings.Value.DatabaseServerMessenger.TimeToRetainInstructions;
53+
using (ICoreScope scope = _scopeProvider.CreateCoreScope())
54+
{
55+
_cacheInstructionRepository.DeleteInstructionsOlderThan(pruneDate.DateTime);
56+
scope.Complete();
57+
}
58+
59+
return Task.CompletedTask;
60+
}
61+
}

src/Umbraco.Infrastructure/Services/CacheInstructionService.cs

Lines changed: 12 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -122,43 +122,30 @@ public void DeliverInstructionsInBatches(IEnumerable<RefreshInstruction> instruc
122122
/// <inheritdoc />
123123
public ProcessInstructionsResult ProcessInstructions(
124124
CacheRefresherCollection cacheRefreshers,
125-
ServerRole serverRole,
126125
CancellationToken cancellationToken,
127126
string localIdentity,
128-
DateTime lastPruned,
129127
int lastId)
130128
{
131129
using (!_profilingLogger.IsEnabled(Core.Logging.LogLevel.Debug) ? null : _profilingLogger.DebugDuration<CacheInstructionService>("Syncing from database..."))
132130
using (ICoreScope scope = ScopeProvider.CreateCoreScope())
133131
{
134132
var numberOfInstructionsProcessed = ProcessDatabaseInstructions(cacheRefreshers, cancellationToken, localIdentity, ref lastId);
135-
136-
// Check for pruning throttling.
137-
if (cancellationToken.IsCancellationRequested || DateTime.UtcNow - lastPruned <=
138-
_globalSettings.DatabaseServerMessenger.TimeBetweenPruneOperations)
139-
{
140-
scope.Complete();
141-
return ProcessInstructionsResult.AsCompleted(numberOfInstructionsProcessed, lastId);
142-
}
143-
144-
var instructionsWerePruned = false;
145-
switch (serverRole)
146-
{
147-
case ServerRole.Single:
148-
case ServerRole.SchedulingPublisher:
149-
PruneOldInstructions();
150-
instructionsWerePruned = true;
151-
break;
152-
}
153-
154133
scope.Complete();
155-
156-
return instructionsWerePruned
157-
? ProcessInstructionsResult.AsCompletedAndPruned(numberOfInstructionsProcessed, lastId)
158-
: ProcessInstructionsResult.AsCompleted(numberOfInstructionsProcessed, lastId);
134+
return ProcessInstructionsResult.AsCompleted(numberOfInstructionsProcessed, lastId);
159135
}
160136
}
161137

138+
/// <inheritdoc />
139+
[Obsolete("Use the non-obsolete overload. Scheduled for removal in V17.")]
140+
public ProcessInstructionsResult ProcessInstructions(
141+
CacheRefresherCollection cacheRefreshers,
142+
ServerRole serverRole,
143+
CancellationToken cancellationToken,
144+
string localIdentity,
145+
DateTime lastPruned,
146+
int lastId) =>
147+
ProcessInstructions(cacheRefreshers, cancellationToken, localIdentity, lastId);
148+
162149
private CacheInstruction CreateCacheInstruction(IEnumerable<RefreshInstruction> instructions, string localIdentity)
163150
=> new(
164151
0,
@@ -486,21 +473,6 @@ private static IJsonCacheRefresher GetJsonRefresher(ICacheRefresher refresher)
486473

487474
return jsonRefresher;
488475
}
489-
490-
/// <summary>
491-
/// Remove old instructions from the database
492-
/// </summary>
493-
/// <remarks>
494-
/// Always leave the last (most recent) record in the db table, this is so that not all instructions are removed which
495-
/// would cause
496-
/// the site to cold boot if there's been no instruction activity for more than TimeToRetainInstructions.
497-
/// See: http://issues.umbraco.org/issue/U4-7643#comment=67-25085
498-
/// </remarks>
499-
private void PruneOldInstructions()
500-
{
501-
DateTime pruneDate = DateTime.UtcNow - _globalSettings.DatabaseServerMessenger.TimeToRetainInstructions;
502-
_cacheInstructionRepository.DeleteInstructionsOlderThan(pruneDate);
503-
}
504476
}
505477
}
506478
}

src/Umbraco.Infrastructure/Sync/BatchedDatabaseServerMessenger.cs

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,41 @@ namespace Umbraco.Cms.Infrastructure.Sync;
1616
/// </summary>
1717
public class BatchedDatabaseServerMessenger : DatabaseServerMessenger
1818
{
19-
private readonly IRequestAccessor _requestAccessor;
2019
private readonly IRequestCache _requestCache;
2120

2221
/// <summary>
2322
/// Initializes a new instance of the <see cref="BatchedDatabaseServerMessenger" /> class.
2423
/// </summary>
24+
public BatchedDatabaseServerMessenger(
25+
IMainDom mainDom,
26+
CacheRefresherCollection cacheRefreshers,
27+
ILogger<BatchedDatabaseServerMessenger> logger,
28+
ISyncBootStateAccessor syncBootStateAccessor,
29+
IHostingEnvironment hostingEnvironment,
30+
ICacheInstructionService cacheInstructionService,
31+
IJsonSerializer jsonSerializer,
32+
IRequestCache requestCache,
33+
LastSyncedFileManager lastSyncedFileManager,
34+
IOptionsMonitor<GlobalSettings> globalSettings)
35+
: base(
36+
mainDom,
37+
cacheRefreshers,
38+
logger,
39+
true,
40+
syncBootStateAccessor,
41+
hostingEnvironment,
42+
cacheInstructionService,
43+
jsonSerializer,
44+
lastSyncedFileManager,
45+
globalSettings)
46+
{
47+
_requestCache = requestCache;
48+
}
49+
50+
/// <summary>
51+
/// Initializes a new instance of the <see cref="BatchedDatabaseServerMessenger" /> class.
52+
/// </summary>
53+
[Obsolete("Use the non-obsolete constructor instead. Scheduled for removal in V18.")]
2554
public BatchedDatabaseServerMessenger(
2655
IMainDom mainDom,
2756
CacheRefresherCollection cacheRefreshers,
@@ -35,11 +64,18 @@ public BatchedDatabaseServerMessenger(
3564
IRequestAccessor requestAccessor,
3665
LastSyncedFileManager lastSyncedFileManager,
3766
IOptionsMonitor<GlobalSettings> globalSettings)
38-
: base(mainDom, cacheRefreshers, serverRoleAccessor, logger, true, syncBootStateAccessor, hostingEnvironment,
39-
cacheInstructionService, jsonSerializer, lastSyncedFileManager, globalSettings)
67+
: this(
68+
mainDom,
69+
cacheRefreshers,
70+
logger,
71+
syncBootStateAccessor,
72+
hostingEnvironment,
73+
cacheInstructionService,
74+
jsonSerializer,
75+
requestCache,
76+
lastSyncedFileManager,
77+
globalSettings)
4078
{
41-
_requestCache = requestCache;
42-
_requestAccessor = requestAccessor;
4379
}
4480

4581
/// <inheritdoc />

src/Umbraco.Infrastructure/Sync/DatabaseServerMessenger.cs

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,9 @@ public abstract class DatabaseServerMessenger : ServerMessengerBase, IDisposable
3131
*/
3232

3333
private readonly IMainDom _mainDom;
34-
private readonly IServerRoleAccessor _serverRoleAccessor;
3534
private readonly ISyncBootStateAccessor _syncBootStateAccessor;
3635
private readonly ManualResetEvent _syncIdle;
3736
private bool _disposedValue;
38-
private DateTime _lastPruned;
3937
private DateTime _lastSync;
4038
private bool _syncing;
4139

@@ -45,7 +43,6 @@ public abstract class DatabaseServerMessenger : ServerMessengerBase, IDisposable
4543
protected DatabaseServerMessenger(
4644
IMainDom mainDom,
4745
CacheRefresherCollection cacheRefreshers,
48-
IServerRoleAccessor serverRoleAccessor,
4946
ILogger<DatabaseServerMessenger> logger,
5047
bool distributedEnabled,
5148
ISyncBootStateAccessor syncBootStateAccessor,
@@ -59,15 +56,14 @@ protected DatabaseServerMessenger(
5956
_cancellationToken = _cancellationTokenSource.Token;
6057
_mainDom = mainDom;
6158
_cacheRefreshers = cacheRefreshers;
62-
_serverRoleAccessor = serverRoleAccessor;
6359
_hostingEnvironment = hostingEnvironment;
6460
Logger = logger;
6561
_syncBootStateAccessor = syncBootStateAccessor;
6662
CacheInstructionService = cacheInstructionService;
6763
JsonSerializer = jsonSerializer;
6864
_lastSyncedFileManager = lastSyncedFileManager;
6965
GlobalSettings = globalSettings.CurrentValue;
70-
_lastPruned = _lastSync = DateTime.UtcNow;
66+
_lastSync = DateTime.UtcNow;
7167
_syncIdle = new ManualResetEvent(true);
7268

7369
globalSettings.OnChange(x => GlobalSettings = x);
@@ -84,6 +80,36 @@ protected DatabaseServerMessenger(
8480
_initialized = new Lazy<SyncBootState?>(InitializeWithMainDom);
8581
}
8682

83+
/// <summary>
84+
/// Initializes a new instance of the <see cref="DatabaseServerMessenger" /> class.
85+
/// </summary>
86+
[Obsolete("Use the non-obsolete constructor. Scheduled for removal in V18.")]
87+
protected DatabaseServerMessenger(
88+
IMainDom mainDom,
89+
CacheRefresherCollection cacheRefreshers,
90+
IServerRoleAccessor serverRoleAccessor,
91+
ILogger<DatabaseServerMessenger> logger,
92+
bool distributedEnabled,
93+
ISyncBootStateAccessor syncBootStateAccessor,
94+
IHostingEnvironment hostingEnvironment,
95+
ICacheInstructionService cacheInstructionService,
96+
IJsonSerializer jsonSerializer,
97+
LastSyncedFileManager lastSyncedFileManager,
98+
IOptionsMonitor<GlobalSettings> globalSettings)
99+
: this(
100+
mainDom,
101+
cacheRefreshers,
102+
logger,
103+
distributedEnabled,
104+
syncBootStateAccessor,
105+
hostingEnvironment,
106+
cacheInstructionService,
107+
jsonSerializer,
108+
lastSyncedFileManager,
109+
globalSettings)
110+
{
111+
}
112+
87113
public GlobalSettings GlobalSettings { get; private set; }
88114

89115
protected ILogger<DatabaseServerMessenger> Logger { get; }
@@ -146,17 +172,10 @@ public override void Sync()
146172
{
147173
ProcessInstructionsResult result = CacheInstructionService.ProcessInstructions(
148174
_cacheRefreshers,
149-
_serverRoleAccessor.CurrentServerRole,
150175
_cancellationToken,
151176
LocalIdentity,
152-
_lastPruned,
153177
_lastSyncedFileManager.LastSyncedId);
154178

155-
if (result.InstructionsWerePruned)
156-
{
157-
_lastPruned = _lastSync;
158-
}
159-
160179
if (result.LastId > 0)
161180
{
162181
_lastSyncedFileManager.SaveLastSyncedId(result.LastId);

src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ public static IUmbracoBuilder AddRecurringBackgroundJobs(this IUmbracoBuilder bu
189189
builder.Services.AddRecurringBackgroundJob<WebhookFiring>();
190190
builder.Services.AddRecurringBackgroundJob<WebhookLoggingCleanup>();
191191
builder.Services.AddRecurringBackgroundJob<ReportSiteJob>();
192+
builder.Services.AddRecurringBackgroundJob<CacheInstructionsPruningJob>();
192193

193194

194195
builder.Services.AddSingleton(RecurringBackgroundJobHostedService.CreateHostedServiceFactory);

0 commit comments

Comments
 (0)