Configure Optimistic Concurrency
-
This article explains optimistic concurrency in RavenDB, focusing on how optimistic concurrency behaves in sessions operating with single-node transactions and how to configure it.
-
By default, single-node sessions do not perform concurrency checks,
so concurrent updates to the same document follow the "Last Write Wins" strategy, which may lead to lost updates. -
Enabling optimistic concurrency ensures RavenDB detects and prevents conflicting updates,
preserving data integrity by rejecting changes when a document has been modified by another session. -
RavenDB supports three modes of optimistic concurrency,
allowing you to choose a level of conflict validation that suits your application. -
The optimistic concurrency mode can be configured at different scopes:
- Globally, for all sessions under a document store.
- Per session, to apply it selectively.
- Per document, to override the session-level settings.
Optimistic concurrency - Overview
-
A session in RavenDB typically follows these steps:
-
Load or query for documents from the database.
Unless Tracking is explicitly disabled, these documents are automatically Tracked by the session.
When tracked, the session stores the document's Change vector (a unique version identifier) in memory.Tracking applies to:
- Loaded documents:
Retrieved explicitly using methods likesession.Load()orsession.LoadStartingWith(). - Queried documents:
Returned as part of query results usingsession.Query(). - Included documents:
Retrieved as part of anInclude()clause, where related documents are included alongside results. - Created documents:
Stored in the session viasession.Store().
Note: Created documents are tracked but have no change vector until saved to the database.
On save, the server verifies that no document with the same ID already exists.
- Loaded documents:
-
Modify documents in memory, such as allowing the user to edit them via application forms.
-
Save changes with
SaveChanges(), persisting updates to the database.
-
-
Because these actions span multiple requests (loading/querying happens separately from saving), another session may modify the same documents during this time - between when you loaded the document into your session and when the changes are saved.
-
This can cause your changes to unintentionally overwrite newer updates, resulting in a lost update.
Optimistic concurrency offers a mechanism to detect and prevent conflicts, ensuring your changes are only saved if the document hasn’t been modified elsewhere since it was loaded by your session.
Default concurrency behavior with single-node transactions
By default, sessions using single-node transactions do not perform optimistic concurrency checks:
-
Silent overwrites:
Documents are saved without validating whether the version loaded by the session is still current,
allowing updates to be applied unconditionally. -
Last write wins:
If two sessions modify the same document, the lastSaveChanges()call overwrites earlier changes. -
Suitability:
This approach works well when overwriting changes is acceptable,
but it can cause lost updates if concurrent modifications go undetected.
Optimistic concurrency with single-node transactions
To prevent lost updates and conflicting writes,
sessions operating with single-node transactions can enable optimistic concurrency checks:
-
Change vector validation:
WhenSaveChanges()is called, the session sends the document’s change vector to the server. The server compares the change vector stored in the database against the one provided by your session to determine whether the document has been modified by another client or session since it was loaded into your session. -
Conflict detection:
If the document has been modified by another session/client during this time, RavenDB rejects the operation and raises aConcurrencyException. This gives your application control over how to handle the conflict - whether by reloading the document, merging the changes, or canceling the operation. -
Optimistic concurrency modes:
Optimistic concurrency can be configured to validate only modified documents or both modified and read-only documents. Learn more in the Optimistic concurrency modes section below.
Concurrency control in cluster-wide transactions
When the session operates in Cluster-wide mode,
concurrency control is managed by the cluster transaction mechanism:
-
RavenDB coordinates cluster-wide writes through the Raft protocol and validates document changes as part of the transaction process. To ensure consistency across the cluster, document writes use Atomic guards.
If another cluster-wide transaction modifies the same document first,SaveChanges()will fail with aConcurrencyException, allowing your application to handle the conflict. -
For more information, see Cluster Transaction - Overview.
Optimistic concurrency - Modes
-
When working with a session that uses Single-node transactions, you can configure optimistic concurrency to determine whether the server validates document versions and raises exceptions for conflicting updates.
-
RavenDB offers three modes, each providing a different level of conflict protection.
None (Default behavior, Last write wins)
In the default mode, OptimisticConcurrencyMode.None, no version checks are performed.
-
Unconditional saves:
Each PUT and DELETE operation sent duringSaveChanges()is transmitted without a change vector,
allowing the server to apply changes unconditionally. -
Last write wins:
If two sessions modify the same document and both callSaveChanges(),
the most recent save silently overwrites earlier changes, potentially resulting in a lost update.
Choose this mode when:
- Write conflicts are rare or acceptable.
- High throughput is more important than enforcing strict concurrency checks.
Writes
When using OptimisticConcurrencyMode.Writes,
the session performs version checks on all modified documents:
-
Change vector inclusion:
The session sends the document’s change vector with all PUT and DELETE operations duringSaveChanges(). -
Conflict Detection:
The server compares the change vector sent by the session with the document's current version in the database. If the document has been modified by another session in the meantime, the server rejects the operation and raises aConcurrencyException. -
Protection against overwrites:
Changes are persisted only if the document's change vector matches the version it had when it was loaded into your session, ensuring no unintended overwrites occur.
This mode is ideal for most scenarios requiring write protection as it:
- Detects and prevents conflicting updates effectively.
- Maintains good performance by not sending change vectors for documents that were loaded but not modified in the session.
WritesAndReads
When using OptimisticConcurrencyMode.WritesAndReads,
the session extends the behavior of Writes to include ALL tracked documents, even those that were not modified.
-
Change vector inclusion:
DuringSaveChanges(), the session sends the change vector of ALL tracked documents to the server,
including both modified and unmodified documents. -
Conflict Detection:
If the server detects that a tracked document - whether modified or not - has been updated by another session, it rejects the save operation and throws aConcurrencyException. This prevents unintended reliance on outdated read-only documents. -
Strict Consistency:
This mode ensures consistency across all documents tracked by the session, protecting against subtle issues caused by documents that were only "read" in your session but were modified externally.
Use this mode in scenarios where:
- The correctness of business operations relies on the state of read-only documents.
- Strict protection against concurrent changes is required for both modified and unmodified documents.
- Consistency and correctness are prioritized over performance.
Configure globally
-
Use the store.Conventions.OptimisticConcurrencyMode convention to set the default optimistic concurrency mode for ALL sessions created under the same document store.
-
You can override this setting for individual sessions if needed. See Configure per session for more details.
// Set optimistic concurrency mode for all sessions opened under this document store
// Set to 'Writes' mode
store.Conventions.OptimisticConcurrencyMode = OptimisticConcurrencyMode.Writes;
// Sessions that do not set an explicit mode will inherit from the conventions
using (IDocumentSession session = store.OpenSession())
{
OptimisticConcurrencyMode mode = session.Advanced.OptimisticConcurrencyMode;
// returns 'Writes'
}
Configure per session
-
Optimistic concurrency can be configured for a specific session,
overriding the mode inherited from the document store conventions. -
Two ways to set the mode for a session:
- Set
OptimisticConcurrencyModeviaSessionOptionswhen opening the session. - Set
session.Advanced.OptimisticConcurrencyModeon an already-opened session.
- Set
Setting mode to Writes
- set_via_session_options
- session_options_async
- set_via_advanced_properties
- session_advanced_async
// Configure optimistic concurrency mode for this session only
using (IDocumentSession session = store.OpenSession(new SessionOptions
{
// Set to 'Writes' mode in the session options
OptimisticConcurrencyMode = OptimisticConcurrencyMode.Writes
}))
{
// Store a new document and save it
// On save, the server verifies that no document with that ID already exists
Product product = new Product { Name = "Some Name" };
session.Store(product, "products/999");
session.SaveChanges();
// The document is modified externally by another session
using (IDocumentSession otherSession = store.OpenSession())
{
Product otherProduct = otherSession.Load<Product>("products/999");
otherProduct.Name = "Other Name";
otherSession.SaveChanges();
}
// Attempting to save 'products/999' without reloading
// will throw a 'ConcurrencyException' because the document was modified
// by another session since it was last saved in this session.
product.Name = "Better Name";
session.SaveChanges(); // Throws 'ConcurrencyException'
}
// Configure optimistic concurrency mode for this session only
using (IAsyncDocumentSession asyncSession = store.OpenAsyncSession(new SessionOptions
{
// Set to 'Writes' mode in the session options
OptimisticConcurrencyMode = OptimisticConcurrencyMode.Writes
}))
{
// Store a new document and save it
// On save, the server verifies that no document with that ID already exists
Product product = new Product { Name = "Some Name" };
await asyncSession.StoreAsync(product, "products/999");
await asyncSession.SaveChangesAsync();
// The document is modified externally by another session
using (IAsyncDocumentSession otherAsyncSession = store.OpenAsyncSession())
{
Product otherProduct = await otherAsyncSession.LoadAsync<Product>("products/999");
otherProduct.Name = "Other Name";
await otherAsyncSession.SaveChangesAsync();
}
// Attempting to save 'products/999' without reloading
// will throw a 'ConcurrencyException' because the document was modified
// by another session since it was last saved in this session.
product.Name = "Better Name";
await asyncSession.SaveChangesAsync(); // Throws 'ConcurrencyException'
}
// Configure optimistic concurrency mode for this session only
using (IDocumentSession session = store.OpenSession())
{
// Set to 'Writes' mode in the 'session.Advanced' properties
session.Advanced.OptimisticConcurrencyMode = OptimisticConcurrencyMode.Writes;
// Store a new document and save it
// On save, the server verifies that no document with that ID already exists
Product product = new Product { Name = "Some Name" };
session.Store(product, "products/999");
session.SaveChanges();
// The document is modified externally by another session
using (IDocumentSession otherSession = store.OpenSession())
{
Product otherProduct = otherSession.Load<Product>("products/999");
otherProduct.Name = "Other Name";
otherSession.SaveChanges();
}
// Attempting to save 'products/999' without reloading
// will throw a 'ConcurrencyException' because the document was modified
// by another session since it was last saved in this session.
product.Name = "Better Name";
session.SaveChanges(); // Throws 'ConcurrencyException'
}
// Configure optimistic concurrency mode for this session only
using (IAsyncDocumentSession asyncSession = store.OpenAsyncSession())
{
// Set to 'Writes' mode in the 'session.Advanced' properties
asyncSession.Advanced.OptimisticConcurrencyMode = OptimisticConcurrencyMode.Writes;
// Store a new document and save it
// On save, the server verifies that no document with that ID already exists
Product product = new Product { Name = "Some Name" };
await asyncSession.StoreAsync(product, "products/999");
await asyncSession.SaveChangesAsync();
// The document is modified externally by another session
using (IAsyncDocumentSession otherAsyncSession = store.OpenAsyncSession())
{
Product otherProduct = await otherAsyncSession.LoadAsync<Product>("products/999");
otherProduct.Name = "Other Name";
await otherAsyncSession.SaveChangesAsync();
}
// Attempting to save 'products/999' without reloading
// will throw a 'ConcurrencyException' because the document was modified
// by another session since it was last saved in this session.
product.Name = "Better Name";
await asyncSession.SaveChangesAsync(); // Throws 'ConcurrencyException'
}
Setting mode to WritesAndReads
- set_via_session_options
- session_options_async
- set_via_advanced_properties
- session_advanced_async
// Configure optimistic concurrency mode for this session only
using (IDocumentSession session = store.OpenSession(new SessionOptions
{
// 'WritesAndReads' checks all tracked documents, both modified and unmodified
OptimisticConcurrencyMode = OptimisticConcurrencyMode.WritesAndReads
}))
{
// Load documents, the session will track them
// 'products/999' is tracked, will Not be modified
Product readOnlyProduct = session.Load<Product>("products/999");
// 'products/111' is tracked, will be modified
Product productToUpdate = session.Load<Product>("products/111")
productToUpdate.Name = "Updated Name";
// Document 'products/999' is modified externally by another session
using (IDocumentSession otherSession = store.OpenSession())
{
Product otherProduct = otherSession.Load<Product>("products/999");
otherProduct.Name = "Other Name";
otherSession.SaveChanges();
}
// Attempting to save will throw a 'ConcurrencyException'
// because even though 'products/999' was Not modified in this session,
// it was modified by another session since it was loaded.
session.SaveChanges(); // Throws 'ConcurrencyException'
}
// Configure optimistic concurrency mode for this session only
using (IAsyncDocumentSession asyncSession = store.OpenAsyncSession(new SessionOptions
{
// 'WritesAndReads' also checks unmodified tracked documents
OptimisticConcurrencyMode = OptimisticConcurrencyMode.WritesAndReads
}))
{
// Load documents, the session will track them
// 'products/999' is tracked, will Not be modified
Product readOnlyProduct = await asyncSession.LoadAsync<Product>("products/999");
// 'products/111' is tracked, will be modified
Product productToUpdate = await asyncSession.LoadAsync<Product>("products/111");
productToUpdate.Name = "Updated Name";
// Document 'products/999' is modified externally by another session
using (IAsyncDocumentSession otherAsyncSession = store.OpenAsyncSession())
{
Product otherProduct = await otherAsyncSession.LoadAsync<Product>("products/999");
otherProduct.Name = "Other Name";
await otherAsyncSession.SaveChangesAsync();
}
// Attempting to save will throw a 'ConcurrencyException'
// because even though 'products/999' was Not modified in this session,
// it was modified by another session since it was loaded.
await asyncSession.SaveChangesAsync(); // Throws 'ConcurrencyException'
}
// Configure optimistic concurrency mode for this session only
using (IDocumentSession session = store.OpenSession())
{
// Load documents, the session will track them
// 'WritesAndReads' also checks unmodified tracked documents
session.Advanced.OptimisticConcurrencyMode = OptimisticConcurrencyMode.WritesAndReads;
// 'products/999' is tracked, will Not be modified
Product readOnlyProduct = session.Load<Product>("products/999");
// 'products/111' is tracked, will be modified
Product productToUpdate = session.Load<Product>("products/111");
productToUpdate.Name = "Updated Name";
// Document 'products/999' is modified externally by another session
using (IDocumentSession otherSession = store.OpenSession())
{
Product otherProduct = otherSession.Load<Product>("products/999");
otherProduct.Name = "Other Name";
otherSession.SaveChanges();
}
// Attempting to save will throw a 'ConcurrencyException'
// because even though 'products/999' was Not modified in this session,
// it was modified by another session since it was loaded.
session.SaveChanges(); // Throws 'ConcurrencyException'
}
// Configure optimistic concurrency mode for this session only
using (IAsyncDocumentSession asyncSession = store.OpenAsyncSession())
{
// Load documents, the session will track them
// 'WritesAndReads' also checks unmodified tracked documents
asyncSession.Advanced.OptimisticConcurrencyMode = OptimisticConcurrencyMode.WritesAndReads;
// 'products/999' is tracked, will Not be modified
Product readOnlyProduct = await asyncSession.LoadAsync<Product>("products/999");
// 'products/111' is tracked, will be modified
Product productToUpdate = await asyncSession.LoadAsync<Product>("products/111");
productToUpdate.Name = "Updated Name";
// Document 'products/999' is modified externally by another session
using (IAsyncDocumentSession otherAsyncSession = store.OpenAsyncSession())
{
Product otherProduct = await otherAsyncSession.LoadAsync<Product>("products/999");
otherProduct.Name = "Other Name";
await otherAsyncSession.SaveChangesAsync();
}
// Attempting to save will throw a 'ConcurrencyException'
// because even though 'products/999' was Not modified in this session,
// it was modified by another session since it was loaded.
await asyncSession.SaveChangesAsync();
}
Disable for specific document (when enabled on session)
-
Optimistic concurrency can be disabled when storing a specific document,
even when it is enabled for an entire session (or globally). -
This is done by passing
nullas a change vector value to the Store method.
- Sync
- Async
using (IDocumentSession session = store.OpenSession())
{
// Store new document 'products/999'
session.Store(new Product { Name = "Some Name" }, id: "products/999");
session.SaveChanges();
}
using (IDocumentSession session = store.OpenSession())
{
// Enable optimistic concurrency for the session
session.Advanced.OptimisticConcurrencyMode = OptimisticConcurrencyMode.Writes;
// Store the same document
// Pass 'null' as the change vector to turn OFF optimistic concurrency for this document
session.Store(
new Product { Name = "Some Other Name" },
changeVector: null,
id: "products/999"
);
// This will NOT throw a ConcurrencyException, and the document will be saved
session.SaveChanges();
}
using (IAsyncDocumentSession asyncSession = store.OpenAsyncSession())
{
// Store new document 'products/999'
await asyncSession.StoreAsync(new Product { Name = "Some Name" }, id: "products/999");
await asyncSession.SaveChangesAsync();
}
using (IAsyncDocumentSession asyncSession = store.OpenAsyncSession())
{
// Enable optimistic concurrency for the session
asyncSession.Advanced.OptimisticConcurrencyMode = OptimisticConcurrencyMode.Writes;
// Store the same document
// Pass 'null' as the change vector to turn OFF optimistic concurrency for this document
await asyncSession.StoreAsync(
new Product { Name = "Some Other Name" },
changeVector: null,
id: "products/999"
);
// This will NOT throw a ConcurrencyException, and the document will be saved
await asyncSession.SaveChangesAsync();
}
Enable for specific document (when disabled on session)
-
Optimistic concurrency can be enabled when storing a specific document,
even when it is disabled for an entire session (or globally). -
This is done by passing
string.Emptyas the change vector value to the Store method.
Setting the change vector to an empty string will cause RavenDB to ensure that this document is a new one and doesn't already exist. AConcurrencyExceptionwill be thrown if the document already exists. -
If you do not provide a change vector or if the change vector is
null, optimistic concurrency will be disabled.
- Sync
- Async
using (IDocumentSession session = store.OpenSession())
{
// Store new document 'products/999'
session.Store(new Product { Name = "Some Name" }, id: "products/999");
session.SaveChanges();
}
using (IDocumentSession session = store.OpenSession())
{
// Disable optimistic concurrency for the session
// This is also the default value
session.Advanced.OptimisticConcurrencyMode = OptimisticConcurrencyMode.None;
// Store the same document
// Pass 'string.Empty' as the change vector
// to turn ON optimistic concurrency for this document
session.Store(
new Product { Name = "Some Other Name" },
changeVector: string.Empty,
id: "products/999");
// This will throw a ConcurrencyException, and the document will NOT be saved
session.SaveChanges();
}
using (IAsyncDocumentSession asyncSession = store.OpenAsyncSession())
{
// Store new document 'products/999'
await asyncSession.StoreAsync(new Product { Name = "Some Name" }, id: "products/999");
await asyncSession.SaveChangesAsync();
}
using (IAsyncDocumentSession asyncSession = store.OpenAsyncSession())
{
// Disable optimistic concurrency for the session
// This is also the default value
asyncSession.Advanced.OptimisticConcurrencyMode = OptimisticConcurrencyMode.None;
// Store the same document
// Pass 'string.Empty' as the change vector
// to turn ON optimistic concurrency for this document
await asyncSession.StoreAsync(
new Product { Name = "Some Other Name" },
changeVector: string.Empty,
id: "products/999");
// This will throw a ConcurrencyException, and the document will NOT be saved
await asyncSession.SaveChangesAsync();
}
Constraints
- Cannot combine Writes and WritesAndReads with NoTracking.
- Cannot combine Writes and WritesAndReads with TransactionMode.ClusterWide.
- UseOptimisticConcurrency is deprecated.
- WritesAndReads mode is not supported with Sharding.
Cannot combine Writes and WritesAndReads with NoTracking
-
If you Disable tracking, the session does Not track documents or retain their change vectors.
As a result, Writes and WritesAndReads modes cannot be used. -
Setting
NoTrackingtotruewhile usingWritesorWritesAndReadsmode will throw anInvalidOperationException. The exception is thrown by whichever property is set second.
- NoTracking_first
- Mode_first
using (IDocumentSession session = store.OpenSession(new SessionOptions
{
NoTracking = true,
// Setting this will throw an 'InvalidOperationException'
OptimisticConcurrencyMode = OptimisticConcurrencyMode.Writes
}))
{
}
using (IDocumentSession session = store.OpenSession(new SessionOptions
{
OptimisticConcurrencyMode = OptimisticConcurrencyMode.Writes,
// Setting this will throw an 'InvalidOperationException'
NoTracking = true
}))
{
}
Cannot combinee Writes and WritesAndReads with TransactionMode.ClusterWide
-
Cluster-wide sessions cannot apply Writes or WritesAndReads modes.
-
Setting
WritesorWritesAndReadsmode on a cluster-wide session will throw anInvalidOperationException.
The exception is thrown by whichever property is set second.
- ClusterWide_first
- Mode_first
using (IDocumentSession session = store.OpenSession(new SessionOptions
{
TransactionMode = TransactionMode.ClusterWide,
// Setting this will throw an 'InvalidOperationException'
OptimisticConcurrencyMode = OptimisticConcurrencyMode.Writes
}))
{
}
using (IDocumentSession session = store.OpenSession(new SessionOptions
{
OptimisticConcurrencyMode = OptimisticConcurrencyMode.Writes,
// Setting this will throw an 'InvalidOperationException'
TransactionMode = TransactionMode.ClusterWide
}))
{
}
UseOptimisticConcurrency is deprecated
-
The
UseOptimisticConcurrencyproperty is deprecated and will be removed in a future major version.
UseOptimisticConcurrencyModeinstead. -
OptimisticConcurrencyModeand the deprecatedUseOptimisticConcurrencyproperty cannot be used together on the same session. Setting one after the other will throw anInvalidOperationException.
- UseOptimisticConcurrency_first
- Mode_first
using (IDocumentSession session = store.OpenSession())
{
session.Advanced.UseOptimisticConcurrency = true;
// Setting this will throw an 'InvalidOperationException'
session.Advanced.OptimisticConcurrencyMode = OptimisticConcurrencyMode.Writes;
}
using (IDocumentSession session = store.OpenSession())
{
session.Advanced.OptimisticConcurrencyMode = OptimisticConcurrencyMode.Writes;
// Setting this will throw an 'InvalidOperationException'
session.Advanced.UseOptimisticConcurrency = true;
}
Note:
In all cases above, the exception is also thrown if the conflicting condition is inherited from the store conventions
rather than set explicitly in the session.
Syntax
// SessionOptions
public OptimisticConcurrencyMode? OptimisticConcurrencyMode { get; set; }
// session.Advanced
public OptimisticConcurrencyMode OptimisticConcurrencyMode { get; set; }
// Deprecated - use OptimisticConcurrencyMode instead
[Obsolete]
public bool UseOptimisticConcurrency { get; set; }
// OptimisticConcurrencyMode enum
public enum OptimisticConcurrencyMode
{
// Default - no concurrency checks are performed (Last Write Wins)
None,
// Concurrency checks are performed only for modified or deleted entities
Writes,
// Concurrency checks are performed for ALL tracked entities,
// both modified and unmodified
WritesAndReads
}