Skip to main content

Enforce Revisions Configuration Operation

Resource consumption

  • Large databases may contain many revisions that will be deleted when the current configuration is enforced.
    Running the operation can delete them all in a single enforcement run and may require substantial server resources. Schedule it accordingly.

  • On large datasets, use MaxOpsPerSecond to throttle the operation and reduce its resource consumption.

  • Revisions in collections that no current configuration applies to may be deleted. Make sure your revisions configuration includes the default settings and collection-specific configurations needed to keep the revisions you want to preserve. Force-created revisions may also be removed when IncludeForceCreated is set to true.

Enforce configuration on all collections

In this example, the current revisions configuration is enforced on the revisions of all collections.

// Define the enforce revisions configuration operation
var enforceConfigurationOp = new EnforceRevisionsConfigurationOperation();

// Execute the operation by passing it to Operations.Send
// The send method returns the operation object, NOT the operation result
Operation operation = store.Operations.Send(enforceConfigurationOp);

// Wait for the operation to complete and get its result
var result = operation.WaitForCompletion<EnforceConfigurationResult>(TimeSpan.FromSeconds(30));

Enforce configuration on specific collections

Set the Collections parameter to enforce the configuration only on revisions in the specified collections.
When Collections is not set, is null, or is an empty array, the operation is applied to all collections.

var enforceConfigurationOp = new EnforceRevisionsConfigurationOperation(
new EnforceRevisionsConfigurationOperation.Parameters
{
// The configuration will be enforced only on these collections
Collections = new[] { "Users", "Products" }
});

Operation operation = store.Operations.Send(enforceConfigurationOp);
var result = (EnforceConfigurationResult)operation.WaitForCompletion(TimeSpan.FromSeconds(30));

Include force-created revisions

By default, force-created revisions are preserved and are not removed by the operation.

Set IncludeForceCreated to true to allow those force-created revisions to be removed as well.

var enforceConfigurationOp = new EnforceRevisionsConfigurationOperation(
new EnforceRevisionsConfigurationOperation.Parameters
{
// Force-created revisions will also be subject to the configuration rules
IncludeForceCreated = true
});

Operation operation = store.Operations.Send(enforceConfigurationOp);
var result = (EnforceConfigurationResult)operation.WaitForCompletion(TimeSpan.FromSeconds(30));

Throttle the operation

Running the operation on a large database may consume substantial server resources and affect server performance.

Set MaxOpsPerSecond to limit the number of documents processed per second,
throttling the operation to reduce its resource consumption.

var enforceConfigurationOp = new EnforceRevisionsConfigurationOperation(
new EnforceRevisionsConfigurationOperation.Parameters
{
// Process at most 100 documents per second
MaxOpsPerSecond = 100
});

Operation operation = store.Operations.Send(enforceConfigurationOp);
var result = (EnforceConfigurationResult)operation.WaitForCompletion(TimeSpan.FromSeconds(30));

MaxOpsPerSecond must be greater than 0.
Setting it to 0 or a negative value throws an InvalidOperationException.

Resume an interrupted operation

  • On a large database, enforcing the revisions configuration can take a long time. If the run is interrupted before it completes, for example by a server restart or by cancelling the operation, it does not return a final result.

  • To avoid starting over from the beginning, you can resume the operation from the latest progress values reported before the interruption. To do this, capture these values from the operation's progress while it runs:

    • Subscribe to Operation.OnProgressChanged and keep the latest EnforceConfigurationResult value.
    • Each progress update is an EnforceConfigurationResult that includes:
      LastProcessedEtags, EtagBarriersUsed, and NodeTags.
  • Progress is reported periodically, so an operation run that was interrupted very early may not yet have usable values to resume from.

  • Because etags are node-local, a resumed operation must run on the same node that produced them.
    Keep the NodeTags values from the progress; they identify that node, and the server validates them on resume
    (throwing if the operation runs on a different node).

EnforceConfigurationResult latestProgress = null;
EnforceConfigurationResult finalResult;

// Start the operation
var operation = store.Operations.Send(
new EnforceRevisionsConfigurationOperation());

// Capture progress as it streams in, so we have continuation values if the run is interrupted.
// EnforceConfigurationResult is reported as progress and carries the continuation values.
operation.OnProgressChanged += (_, progress) =>
{
if (progress is EnforceConfigurationResult result)
latestProgress = result;
};

try
{
finalResult = operation.WaitForCompletion<EnforceConfigurationResult>();
}
catch (Exception)
{
// The run was interrupted and returned no final result.
// If we captured progress before the interruption, resume from the latest values.
if (latestProgress is null)
throw;

var resumeOperation = new EnforceRevisionsConfigurationOperation(
new EnforceRevisionsConfigurationOperation.Parameters
{
ContinuationParameters = new RevisionsOperationContinuationParameters
{
StartFromEtags = latestProgress.LastProcessedEtags,
EtagBarriers = latestProgress.EtagBarriersUsed,
// NodeTags are validated on resume: the operation must run on the
// node that produced the etags, since etags are node-local.
NodeTags = latestProgress.NodeTags
}
});

finalResult = store.Operations
.Send(resumeOperation)
.WaitForCompletion<EnforceConfigurationResult>();
}

// finalResult holds the outcome from whichever path completed (initial run or resume)
Console.WriteLine($"Removed {finalResult.RemovedRevisions} revisions");

Capture early progress

  • Operation progress notifications are delivered through the Changes API.
    Operation.OnProgressChanged is the simple way to consume it.

  • Because the handler above is attached after the operation starts, the earliest progress notifications can be missed. To capture progress from the very beginning, subscribe to operation changes via the Changes API before starting the operation. Learn more in How to subscribe to operation changes.

Syntax

// Available overloads:
// ====================

// Enforce the configuration using default parameters (all collections)
public EnforceRevisionsConfigurationOperation();

// Enforce the configuration using the specified parameters
public EnforceRevisionsConfigurationOperation(Parameters parameters);

ParameterTypeDescription
parametersParametersThe parameters defining how the configuration is enforced. See the Parameters class below.
public sealed class Parameters : RevisionsOperationParameters
{
public bool IncludeForceCreated { get; set; } = false;
public int? MaxOpsPerSecond { get; set; } // setter throws if value <= 0
}

public class RevisionsOperationParameters
{
public string[] Collections { get; set; } = null;
public RevisionsOperationContinuationParameters ContinuationParameters { get; set; }
}

ParameterTypeDescription
IncludeForceCreatedbooltrue - Force-created revisions may also be removed by the operation.
false (default) - Force-created revisions are not included in deletion paths that protect them.
MaxOpsPerSecondint?Limits the number of documents processed per second.
null (default) - no throttling.
Must be greater than 0, otherwise an InvalidOperationException is thrown.
Collectionsstring[]The collections the operation applies to.
null or empty array - the operation applies to all collections.
ContinuationParametersRevisionsOperationContinuationParametersOptional state used to resume an interrupted revisions operation. See Resume an interrupted operation.
public class RevisionsOperationContinuationParameters
{
public Dictionary<string, long> StartFromEtags { get; set; }
public Dictionary<string, long> EtagBarriers { get; set; }
public Dictionary<string, string> NodeTags { get; set; }
}

The operation returns an EnforceConfigurationResult object:

public sealed class EnforceConfigurationResult : OperationResult
{
// The number of revisions removed by the operation
public int RemovedRevisions { get; set; }
}

public abstract class OperationResult
{
// The number of revisions that were scanned by the operation
public int ScannedRevisions { get; set; }

// The number of documents that were scanned by the operation
public int ScannedDocuments { get; set; }

// Warnings collected during the operation
public Dictionary<string, string> Warnings { get; set; }

public Dictionary<string, long> LastProcessedEtags { get; set; }
public Dictionary<string, long> EtagBarriersUsed { get; set; }
public Dictionary<string, string> NodeTags { get; set; }
}

In this article