Compare Exchange Overview
-
Compare-exchange items are key/value pairs where the key is a globally unique identifier in the database.
Items are versioned and managed at the cluster level. -
Compare-exchange provides a built-in consensus mechanism for safe coordination across sessions and nodes.
It ensures global consistency in the database and prevents conflicts when multiple clients try to modify or reserve
the same resource, allowing you to:- Enforce global uniqueness (e.g., prevent duplicate usernames or emails).
- Assign work to a single client or reserve a resource once.
- Handle concurrency safely, without external services or custom locking logic.
-
Compare-exchange items are also suitable for storing shared or global values that aren't tied to a specific document - such as configuration flags, feature toggles, or reusable identifiers stored under a unique key.
However, unlike regular documents, compare-exchange provides atomic updates, version-based conflict prevention, and Raft-based consistency for distributed safety. -
Compare-exchange items are not replicated externally to other databases.
-
In this article:
What compare-exchange items are
Compare-exchange items are key/value pairs where the key serves as a unique value across your database.
-
Each compare-exchange item contains:
- A key - A unique string identifier in the database scope.
- A value - Can be any value (a number, string, array, or any valid JSON object).
- Metadata - Optional data that is associated with the compare-exchange item. Must be a valid JSON object.
For example, the metadata can be used to set expiration time for the compare-exchange item.
Learn more in compare-exchange expiration. - Raft index - The compare-exchange item's version.
Any change to the value or metadata will increase this number.
-
Creating and modifying a compare-exchange item follows the same principle as the compare-and-swap operation in multi-threaded systems, but in RavenDB, this concept is applied to a distributed environment across multiple nodes instead of within a single multi-threaded process.
These operations require cluster consensus to ensure consistency. Once consensus is reached, the compare-exchange items are distributed through the Raft algorithm to all nodes in the database group.
Ways to create and manage compare-exchange items
Compare exchange items can be created and managed using any of the following approaches:
-
Document Store Operations
You can create and manage compare-exchange items using document store operations.
For example, see Create items using a store operation. -
Cluster-Wide Sessions
You can create and manage compare-exchange items from within a Cluster-Wide session.
For example, see Create items using a cluster-wide session.
When using a cluster-wide session, the compare-exchange item is created as part of the cluster-wide transaction.
Like any transaction, all operations either succeed as a group or are rolled back.
If the transaction is not committed, the new compare-exchange item will not be stored on any node in the database group. -
Atomic Guards
When creating documents using a cluster-wide session, RavenDB automatically creates Atomic Guards,
which are special compare-exchange items that guarantee ACID transactions.
See Cluster-wide transaction vs. Single-node transaction for a session comparison overview. -
Studio
All compare-exchange items can also be managed from the Compare-Exchange view in the Studio:

- Open the Documents section in the Studio sidebar.
- Click on the Compare-Exchange tab.
- This is a compare-exchange item.
In this view you can create, edit, and delete compare-exchange items.
Why compare-exchange items are not replicated to external databases
-
Each cluster defines its own policies and configurations, and should ideally have sole responsibility for managing its own documents. Read Consistency in a Globally Distributed System to learn more about why global database modeling is more efficient this way.
-
When creating a compare-exchange item, a Raft consensus is required from the nodes in the database group. Externally replicating such data is problematic because the target database may reside within a cluster that is in an unstable state where Raft decisions cannot be made. In such a state, the compare-exchange item will not be persisted in the target database.
-
Conflicts between documents that occur between two databases are resolved using the documents' change-vector. Compare-exchange conflicts cannot be resolved in the same way, as they lack a similar conflict resolution mechanism.
-
Learn more about Replication in RavenDB in Replication overview. For details about what is and what isn't replicated in What is Replicated.
Why not use regular documents to enforce uniqueness
-
You might consider storing a document with a predictable ID (for example, phones/123456) as a way to enforce uniqueness, and then checking for its existence before allowing another document to use the same value.
-
While this might work in a single-node setup or with external replication, it does not reliably enforce uniqueness in a clustered environment.
-
If a node was not part of the cluster when the document was created, it might not be aware of its existence when it comes back online. In such cases, attempting to load the document on this node may return null, leading to duplicate values being inserted.
-
To reliably enforce uniqueness across all cluster nodes, you must use compare-exchange items, which are designed for this purpose and ensure global consistency.
Example I - Email address reservation
The following example shows how to use compare-exchange to create documents with unique values.
The scope is within the database group on a single cluster.
string email = "user@example.com";
User user = new User
{
Email = email
};
using (IDocumentSession session = store.OpenSession())
{
session.Store(user);
// At this point, the user object has a document ID assigned by the session.
// Try to reserve the user email using a compare-exchange item.
// Note: This 'put compare-exchange operation' is not part of the session transaction,
// It is a separate, cluster-wide reservation.
CompareExchangeResult<string> cmpXchgResult
= store.Operations.Send(
// parameters passed to the operation:
// email - the unique key of the compare-exchange item
// user.Id - the value associated with the key
// 0 - pass `0` to ensure the item is created only if it doesn't already exist.
// If a compare-exchange item with the given key already exists, the operation will fail.
new PutCompareExchangeValueOperation<string>(email, user.Id, 0));
if (cmpXchgResult.Successful == false)
throw new Exception("Email is already in use");
// At this point, the email has been successfully reserved/saved.
// We can now save the user document to the database.
session.SaveChanges();
}
Implications:
-
This compare-exchange item was created as an operation rather than with a cluster-wide session.
Thus, ifsession.SaveChangesfails, then the email reservation is Not rolled back automatically.
It is your responsibility to do so. -
The compare-exchange value that was saved can be accessed in a query using the
CmpXchgmethod:
- Query
- Query_async
- DocumentQuery
- RQL
using (var session = store.OpenSession())
{
// Retrieve the user document that has the specified email:
var user = session.Query<User>()
// Access the compare-exchange value using the CmpXchg method:
.Where(x => x.Id == RavenQuery.CmpXchg<string>("ayende@ayende.com"))
.FirstOrDefault();
}
using (var asyncSession = store.OpenAsyncSession())
{
var user = await asyncSession.Query<User>()
.Where(x => x.Id == RavenQuery.CmpXchg<string>("ayende@ayende.com"))
.FirstOrDefaultAsync();
}
using (var session = store.OpenSession())
{
var user = session.Advanced
.DocumentQuery<User>()
.WhereEquals("Id", CmpXchg.Value("ayende@ayende.com"))
.FirstOrDefault();
}
from "Users"
where id() == cmpxchg("ayende@ayende.com")
limit 0, 1 // take the first result
Example II - Reserve a shared resource
In the following example, we use compare-exchange to reserve a shared resource.
The scope is within the database group on a single cluster.
The code also checks for clients which never release resources (i.e. due to failure) by using timeout.
private class SharedResource
{
public DateTime? ReservedUntil { get; set; }
}
public void PrintWork()
{
// Try to get hold of the printer resource
long reservationIndex = LockResource(store, "Printer/First-Floor", TimeSpan.FromMinutes(20));
try
{
// Do some work for the duration that was set (TimeSpan.FromMinutes(20)).
//
// In a distributed system (unlike a multi-threaded app), a process may crash or exit unexpectedly
// without releasing the resource it reserved (i.e. never reaching the 'finally' block).
// This can leave the resource locked indefinitely.
//
// To prevent that, each reservation includes a timeout (TimeSpan.FromMinutes(20)).
// If the process fails or exits, the resource becomes available again once the timeout expires.
//
// Important: Ensure the work completes within the timeout period.
// If it runs longer, another client may acquire the same resource at the same time.
}
finally
{
ReleaseResource(store, "Printer/First-Floor", reservationIndex);
}
}
public long LockResource(IDocumentStore store, string resourceName, TimeSpan duration)
{
while (true)
{
DateTime now = DateTime.UtcNow;
SharedResource resource = new SharedResource
{
ReservedUntil = now.Add(duration)
};
CompareExchangeResult<SharedResource> putResult = store.Operations.Send(
new PutCompareExchangeValueOperation<SharedResource>(resourceName, resource, 0));
if (putResult.Successful)
{
// resourceName wasn't present - we managed to reserve
return putResult.Index;
}
// At this point, another process owns the resource.
// But if that process crashed and never released the resource, the reservation may have expired,
// so we can try to take the lock by overwriting the value using the current index.
if (putResult.Value.ReservedUntil < now)
{
// Time expired - Update the existing key with the new value
CompareExchangeResult<SharedResource> takeLockWithTimeoutResult = store.Operations.Send(
new PutCompareExchangeValueOperation<SharedResource>(
resourceName, resource, putResult.Index
));
if (takeLockWithTimeoutResult.Successful)
{
return takeLockWithTimeoutResult.Index;
}
}
// Wait a little bit and retry
Thread.Sleep(20);
}
}
public void ReleaseResource(IDocumentStore store, string resourceName, long index)
{
CompareExchangeResult<SharedResource> deleteResult = store.Operations.Send(
new DeleteCompareExchangeValueOperation<SharedResource>(resourceName, index));
// We have 2 options here:
// deleteResult.Successful is true - we managed to release resource
// deleteResult.Successful is false - someone else took the lock due to timeout
}
Use cases
Enforce unique usernames or emails
-
Use compare-exchange to enforce global uniqueness in your database even under concurrent operations.
For example, ensure that no two users can register with the same username or email, even if they do so simultaneously on different servers. Compare-exchange guarantees that a specific value can only be claimed once across the cluster reliably and without race conditions. -
✅ Why compare-exchange?
It provides a guaranteed, cluster-wide check for uniqueness. -
How it works:
- When a user registers, the app attempts to create a compare-exchange item like
(key:"emails/john@example.com", value:"users/1-A"). - Only the first attempt to claim this key succeeds.
- Any concurrent or repeated attempts to claim the same key fail automatically.
- When a user registers, the app attempts to create a compare-exchange item like
-
This makes it easy to enforce rules like:
- No two users can register with the same email address.
- No two orders can use the same external reference ID.
Claim a job or task once
-
Use compare-exchange to safely assign client-side jobs or tasks in a distributed system,
ensuring that each task is claimed only once. -
✅ Why compare-exchange?
It provides a reliable, cluster-wide locking mechanism for coordination within your database scope. -
How it works:
- Each worker attempts to create a compare-exchange item like
(key:"locks/job/1234", value:"worker-A"). - The first worker to succeed gets the job.
- Other workers trying to claim the same job will fail - they can back off or retry later.
- Each worker attempts to create a compare-exchange item like
-
This ensures:
- No two workers process the same job.
- Each job runs exactly once, even with multiple competing workers or nodes.
-
Also useful for:
- Implementing mutex-style locks between clients.
- Ensuring that scheduled tasks or batch jobs run only once across the cluster.
Reserve a resource
-
Need to reserve a table in a restaurant app or a seat at an event?
Use compare-exchange to lock the reservation and prevent double booking, even under concurrent access. -
✅ Why compare-exchange?
It gives you a reliable, cluster-wide way to reserve something exactly once - no race conditions, no conflicts. -
How it works:
- Try to create a Compare-Exchange item for the resource
(e.g., key:"reservations/seat/17", value:"user/123"). - If the item doesn't exist, the reservation is successful.
- If it already exists, someone else claimed it - you can show an error or let the user pick another.
- Try to create a Compare-Exchange item for the resource
-
This pattern is useful for:
- Reserving seats, tables, or event slots.
- Assigning support engineers to incoming tickets.
- Allocating limited resources like promotion codes or serial numbers.
-
Only one client can claim the item so your reservation logic stays safe and simple, even under high load.
Prevent double processing
-
Use compare-exchange to make sure an operation runs only once even in a distributed setup.
This is useful for avoiding things like sending the same email twice, processing the same order multiple times,
or executing duplicate actions after retries. -
✅ Why compare-exchange?
It acts as a once-only flag - a lightweight, atomic check to prevent duplicate processing. -
How it works:
- Before running the operation, try to create a compare-exchange key like
processed/orders/9876. - If the key creation succeeds - run the operation.
- If the key already exists - skip processing. It's already been handled.
- Before running the operation, try to create a compare-exchange key like
-
This approach is especially useful in retry scenarios, background jobs, or any flow where idempotency matters.
Run business logic only if data hasn't changed
-
Use compare-exchange as a version guard to ensure the data wasn't modified while you were working on it.
This is useful when applying business logic that depends on the current state of the data - like approving a request, processing a payment, or updating a workflow step. -
✅ Why compare-exchange?
It helps detect changes and prevents acting on stale or outdated data. -
How it works:
- Load the compare-exchange item that tracks the current version or state of the resource.
- After performing your checks and logic, attempt to update the item - but only if the version is still current.
- If the item was modified in the meantime, the update fails and you can abort or retry your business logic.
-
This pattern helps you maintain correctness and consistency in flows that involve multiple steps,
long-running tasks, or user input.
Lock a document for editing
-
In collaborative systems, it's common to allow only one user edit a document at a time.
Use compare-exchange to create a lightweight, distributed lock on the document. -
✅ Why compare-exchange?
It ensures that only one client can acquire the lock - preventing conflicting edits across users or servers. -
How it works:
- When a user starts editing a document (e.g.,
task/72), try to create a compare-exchange item:
(key:"editing/task/72", value:"user/123"). - If the item is created successfully, the user holds the lock.
- Other users attempting the same key will fail and can be blocked, shown a message, or put into read-only mode.
- When editing is done, delete the compare-exchange item to release the lock.
- When a user starts editing a document (e.g.,
-
This is useful for:
- Locking tasks, issues, or shared forms during editing.
- Preventing data loss or conflicts from simultaneous updates.
- Letting users know who’s currently editing a shared resource.
-
Simple to implement and works seamlessly across the cluster.
Add safety to cluster-wide transactions
-
When using cluster-wide sessions to handle documents, RavenDB automatically creates internal compare-exchange items, called atomic guards, to enforce atomic document modifications. These items coordinate access and prevent conflicting writes across nodes.
-
✅ Why compare-exchange?
It provides a Raft-based coordination mechanism that ensures consistency and safety during multi-node transactions. -
How it works:
- When you store or update a document in a cluster-wide session,
RavenDB creates an atomic guard to track the document’s version across the cluster. - If another session modifies the document in the meantime,
your transaction fails with aConcurrencyException, ensuring data consistency.
- When you store or update a document in a cluster-wide session,
-
This protects you from:
- Writing over documents that were modified by other sessions.
- Acting on stale data in a distributed environment.
- Violating ACID guarantees in multi-node clusters.
-
You don’t need to manage these guards manually -
RavenDB handles everything automatically when you use a session in cluster-wide mode.