Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/Jhoose.Security/Configuration/ReportingOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ public class ReportingOptions
{
public int RetainDays { get; set; } = 30;

/// <summary>
/// Maximum rows the purge job deletes per batch. Set to 0 to disable batching
/// (issue a single unbounded DELETE, matching pre-batching behavior).
/// Only the SQL provider honors this; ElasticSearch handles bulk deletes natively.
/// </summary>
public int PurgeBatchSize { get; set; } = 5000;

public string UseProvider { get; set; } = string.Empty;

public string ConnectionString { get; set; } = string.Empty;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,27 @@ await sqlHelper.ExecuteStoredProcedure("GetSecurityReportSummary", parameters, (
}
}

public async Task<int> PurgeReporingData(DateTime beforeDate)
public async Task<int> PurgeReporingData(DateTime beforeDate, int? batchSize = null)
{
try {
var sqlCommand = "DELETE FROM SecurityReportTo WHERE RecievedAt < @BeforeDate";
// Unbatched path preserves prior behavior for callers that pass no batch size.
if (!batchSize.HasValue || batchSize.Value <= 0)
{
var sqlCommand = "DELETE FROM SecurityReportTo WHERE RecievedAt < @BeforeDate";

return await sqlHelper.ExecuteNonQuery(
sqlCommand,
sqlHelper.CreateParameter<DateTime>("BeforeDate", SqlDbType.DateTime, beforeDate));
}

// Single batch: caller is expected to loop until rows == 0. Keeps each
// statement's transaction small enough to commit before the SqlCommand
// timeout, instead of rolling back a multi-hour DELETE.
var batchedSql = "DELETE TOP (@BatchSize) FROM SecurityReportTo WHERE RecievedAt < @BeforeDate";

return await sqlHelper.ExecuteNonQuery(
sqlCommand,
batchedSql,
sqlHelper.CreateParameter<int>("BatchSize", SqlDbType.Int, batchSize.Value),
sqlHelper.CreateParameter<DateTime>("BeforeDate", SqlDbType.DateTime, beforeDate));
}
catch (Exception ex)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,13 @@ public async Task<DashboardSummary> GetDashboardSummary(DashboardSummary summary
return summary;
}

public async Task<int> PurgeReporingData(DateTime beforeDate)
public async Task<int> PurgeReporingData(DateTime beforeDate, int? batchSize = null)
{
// batchSize is ignored: Elasticsearch DeleteByQuery handles large deletions
// server-side without the rollback risk SQL has, so the caller's batching
// loop is unnecessary here. The parameter exists only to satisfy the interface.
_ = batchSize;

var response = await this.client.Value.DeleteByQueryAsync<ReportTo<IReportToBody>>(d => d.Query(query => query.Bool(b => b.Must(m => m.Range(r => r.DateRange(dr => dr.Field(f => f.RecievedAt).Gte(beforeDate)))))));

var deletedCount = response.Deleted ?? 0;
Expand Down
10 changes: 9 additions & 1 deletion src/Jhoose.Security/Features/Reporting/IReportingRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,15 @@ public interface IReportingRepository

Task<DashboardSummary> GetDashboardSummary(DashboardSummary summary);

Task<int> PurgeReporingData(DateTime beforeDate);
/// <summary>
/// Deletes reporting rows older than <paramref name="beforeDate"/>.
/// When <paramref name="batchSize"/> is provided and greater than zero, providers
/// that support it should delete at most that many rows per call so callers can
/// loop and bound the work each query does (avoiding command-timeout rollbacks on
/// large tables). When null, the call behaves as before and deletes everything in
/// one statement.
/// </summary>
Task<int> PurgeReporingData(DateTime beforeDate, int? batchSize = null);

Task<CspSearchResults> Search(CspSearchParams searchParams);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public class PurgeReporintgDataJob : ScheduledJobBase
private readonly IReportingRepositoryFactory reportingRepositoryFactory;
private readonly IOptions<ReportingOptions> options;
private readonly ILogger<PurgeReporintgDataJob> logger;
private bool stopSignaled;

public PurgeReporintgDataJob(IReportingRepositoryFactory reportingRepositoryFactory,
IOptions<ReportingOptions> options,
Expand All @@ -24,8 +25,11 @@ public PurgeReporintgDataJob(IReportingRepositoryFactory reportingRepositoryFact
this.reportingRepositoryFactory = reportingRepositoryFactory;
this.options = options;
this.logger = logger;
this.IsStoppable = true;
}

public override void Stop() => stopSignaled = true;

public override string Execute()
{
var reportingRepository = reportingRepositoryFactory.GetReportingRepository();
Expand All @@ -42,9 +46,37 @@ public override string Execute()
}

var beforeDate = DateTime.UtcNow.AddDays(options.Value.RetainDays * -1);
var purged = reportingRepository.PurgeReporingData(beforeDate).Result;
var batchSize = options.Value.PurgeBatchSize;

// PurgeBatchSize <= 0 disables batching and preserves the original
// single-DELETE behavior for anyone who relied on it.
if (batchSize <= 0)
{
var purgedOnce = reportingRepository.PurgeReporingData(beforeDate).Result;
return $"Purged {purgedOnce} records, from before {beforeDate}";
}

var totalPurged = 0;
var batches = 0;

OnStatusChanged($"Purging reporting rows older than {beforeDate:u} in batches of {batchSize}...");

while (!stopSignaled)
{
var purgedInBatch = reportingRepository.PurgeReporingData(beforeDate, batchSize).Result;
if (purgedInBatch <= 0)
{
break;
}

totalPurged += purgedInBatch;
batches++;
OnStatusChanged($"Batch {batches}: purged {purgedInBatch} (total {totalPurged}).");
}

return $"Purged {purged} records, from before {beforeDate}";
return stopSignaled
? $"Stopped. Purged {totalPurged} records, from before {beforeDate} across {batches} batches."
: $"Purged {totalPurged} records, from before {beforeDate} across {batches} batches.";
}
catch (Exception ex)
{
Expand Down