-
Notifications
You must be signed in to change notification settings - Fork 143
RDoc-2293 [Per-feature reorganization] Compare-Exchange articles #2140
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
haludi
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I only checked docs/compare-exchange/start.mdx for now
docs/compare-exchange/start.mdx
Outdated
| * Assign work to a single client or reserve a resource once. | ||
| * Handle concurrency safely, without external services or custom locking logic. | ||
|
|
||
| * Compare-exchange items can also serve as a place to store shared data that lives outside of the documents - |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand this section.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how about:
* Compare-exchange items can also store shared or global values that aren't tied to a specific document.
For example: system-wide configuration flags, shared settings, or reusable identifiers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the advantage over a normal document?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I removed this bullet from start.mdx and moved it to overview.mdx, where this is now explained.
docs/compare-exchange/start.mdx
Outdated
| useful for values that are reused, referenced globally, or managed independently of any single document. | ||
|
|
||
| * Key Characteristics of a compare-exchange item: | ||
| * Cluster-wide - Visible and consistent across all nodes. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We worth to specify "all nodes in the database group"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
docs/compare-exchange/start.mdx
Outdated
| ### Add safety to cluster-wide transactions | ||
|
|
||
| * RavenDB uses **compare-exchange items** behind the scenes to protect cluster-wide transactions. | ||
| When using cluster-wide sessions, RavenDB automatically creates internal compare-exchange entries called Atomic Guards |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When using cluster-wide sessions to handle documents, RavenDB automatically uses compare-exchange to enforce the modification to be atomic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
=>
### Add safety to cluster-wide transactions
* When using cluster-wide sessions to handle documents, RavenDB automatically creates internal compare-exchange items,
called [atomic guards](../compare-exchange/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 a `ConcurrencyException`, ensuring data consistency.
* 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.
docs/compare-exchange/start.mdx
Outdated
| * When you store or update a document in a cluster-wide session, | ||
| RavenDB generates an atomic guard with a Raft-based version. | ||
| * This guard tracks the document’s state across the cluster. | ||
| * If another session modifies the document (or its state changes due to replication), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"or its state changes due to replication"
I don't think this is relevant - implementation details.
What does it add to the user understanding?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed
… end to avoid a long initial scroll.
haludi
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A review for docs/compare-exchange/content/_overview-csharp.mdx
|
|
||
| * Each compare-exchange item contains: | ||
| * **A key** - A unique string identifier in the database scope. | ||
| * **A value** - Can be any object (a number, string, array, or any valid JSON object). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can be any object
I think it should be "value"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
| * **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 is an atomic, thread-safe [compare-and-swap](https://en.wikipedia.org/wiki/Compare-and-swap) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is similar to the compare-and-swap operation in a process, but in RavenDB, it serves a distributed system rather than a multi-threaded system.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
=>
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.
| You can create and manage compare-exchange items from within a [Cluster-Wide session](../client-api/session/cluster-transaction/overview#cluster-wide-transaction-vs-single-node-transaction). | ||
| For example, see [Create items using a cluster-wide session](../compare-exchange/create-cmpxchg-items#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. | ||
| If the session fails, the item creation also fails, and none of the nodes in the database group will store the new compare-exchange item. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something is not clear here.
If the session "fails".
What can make it fail?
We only validate the cluster command in the cluster transaction.
Of course, the request can fail for a network issue, but that is not relevant here, and it is more complicated than true or false.
I would write something like, "like a transaction, all of the commands succeed or none of them."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
=>
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.
| 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. | ||
|
|
||
| Compare-exchange items are not externally replicated to other databases. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like it shouldn't be here.
Is it a duplicate of the above?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed
| using (IDocumentSession session = store.OpenSession()) | ||
| { | ||
| session.Store(user); | ||
| // At this point, the user document has an Id assigned |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The object (C# object) has an ID assigned.
The document in RavenDB wasn't created at this point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
=>
// At this point, the user object has a document ID assigned by the session.
| // It is a cluster-wide reservation | ||
| CompareExchangeResult<string> cmpXchgResult | ||
| = store.Operations.Send( | ||
| new PutCompareExchangeValueOperation<string>(email, user.Id, 0)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe worth mentioning that using 0 here is the key to say we want the operation to succeed only if this PUT creates the compare-exchange.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
| } | ||
|
|
||
| // At this point, Put operation failed - someone else owns the lock or lock time expired | ||
| if (putResult.Value.ReservedUntil < now) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is worth mentioning why we do that, and we don't just try to create it with 0.
There can be a case where the process that locked the resource crashed or anything else, so it will not release it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
=>
// At this point, someone else owns the resource.
// But if their reservation has expired (e.g., the process crashed and never released it),
// we can try to take the lock by overwriting the value using the current index.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is still unclear.
The motivation is not clear.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
=>
// 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.
|
|
||
| --- | ||
|
|
||
| ## Example III - Ensuring unique values without using compare exchange |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not familiar with this option, and I don't think it should work.
I tested it, and I can make it fail.
Cluster of 3:
Node C is taken down.
A document 'user/1' is created with a cluster-wide transaction.
Nodes A and B are taken down.
Node C is started.
A new application tries to load 'user/1' with a cluster-wide transaction.
The result is 'null'.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Removed this example.
- Added this new section instead:
## 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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why mention that in the first place?
It only makes things more confusing instead of explaining.
haludi
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
docs/compare-exchange/content/_create-cmpxchg-items-csharp.mdx
docs/compare-exchange/content/_get-cmpxchg-item-csharp.mdx
docs/compare-exchange/content/_get-cmpxchg-items-csharp.mdx
|
|
||
| ## Create item using a store operation | ||
|
|
||
| * Use the `PutCompareExchangeValueOperation` [store operation](../client-api/operations/what-are-operations) to create a compare-exchange item independently, without opening a session. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be good to emphasize here that the 0 index is what defines the operation as creating rather than modifying.
Basically, this operation is used for both.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a bullet:
* Note:
_PutCompareExchangeValueOperation_ is used for both **creating** and [modifying](../compare-exchange/update-cmpxchg-item) compare-exchange items.
The intent of the operation is determined by the index you pass:
* An index of `0` indicates a **create** operation.
* A non-zero index indicates a **modify** operation.
| | Parameter | Type | Description | | ||
| |--------------|-----------------------|-------------| | ||
| | **key** | `string` | <ul><li>A unique identifier in the database scope.</li><li>Can be up to 512 bytes.</li></ul> | | ||
| | **value** | `T` | <ul><li>A value to be saved for the specified _key_.</li><li>Can be any object (number, string, array, or any valid JSON object).</li></ul> | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can be any value
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
|
|
||
| session.SaveChanges(); | ||
|
|
||
| // Get the compare-exchange item: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It’s better to do that in a different session.
As you wrote, the session tracks the compare-exchange, so it will take it from its state.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
|
|
||
| await asyncSession.SaveChangesAsync(); | ||
|
|
||
| // Get the compare-exchange item: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It’s better to do that in a different session.
As you wrote, the session tracks the compare-exchange, so it will take it from its state.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
| ); | ||
| session.SaveChanges(); | ||
|
|
||
| // Get the compare-exchange item lazily: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It’s better to do that in a different session.
As you wrote, the session tracks the compare-exchange, so it will take it from its state.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
| Dictionary<string, CompareExchangeValue<string>> items = store.Operations.Send(getCmpXchgItemsOp); | ||
|
|
||
| // Check results | ||
| Debug.Assert(items.Count == 4); // 4 keys were requested |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The count of the result is always the same as the requested.
This code is part of our testing, not something the user should do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
| store.Operations.SendAsync(getCmpXchgItemsOp); | ||
|
|
||
| // Check results | ||
| Debug.Assert(items.Count == 4); // 4 keys were requested |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The count of the result is always the same as the requested.
This code is part of our testing, not something the user should do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
| Dictionary<string, CompareExchangeValue<string>> items = lazyItems.Value; | ||
|
|
||
| // Check results | ||
| Debug.Assert(items.Count == 4); // 4 keys were requested |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The count of the result is always the same as the requested.
This code is part of our testing, not something the user should do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
| Dictionary<string, CompareExchangeValue<string>> items = await lazyItems.Value; | ||
|
|
||
| // Check results | ||
| Debug.Assert(items.Count == 4); // 4 keys were requested |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The count of the result is always the same as the requested.
This code is part of our testing, not something the user should do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
| startsWith: "employees", start: 0, pageSize: 10); | ||
|
|
||
| // Results will include only compare-exchange items with keys that start with "employees" | ||
| Debug.Assert(items.Count == 3); // There are 3 keys with the "employees" prefix |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think Debug.Assert is a good way to demonstrate the usage of the value.
But that is only my opinion
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
=>
Console.WriteLine($"Number of compare-exchange items with prefix 'employees': {items.Count}");
haludi
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
docs/compare-exchange/content/_update-cmpxchg-item-csharp.mdx
docs/compare-exchange/content/_delete-cmpxchg-items-csharp.mdx
docs/compare-exchange/content/_include-compare-exchange-items-csharp.mdx
docs/compare-exchange/content/_indexing-compare-exchange-values-csharp.mdx
docs/compare-exchange/content/_cmpxchg-item-expiration-csharp.mdx
docs/compare-exchange/content/_atomic-guards-csharp.mdx
docs/compare-exchange/configuration.mdx
| * To perform an update, provide: | ||
| * The existing key | ||
| * A new value and/or metadata | ||
| * The latest index (version) of the item as last seen by the client. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it will be more understandable if you write something like "the expected version/index"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
=>
* The expected index (version) of the item, which must match the current version stored on the server.
| Whenever a compare-exchange item is deleted, a compare-exchange tombstone item is created for it. | ||
| Tombstones are used to indicate to other RavenDB processes that the item was deleted, so they can react accordingly. | ||
| E.g., indexes referencing the deleted item will update themselves to remove those references. | ||
| The tombstones themselves are not removed immediately - RavenDB uses an internal cleaner task to periodically remove tombstones that are eligible for deletion. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very minor.
Saying "The tombstones themselves are not removed immediately" makes it sound too important.
The customer doesn’t really need to care about that.
All he needs to know is that it’s a background process.
Anyway, it’s not something he interacts with directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
=>
Compare-exchange tombstones that are eligible for deletion are removed periodically by an internal cleanup task.
| ## Delete compare-exchange item using a cluster-wide session | ||
|
|
||
| * Delete compare-exchange items using a cluster-wide session when you want the deletion to be part of a transaction committed via `SaveChanges()`. | ||
| This is suitable if you want to include compare-exchange deletions and document changes in the same transaction. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor
It is also suitable if you want to delete and put a compare exchange as one transaction.
In general, this is to do multiple commands in one transaction.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
=>
This is suitable if you want to include compare-exchange deletions alongside other operations, such as putting or deleting documents and compare-exchange items, in a single transaction.
| The item will be deleted as part of the cluster-wide transaction when _SaveChanges()_ is called. | ||
|
|
||
| * If the item's index (its version) on the server is different from the index you provide, | ||
| _SaveChanges()_ will throw a `ClusterTransactionConcurrencyException`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe worth mentioning that the whole transaction will be rejected?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
=>
* If the item's index (its version) on the server is different from the index you provide, _SaveChanges()_ will throw a `ClusterTransactionConcurrencyException`.
This means the item was modified by another operation after it was loaded into the session, and the entire transaction will be rejected.
| ## Delete compare-exchange item using a store operation | ||
|
|
||
| * Use the `DeleteCompareExchangeValueOperation` [store operation](../client-api/operations/what-are-operations) to delete a compare-exchange item by its key and index, without opening a session. | ||
| This is ideal for stand-alone tasks where you need to perform a direct compare-exchange operation without involving document transactions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor.
The session is not only for handling documents but for handling multiple commands as one transaction.
The underlying endpoint is handled by BatchHandler, and I think its name reflects its purpose.
If you want to batch multiple compare-exchange commands as one transaction, it’s also a good use case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
=>
This is ideal for stand-alone tasks that don't require batching multiple commands into a single transactional session.
| .GetCompareExchangeValue<string>(company1.Supplier); | ||
|
|
||
| // You can assert that no further server calls were made | ||
| Debug.Assert(session.Advanced.NumberOfRequests == numberOfRequests); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor
It is weird for me to see in the documentation Debug.Assert, but maybe it is only me
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Modified to Console.WriteLine
|
|
||
| * **If the document is found**, it is loaded into the session, | ||
| and modifications will be saved successfully as long as no other session has modified the document in the meantime. | ||
| Specifically, if the document’s [change vector](../server/clustering/replication/change-vector) matches the one currently stored on the server, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you mention the change vector here?
It is not relevant for cluster-wide transactions.
The change vector indeed contains some relevant information, but the only check is against the compare exchange index
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
=>
RavenDB checks whether the Raft index of the atomic guard associated with the document matches the version tracked by the current session.
If another session has already updated the document (and incremented the atomic guard’s Raft index), the save will fail with a _ConcurrencyException_.
|
|
||
| * **If the document is found**, it is loaded into the session, | ||
| and modifications will be saved successfully as long as no other session has modified the document in the meantime. | ||
| Specifically, if the document’s [change vector](../server/clustering/replication/change-vector) matches the one currently stored on the server, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
=>
RavenDB checks whether the Raft index of the atomic guard associated with the document matches the version tracked by the current session.
If another session has already updated the document (and incremented the atomic guard’s Raft index), the save will fail with a _ConcurrencyException_.
|
|
||
| ## Create item using a store operation | ||
|
|
||
| * Use the `PutCompareExchangeValueOperation` [store operation](../client-api/operations/what-are-operations) to create a compare-exchange item independently, without opening a session. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a bullet:
* Note:
_PutCompareExchangeValueOperation_ is used for both **creating** and [modifying](../compare-exchange/update-cmpxchg-item) compare-exchange items.
The intent of the operation is determined by the index you pass:
* An index of `0` indicates a **create** operation.
* A non-zero index indicates a **modify** operation.
| | Parameter | Type | Description | | ||
| |--------------|-----------------------|-------------| | ||
| | **key** | `string` | <ul><li>A unique identifier in the database scope.</li><li>Can be up to 512 bytes.</li></ul> | | ||
| | **value** | `T` | <ul><li>A value to be saved for the specified _key_.</li><li>Can be any object (number, string, array, or any valid JSON object).</li></ul> | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
| Whenever a compare-exchange item is deleted, a compare-exchange tombstone item is created for it. | ||
| Tombstones are used to indicate to other RavenDB processes that the item was deleted, so they can react accordingly. | ||
| E.g., indexes referencing the deleted item will update themselves to remove those references. | ||
| The tombstones themselves are not removed immediately - RavenDB uses an internal cleaner task to periodically remove tombstones that are eligible for deletion. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
=>
Compare-exchange tombstones that are eligible for deletion are removed periodically by an internal cleanup task.
| ## Delete compare-exchange item using a cluster-wide session | ||
|
|
||
| * Delete compare-exchange items using a cluster-wide session when you want the deletion to be part of a transaction committed via `SaveChanges()`. | ||
| This is suitable if you want to include compare-exchange deletions and document changes in the same transaction. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
=>
This is suitable if you want to include compare-exchange deletions alongside other operations, such as putting or deleting documents and compare-exchange items, in a single transaction.
| store.Operations.SendAsync(getCmpXchgItemsOp); | ||
|
|
||
| // Check results | ||
| Debug.Assert(items.Count == 4); // 4 keys were requested |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
| Dictionary<string, CompareExchangeValue<string>> items = lazyItems.Value; | ||
|
|
||
| // Check results | ||
| Debug.Assert(items.Count == 4); // 4 keys were requested |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
| Dictionary<string, CompareExchangeValue<string>> items = await lazyItems.Value; | ||
|
|
||
| // Check results | ||
| Debug.Assert(items.Count == 4); // 4 keys were requested |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
| .GetCompareExchangeValue<string>(company1.Supplier); | ||
|
|
||
| // You can assert that no further server calls were made | ||
| Debug.Assert(session.Advanced.NumberOfRequests == numberOfRequests); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Modified to Console.WriteLine
| public void PrintWork() | ||
| { | ||
| // Try to get hold of the printer resource | ||
| long reservationIndex = LockResource(store, "Printer/First-Floor", TimeSpan.FromMinutes(20)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
=>
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)).
// Don't exceed the duration, otherwise the resource won't be available for someone else.
// In a distributed system (unlike a multi-threaded app), a process may fail or exit unexpectedly
// before releasing the resource - never reaching the 'finally' block.
// To prevent the resource from remaining locked indefinitely,
// we use a timeout to expire the reservation.
}
finally
{
ReleaseResource(store, "Printer/First-Floor", reservationIndex);
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't exceed the duration, otherwise the resource won't be available for someone else.
If you exceed the duration, two components can use the same resource at the same time.
I would mention the motivation before I get into the implementation details, but it is only my opinion
haludi
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice work.
This will be very useful when a customer comes with questions about this topic.
Thanks.
Related issues:
@haludi => C# files to review + start & configuration:
@M4xymm => Node.js files to review:
@kalczur => tsx file to review
This allows us to add a basic card without an image.
It's a good solution until an image is provided/generated, or in cases where adding images is too noisy or crowded in the UI.
Content for the other languages (Java, PHP, Python) was simply copied over - no code review is currently needed.
Articles for these languages should be handled separately in dedicated PRs.
This is summarized in the table “Status of article coverage per client language” in the issue description:
https://issues.hibernatingrhinos.com/issue/RDoc-2293/Per-feature-reorganization-Compare-Exchange-articles