Enforce Revisions Configuration Operation
-
The revision configuration rules are usually applied to a document's revisions only when the document is modified.
UseEnforceRevisionsConfigurationOperationto apply the current revisions configuration to existing revisions, instead of waiting for each document to be modified. -
Running this operation scans existing revisions and enforces the current rules immediately, deleting eligible revisions that should be purged according to the configuration.
-
The operation can be applied to all collections (default) or to a specified set of collections.
-
By default, the operation is applied to the default database.
To operate on a different database, see switch operations to a different database. -
This article explains how to run the operation from the Client API.
To run it from Studio, see Enforce configuration in the Document Revisions settings view. -
In this article:
-
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
MaxOpsPerSecondto 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
IncludeForceCreatedis set totrue.
Enforce configuration on all collections
In this example, the current revisions configuration is enforced on the revisions of all collections.
- Sync
- Async
// 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));
// Define the enforce revisions configuration operation
var enforceConfigurationOp = new EnforceRevisionsConfigurationOperation();
// Execute the operation by passing it to Operations.SendAsync
// The send method returns the operation object, NOT the operation result
Operation operation = await store.Operations.SendAsync(enforceConfigurationOp);
// Wait for the operation to complete and get its result
var result = await operation.WaitForCompletionAsync<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.
- Sync
- Async
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));
var enforceConfigurationOp = new EnforceRevisionsConfigurationOperation(
new EnforceRevisionsConfigurationOperation.Parameters
{
// The configuration will be enforced only on these collections
Collections = new[] { "Users", "Products" }
});
Operation operation = await store.Operations.SendAsync(enforceConfigurationOp);
var result = (EnforceConfigurationResult)await operation.WaitForCompletionAsync(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.
- Sync
- Async
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));
var enforceConfigurationOp = new EnforceRevisionsConfigurationOperation(
new EnforceRevisionsConfigurationOperation.Parameters
{
// Force-created revisions will also be subject to the configuration rules
IncludeForceCreated = true
});
Operation operation = await store.Operations.SendAsync(enforceConfigurationOp);
var result = (EnforceConfigurationResult)await operation.WaitForCompletionAsync(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.
- Sync
- Async
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));
var enforceConfigurationOp = new EnforceRevisionsConfigurationOperation(
new EnforceRevisionsConfigurationOperation.Parameters
{
// Process at most 100 documents per second
MaxOpsPerSecond = 100
});
Operation operation = await store.Operations.SendAsync(enforceConfigurationOp);
var result = (EnforceConfigurationResult)await operation.WaitForCompletionAsync(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.OnProgressChangedand keep the latestEnforceConfigurationResultvalue. - Each progress update is an
EnforceConfigurationResultthat includes:
LastProcessedEtags,EtagBarriersUsed, andNodeTags.
- Subscribe to
-
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 theNodeTagsvalues from the progress; they identify that node, and the server validates them on resume
(throwing if the operation runs on a different node).
- Sync
- Async
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");
EnforceConfigurationResult latestProgress = null;
EnforceConfigurationResult finalResult;
// Start the operation
var operation = await store.Operations.SendAsync(
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 = await operation.WaitForCompletionAsync<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
}
});
var resumedOperation = await store.Operations.SendAsync(resumeOperation);
finalResult = await resumedOperation.WaitForCompletionAsync<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.OnProgressChangedis 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);
| Parameter | Type | Description |
|---|---|---|
| parameters | Parameters | The 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; }
}
| Parameter | Type | Description |
|---|---|---|
| IncludeForceCreated | bool | true - Force-created revisions may also be removed by the operation.false (default) - Force-created revisions are not included in deletion paths that protect them. |
| MaxOpsPerSecond | int? | Limits the number of documents processed per second.null (default) - no throttling.Must be greater than 0, otherwise an InvalidOperationException is thrown. |
| Collections | string[] | The collections the operation applies to.null or empty array - the operation applies to all collections. |
| ContinuationParameters | RevisionsOperationContinuationParameters | Optional 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; }
}