Skip to content

Commit a814295

Browse files
feat: implement TableCleanupService for periodic cleanup of excess discrete probabilities and utilities (#345)
* feat: implement TableCleanupService for periodic cleanup of excess discrete probabilities and utilities * refactor: optimize cleanup loop in TableCleanupService using PeriodicTimer
1 parent 5254936 commit a814295

4 files changed

Lines changed: 114 additions & 0 deletions

File tree

PrismaDotnetApi/PrismaApi.Api/Program.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using PrismaApi.Api.Configuration.Extensions;
66
using PrismaApi.Api.Configuration.JsonResponseOptions;
77
using PrismaApi.Api.SecurityPolicy;
8+
using PrismaApi.Application.BackgroundServices;
89
using PrismaApi.Application.Interfaces.Repositories;
910
using PrismaApi.Application.Interfaces.Services;
1011
using PrismaApi.Application.Repositories;
@@ -137,6 +138,7 @@ public static void Main(string[] args)
137138
builder.Services.AddScoped<IObjectiveService, ObjectiveService>();
138139
builder.Services.AddScoped<IProjectRoleService, ProjectRoleService>();
139140
builder.Services.AddScoped<IBoardNodeService, BoardNodeService>();
141+
builder.Services.AddHostedService<TableCleanupService>();
140142

141143
if (isPublicInstance)
142144
{
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
using Microsoft.Extensions.Hosting;
3+
using Microsoft.Extensions.Logging;
4+
using PrismaApi.Application.Interfaces.Services;
5+
6+
namespace PrismaApi.Application.BackgroundServices;
7+
8+
public class TableCleanupService : BackgroundService
9+
{
10+
private readonly IServiceScopeFactory _scopeFactory;
11+
private readonly ILogger<TableCleanupService> _logger;
12+
private readonly TimeSpan _delay = TimeSpan.FromMinutes(10);
13+
14+
public TableCleanupService(IServiceScopeFactory serviceScopeFactory, ILogger<TableCleanupService> logger)
15+
{
16+
_scopeFactory = serviceScopeFactory;
17+
_logger = logger;
18+
}
19+
20+
protected override async Task ExecuteAsync(CancellationToken ct = default)
21+
{
22+
try
23+
{
24+
await CleanupLoopAsync(ct);
25+
}
26+
catch (TaskCanceledException)
27+
{
28+
_logger.LogInformation("TableCleanupService is stopping due to cancellation.");
29+
}
30+
catch (Exception ex)
31+
{
32+
_logger.LogError(ex, "An error occurred in TableCleanupService.");
33+
}
34+
}
35+
36+
/// <summary>
37+
/// This is a temporary solution to clean up excess discrete probabilities and utilities until we implement a more robust solution in the future. It continuously runs a loop that cleans up excess discrete probabilities and utilities every 10 or so minutes. This is necessary because currently we do not have a way to automatically clean up excess discrete probabilities and utilities.
38+
/// </summary>
39+
private async Task CleanupLoopAsync(CancellationToken ct = default)
40+
{
41+
using PeriodicTimer timer = new(_delay);
42+
while (await timer.WaitForNextTickAsync(ct))
43+
{
44+
try
45+
{
46+
var startTime = DateTime.UtcNow;
47+
using var scope = _scopeFactory.CreateScope();
48+
var tableRebuildingService = scope.ServiceProvider.GetRequiredService<ITableRebuildingService>();
49+
await tableRebuildingService.RemoveExcessDiscreteProbabilities(ct);
50+
await tableRebuildingService.RemoveExcessDiscreteUtilities(ct);
51+
var endTime = DateTime.UtcNow;
52+
// logging the duration of the cleanup process,
53+
// if the duration in the future becomes consistantly short it implyies that our fixes for excess discrete probabilities and utilities are working and we can remove this background service
54+
_logger.LogInformation("Table cleanup completed successfully in {Duration} ms.", (endTime - startTime).TotalMilliseconds);
55+
}
56+
catch (Exception ex)
57+
{
58+
_logger.LogError(ex, "An error occurred in CleanupLoopAsync.");
59+
}
60+
}
61+
}
62+
}

PrismaDotnetApi/PrismaApi.Application/Interfaces/Services/ITableRebuildingService.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,7 @@ public interface ITableRebuildingService
55
Task RebuildTablesAsync(CancellationToken ct = default);
66
Task RebuildIssuesFromIssueIds(ICollection<Guid> issueIds, CancellationToken ct = default);
77
Task RemoveExcessDiscreteProbabilities(Guid uncertaintyId, CancellationToken ct = default);
8+
Task RemoveExcessDiscreteProbabilities(CancellationToken ct = default);
89
Task RemoveExcessDiscreteUtilities(Guid utilityId, CancellationToken ct = default);
10+
Task RemoveExcessDiscreteUtilities(CancellationToken ct = default);
911
}

PrismaDotnetApi/PrismaApi.Application/Services/TableRebuildingService.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,12 +282,28 @@ public async Task RemoveExcessDiscreteProbabilities(Guid uncertaintyId, Cancella
282282
{
283283
var discreteProbabilities = await FindExcessDiscreteProbabilities(uncertaintyId, ct);
284284
RemoveDiscreteProbabilities(discreteProbabilities);
285+
await DbContext.SaveChangesAsync(ct);
286+
}
287+
288+
public async Task RemoveExcessDiscreteProbabilities(CancellationToken ct = default)
289+
{
290+
var discreteProbabilities = await FindExcessDiscreteProbabilities(ct);
291+
RemoveDiscreteProbabilities(discreteProbabilities);
292+
await DbContext.SaveChangesAsync(ct);
285293
}
286294

287295
public async Task RemoveExcessDiscreteUtilities(Guid utilityId, CancellationToken ct = default)
288296
{
289297
var discreteUtilities = await FindExcessDiscreteUtility(utilityId, ct);
290298
RemoveDiscreteUtilities(discreteUtilities);
299+
await DbContext.SaveChangesAsync(ct);
300+
}
301+
302+
public async Task RemoveExcessDiscreteUtilities(CancellationToken ct = default)
303+
{
304+
var discreteUtilities = await FindExcessDiscreteUtility(ct);
305+
RemoveDiscreteUtilities(discreteUtilities);
306+
await DbContext.SaveChangesAsync(ct);
291307
}
292308

293309
private async Task<IEnumerable<DiscreteProbability>> FindExcessDiscreteProbabilities(Guid uncertaintyId, CancellationToken ct = default)
@@ -307,6 +323,22 @@ private async Task<IEnumerable<DiscreteProbability>> FindExcessDiscreteProbabili
307323
.SelectMany(group => group.Skip(1));
308324
}
309325

326+
private async Task<IEnumerable<DiscreteProbability>> FindExcessDiscreteProbabilities(CancellationToken ct = default)
327+
{
328+
var discreteProbabilities = await DbContext.DiscreteProbabilities
329+
.Include(p => p.ParentOutcomes)
330+
.Include(p => p.ParentOptions)
331+
.ToListAsync(ct);
332+
333+
return discreteProbabilities
334+
.GroupBy(p => (
335+
p.OutcomeId,
336+
ParentOutcomes: string.Join(",", p.ParentOutcomes.Select(o => o.ParentOutcomeId).OrderBy(id => id)),
337+
ParentOptions: string.Join(",", p.ParentOptions.Select(o => o.ParentOptionId).OrderBy(id => id))
338+
))
339+
.SelectMany(group => group.Skip(1));
340+
}
341+
310342
private async Task<IEnumerable<DiscreteUtility>> FindExcessDiscreteUtility(Guid utilityId, CancellationToken ct = default)
311343
{
312344
var discreteUtilities = await DbContext.DiscreteUtilities
@@ -324,6 +356,22 @@ private async Task<IEnumerable<DiscreteUtility>> FindExcessDiscreteUtility(Guid
324356
.SelectMany(group => group.Skip(1));
325357
}
326358

359+
private async Task<IEnumerable<DiscreteUtility>> FindExcessDiscreteUtility(CancellationToken ct = default)
360+
{
361+
var discreteUtilities = await DbContext.DiscreteUtilities
362+
.Include(p => p.ParentOutcomes)
363+
.Include(p => p.ParentOptions)
364+
.ToListAsync(ct);
365+
366+
return discreteUtilities
367+
.GroupBy(p => (
368+
p.ValueMetricId,
369+
ParentOutcomes: string.Join(",", p.ParentOutcomes.Select(o => o.ParentOutcomeId).OrderBy(id => id)),
370+
ParentOptions: string.Join(",", p.ParentOptions.Select(o => o.ParentOptionId).OrderBy(id => id))
371+
))
372+
.SelectMany(group => group.Skip(1));
373+
}
374+
327375
private void RemoveDiscreteProbabilities(IEnumerable<DiscreteProbability> discreteProbabilities)
328376
{
329377
var discreteProbabilityIds = discreteProbabilities.Select(probability => probability.Id).ToList();

0 commit comments

Comments
 (0)