Skip to content

Add --apply-retention to the audit instance - #6078

Draft
ramonsmits wants to merge 1 commit into
masterfrom
audit-apply-retention
Draft

ramonsmits wants to merge 1 commit into
masterfrom
audit-apply-retention

Conversation

@ramonsmits

@ramonsmits ramonsmits commented Oct 30, 2026 •

Copy link
Copy Markdown
Member

A change of ServiceControl.Audit/AuditRetentionPeriod only reaches new messages. Ingestion sets the expiry of each document when it is stored. This PR adds a command that applies the configured retention period to the stored messages.

ServiceControl.Audit.exe --apply-retention
  • IAuditRetentionApplier in the persistence abstraction. Only a persister that stores an expiry per document registers an implementation.
  • RavenDB: a patch by query over ProcessedMessages and SagaSnapshots sets the expiry of each document to its processed time plus the retention period. Progress is logged per batch.
  • SQL Server and PostgreSQL register nothing. Their retention sweep uses the current period, so the stored rows need no update. The command resolves the interface as optional and reports that.
  • The command builds a persistence-only host, like maintenance mode, so it does not need the transport.

Tests: argument parsing for the command, and a RavenDB persistence test that ingests under one retention period, applies another, and checks the expiry of a message and a saga snapshot.

Open points for review:

  • The expiry base. Ingestion uses the ingestion time, which is not stored. The command uses ProcessedAt, the stored time closest to it.
  • The name of the command.

Docs: a matching change to the AuditRetentionPeriod section of the audit configuration page is prepared and follows when the name is final.

A change of ServiceControl.Audit/AuditRetentionPeriod only reaches new messages: ingestion sets the expiry of each document when it is stored. The new command rewrites the expiry of the stored messages and saga snapshots to their processed time plus the configured retention period.

- IAuditRetentionApplier in the persistence abstraction, registered only by a persister that stores an expiry per document
- RavenDB: patch by query over ProcessedMessages and SagaSnapshots, progress reported per batch
- Without a registered implementation the command reports that the persister applies the retention period when it removes expired messages
- Help text, host arguments test, RavenDB persistence test
@ramonsmits
ramonsmits force-pushed the audit-apply-retention branch from 47c6b52 to f33227e Compare October 30, 2026 17:33
@mauroservienti

Copy link
Copy Markdown
Member

Does changing retention on indexed documents causes the indexing engine to reindex them?

@danielmarbach

Copy link
Copy Markdown
Contributor

Does changing retention on indexed documents causes the indexing engine to reindex them?

Document metadata updates, as far as I recall, are regular document indexes and cause incremental index changes

tokenSource.Cancel();
};

using var host = hostBuilder.Build();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't the host support async disposable?

/// <c>ServiceControl.Audit/AuditRetentionPeriod</c>: ingestion sets the expiry of each message when it is
/// stored, so the stored messages keep the old retention period until this command rewrites their expiry.
/// </summary>
class ApplyRetentionCommand : AbstractCommand

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the idea is that this is an optional enforcement because we want to keep the existing expiry mechanism (since that can be seen as a feature), but when someone would like to enforce for old documents the new retention, they can do so?

Comment on lines +20 to +21
The new expiry of each message is its processed time plus the retention period. Messages that are already past the new
expiry are removed by the next expiration run.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The new expiry of each message is its processed time plus the retention period. Messages that are already past the new
expiry are removed by the next expiration run.
The new expiry of each message is its processed time plus the retention period. Messages that are already past the new expiry are removed by the next expiration run.

Comment on lines +23 to +24
Stop the instance before running this command. A persister that applies the retention period when it removes expired
messages needs no update, and the command reports that.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Stop the instance before running this command. A persister that applies the retention period when it removes expired
messages needs no update, and the command reports that.
Stop the instance before running this command. A persister that applies the retention period when it removes expired messages needs no update, and the command reports that.

{
{
"apply-retention",
"Apply the configured audit retention period to the stored messages",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this mention saga audits too?

var documentStore = await documentStoreProvider.GetDocumentStore(cancellationToken);
long updated = 0;

foreach (var collection in Collections)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is the wait only waits for the command to be accepted and then returns is that correct? If true, then when all makes no sense, correct?

from {{collection}}
update
{
this['@metadata']['@expires'] = new Date(new Date(this.ProcessedAt).getTime() + $retentionMilliseconds).toISOString();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should introduce a cutoff date here rather than patching every document in both collections.

Newly ingested documents already have the correct expiration metadata based on the configured retention period. Rewriting those documents unnecessarily creates write and indexing overhead, and potentially changes their expiration because the patch uses ProcessedAt rather than ingestion time.

Could we limit the patch to documents stored before the retention configuration changed? I wonder whether @last-modified would be a better cutoff criterion than ProcessedAt, since audit messages can be ingested after their actual processing time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants