Skip to content
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

(DOCSP-45012) Transactions #71

Merged
merged 34 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
25b5bb8
(DOCSP-45012) Added file.
elyse-mdb Nov 8, 2024
a41f264
(DOCSP-45012) Added methods to page.
elyse-mdb Nov 11, 2024
7c0b013
(DOCSP-45012) Added code.
elyse-mdb Nov 11, 2024
64cdb5c
(DOCSP-45012) Edits.
elyse-mdb Nov 12, 2024
1528874
(DOCSP-45012) Fix list formatting.
elyse-mdb Nov 12, 2024
9783144
(DOCSP-45012) Fix list formatting.
elyse-mdb Nov 12, 2024
fb50c58
(DOCSP-45012) Added examples for manual transactions and moved transa…
elyse-mdb Nov 18, 2024
97f573a
(DOCSP-45012) Edits.
elyse-mdb Nov 19, 2024
7fb6f64
(DOCSP-45012) Edits.
elyse-mdb Nov 19, 2024
5d151bc
(DOCSP-45012) Edits.
elyse-mdb Nov 19, 2024
ab493d6
(DOCSP-45012) Edits.
elyse-mdb Nov 20, 2024
93742f5
(DOCSP-45012) Edits.
elyse-mdb Nov 20, 2024
acf1eda
(DOCSP-45012) Editing instructions for transactions.
elyse-mdb Nov 20, 2024
cec82eb
(DOCSP-45012) Edits.
elyse-mdb Nov 20, 2024
50213c0
(DOCSP-45012) Formatting.
elyse-mdb Nov 20, 2024
e105e2e
(DOCSP-45012) Wording.
elyse-mdb Nov 20, 2024
490de3c
(DOCSP-45012) Edit introduction to code examples.
elyse-mdb Nov 20, 2024
06ca58b
(DOCSP-45012) Edit.
elyse-mdb Nov 20, 2024
d18abff
(DOCSP-45012) Edits.
elyse-mdb Nov 21, 2024
fd6a0eb
(DOCSP-45012) Edits>
elyse-mdb Nov 21, 2024
d1c3d54
Merge remote-tracking branch 'upstream/master' into DOCSP-45012-trans…
elyse-mdb Nov 21, 2024
c317a97
(DOCSP-45012) Edits.
elyse-mdb Nov 21, 2024
790110f
(DOCSP-45012) Fix links.g
elyse-mdb Nov 21, 2024
665e5c0
(DOCSP-45012) Edits + vale fix.
elyse-mdb Nov 21, 2024
2931dc7
(DOCSP-45012) Vale error fix.
elyse-mdb Nov 21, 2024
1512764
(DOCSP-45012) Edit.
elyse-mdb Nov 21, 2024
7f33c54
(DOCSP-45012) Add refs.
elyse-mdb Nov 21, 2024
17424ef
(DOCSP-45012) Fix links.
elyse-mdb Nov 22, 2024
9754844
(DOCSP-45012) @mayaraman19 Review feedback.
elyse-mdb Nov 22, 2024
1c11e70
(DOCSP-45012) Clean up.
elyse-mdb Nov 22, 2024
b668948
(DOCSP-45012) Clean up.
elyse-mdb Nov 22, 2024
02d6011
(DOCSP-45012) @mayaraman19 Final round of review edits.
elyse-mdb Nov 25, 2024
7a3fc40
(DOCSP-45012) Edits.
elyse-mdb Nov 25, 2024
5492ba4
Merge branch 'master' into DOCSP-45012-transactions
elyse-mdb Dec 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 151 additions & 0 deletions source/includes/write/transactions.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
#include <iostream>

#include <bsoncxx/builder/basic/document.hpp>
#include <bsoncxx/json.hpp>
#include <mongocxx/client.hpp>
#include <mongocxx/exception/exception.hpp>
#include <mongocxx/instance.hpp>
#include <mongocxx/uri.hpp>
#include <mongocxx/exception/operation_exception.hpp>

using bsoncxx::builder::basic::kvp;
using bsoncxx::builder::basic::make_document;

int main() {

{
// start-callback-api
// Establish a connection to the MongoDB deployment
mongocxx::instance instance{};
mongocxx::client client(mongocxx::uri{"<connectionString>"});

// Define database and collection variables
auto db = client["sample_mflix"];
auto movies_collection = db["movies"];
auto comments_collection = db["comments"];

// Define a callback specifying the sequence of operations to perform during the transaction
mongocxx::client_session::with_transaction_cb callback = [&](mongocxx::client_session* session) {
mayaraman19 marked this conversation as resolved.
Show resolved Hide resolved
// Important:: You must pass the session to the operations.
movies_collection.insert_one(*session, make_document(kvp("title", "Parasite")));
comments_collection.insert_one(*session, make_document(kvp("name", "Anjali Patel"),
kvp("text", "This is my new favorite movie!")));
};

// Define an options instance to explicitly set the write concern for the transaction operations
mongocxx::options::transaction opts;
mongocxx::write_concern wc;
wc.acknowledge_level(mongocxx::write_concern::level::k_majority);
opts.write_concern(wc);

// Start a client session
auto session = client.start_session();

try {
// Start a transaction, execute the operations in the callback function, and commit the results
session.with_transaction(callback, opts);
} catch (const mongocxx::exception& e) {
std::cout << "An exception occurred: " << e.what() << std::endl;
return EXIT_FAILURE;
}

return EXIT_SUCCESS;
// end-callback-api
}
{
// start-core-api
// Establish a connection to the MongoDB deployment
mongocxx::instance instance{};
mongocxx::client client(mongocxx::uri{"<connectionString>"});

// Runs the txn_func and retries if TransientTransactionError occurs
using transaction_func = std::function<void(mongocxx::client_session& session)>;
auto run_with_retry = [](transaction_func txn_func,
mongocxx::client_session& session) {
while (true) {
try {
txn_func(session); // performs transaction.
break;
} catch (const mongocxx::operation_exception& oe) {
std::cout << "Transaction aborted. Caught exception during transaction."
<< std::endl;
// If transient error, retry the whole transaction.
if (oe.has_error_label("TransientTransactionError")) {
std::cout << "TransientTransactionError, retrying transaction..."
<< std::endl;
continue;
} else {
throw oe;
}
}
}
};

// Commits the active transaction and retries commit if UnknownTransactionCommitResult occurs
auto commit_with_retry = [](mongocxx::client_session& session) {
while (true) {
try {
session.commit_transaction(); // Uses write concern set at transaction start.
std::cout << "Transaction committed."
<< std::endl;
break;
} catch (const mongocxx::operation_exception& oe) {
// Can retry commit
if (oe.has_error_label("UnknownTransactionCommitResult")) {
std::cout << "UnknownTransactionCommitResult, retrying commit..."
<< std::endl;
continue;
} else {
std::cout << "Error during commit..."
<< std::endl;
throw oe;
}
}
}

};

auto txn_func = [&](mongocxx::client_session& session) {
auto& client = session.client();

// Define database and collection variables
auto db = client["sample_mflix"];
auto movies_collection = db["movies"];
auto comments_collection = db["comments"];

// Define an options instance to explicitly set the write concern for the transaction operations
mongocxx::options::transaction opts;
mongocxx::write_concern wc;
wc.acknowledge_level(mongocxx::write_concern::level::k_majority);
opts.write_concern(wc);

session.start_transaction(opts);

// Attempt to insert documents into database collections
try {
movies_collection.insert_one(session, make_document(kvp("title", "Parasite")));
comments_collection.insert_one(session, make_document(kvp("name", "Anjali Patel"),
kvp("text", "This is my new favorite movie!")));
} catch (const mongocxx::operation_exception& oe) {
std::cout << "Caught exception during transaction, aborting."
<< std::endl;
session.abort_transaction();
throw oe;
}

commit_with_retry(session);
};

// Start a client session
auto session = client.start_session();

try {
run_with_retry(txn_func, session);
} catch (const mongocxx::operation_exception& oe) {
// Do something with error
throw oe;
}
// end-core-api
}
}

1 change: 1 addition & 0 deletions source/write.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Write Data to MongoDB
Delete </write/delete>
Bulk Write </write/bulk-write>
GridFS </write/gridfs>
Transactions </write/transactions>

Overview
--------
Expand Down
204 changes: 204 additions & 0 deletions source/write/transactions.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
.. _cpp-transactions:

============
Transactions
============

.. facet::
:name: genre
:values: reference

.. meta::
:keywords: code example, rollback, undo operation

.. contents:: On this page
:local:
:backlinks: none
:depth: 2
:class: singlecol

Overview
--------

In this guide, you can learn how to use the {+driver-long+} to perform
**transactions**. Transactions allow
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm I have generally been advised by Nora to avoid inline links where the link destination is not explicitly described in the text. So, being a little careful to follow the Link With Care section of the style guide, I'm avoiding inline links in the first few paragraphs and instead have an Additional Information section at the bottom of the page that has links to Transactions, causal consistency, etc.

If you still think this link would be helpful, let me know!

you to run a series of operations that do not change any data until the
transaction is committed. If any operation in the transaction returns an
error, the driver cancels the transaction and discards all data changes
before they ever become visible.

In MongoDB, transactions run within logical **sessions**. A
session is a grouping of related read or write operations that you intend to run sequentially.
Sessions enable causal consistency for a group of operations in an **ACID-compliant** transaction, which is a
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

S: link to https://www.mongodb.com/docs/manual/core/read-isolation-consistency-recency/#causal-consistency for causal consistency since this can be an unfamiliar concept

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same reply as above!

Copy link
Collaborator

@mayaraman19 mayaraman19 Nov 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For causal consistency, I do think it can be an unfamiliar term so I think it would be good to provide some sort of reference when it's mentioned (doesn't have to be a link - could be a short definition). I don't feel too strongly about it though, so I defer to your judgement!

transaction that meets an expectation of atomicity, consistency, isolation, and durability. MongoDB
guarantees that the data involved in your transaction operations remains
consistent, even if the operations encounter unexpected errors.

When using the {+driver-short+}, you can create a new session from a ``mongocxx::client`` instance.
Then, you can use the resulting ``mongocxx::client_session`` instance to perform transactions.
We recommend that you reuse your client for multiple sessions and transactions instead of
instantiating a new client each time.

.. warning::

Use a ``mongocxx::client_session`` only with the ``mongocxx::client`` that created it.
Using a ``client_session`` with a different ``client`` results in operation errors.

.. important::

Instances of ``mongocxx::client`` are not thread-safe.
Each ``mongoxcc::client`` instance and its child instances, including ``mongocxx::client_session``, should be used by a single thread at a time.

Check failure on line 50 in source/write/transactions.txt

View workflow job for this annotation

GitHub Actions / TDBX Vale rules

[vale] reported by reviewdog 🐶 [MongoDB.AvoidSubjunctive] Avoid the subjunctive 'should'. Raw Output: {"message": "[MongoDB.AvoidSubjunctive] Avoid the subjunctive 'should'.", "location": {"path": "source/write/transactions.txt", "range": {"start": {"line": 50, "column": 104}}}, "severity": "ERROR"}
To learn more, see the :ref:`Thread and Fork Safety <cpp-thread-safety>` guide.

.. _cpp-transaction-apis:

Transaction APIs
----------------

The {+driver-long+} provides a callback API and a core API to manage the transaction lifestyle.
Before you begin a transaction, you must call the ``start_session()`` method to instantiate a ``mongocxx::client_session``.
Then, you can use either of the following APIs to perform a transaction:

- :ref:`Callback API <cpp-callback-api>`: High-level API that manages the life cycle of the transaction and automatically incorporates error handling logic.
- :ref:`Core API <cpp-core-api>`: Low-level API that allows you to manage the life cycle of the transaction and implement custom error handling logic.

.. tip::

To learn more about error handling, see the :manual:`Transaction Error Handling </core/transactions-in-applications/#transaction-error-handling>` section in the {+mdb-server+} manual.

.. _cpp-callback-api:

Callback API
~~~~~~~~~~~~

Use the callback API to allow the {+driver-long+} to manage the life cycle of your transaction.
To implement this API, call the ``with_transaction()`` method on your ``mongocxx::client_session`` and pass in a
callback function specifying the sequence of operations you want to run. The ``with_transaction()`` method starts a transaction, executes the callback function, and
either commits your transaction or ends the transaction if it encounters an error. If your transaction encounters a ``TransientTransactionError`` or ``UnknownTransactionCommitResult`` error, the
``with_transaction()`` method reruns the transaction.

The following code uses the callback API to perform a transaction that inserts documents into the ``movies`` and ``comments`` collections in the ``sample_mflix`` database.
This code performs the following actions:

1. Starts a session from the client using the ``start_session()`` method.
#. Defines a callback function that specifies the operations to perform during the transaction.
#. Creates an option object to prepare to set the write concern for the transaction operations.
To learn more about read and write semantics, see the :manual:`Read Concern/Write Concern/Read Preference </core/transactions/#read-concern-write-concern-read-preference>`
section in the {+mdb-server+} manual.
#. Calls the ``with_transaction()`` method to manage the transaction, passing the callback function and option object as arguments.

.. literalinclude:: /includes/write/transactions.cpp
:language: cpp
:dedent:
:start-after: start-callback-api
:end-before: end-callback-api

.. _cpp-core-api:

Core API
~~~~~~~~

Use the core API to manage the life cycle of your transaction. To implement this API, you must make explicit calls to methods in the ``mongocxx::client_session`` interface
to start a transaction, commit an active transaction, and end a transaction if an error occurs. The core API doesn't automatically incorporate error handling logic,
and instead allows you to implement custom handling logic for errors including ``TransientTransactionError`` and ``UnknownTransactionCommitResult``.

The following table describes the core API methods provided by the ``mongocxx::client_session`` interface:

.. list-table::
:widths: 25 75
:header-rows: 1

* - Method
- Description

* - ``start_transaction()``
- | Starts a new transaction on the current client session. Accepts an optional ``mongocxx::options::transaction``
instance as an argument to set options. For a full list of options, see `mongocxx::options::transaction <{+api+}/classmongocxx_1_1v__noabi_1_1options_1_1transaction.html>`__
in the API documentation.
|
| Raises an exception if the options are misconfigured, if there are network or other transient failures, or if there
are other errors such as a session with a transaction already in progress. If an error is returned with the ``TransientTransactionError`` label,
you can end the transaction and then retry it with the expectation that it will succeed.
|
| To learn more about this method, see the :manual:`startTransaction()
</reference/method/Session.startTransaction/>` guide in the {+mdb-server+} manual.

* - ``commit_transaction()``
- | Commits the active transaction on the current client session.
|
| Raises an exception if options are misconfigured, if there are network or other transient failures,
or if there are other errors such as a session with no transaction in progress. If an error is returned with the ``UnknownTransactionCommitResult`` label,
you can end the transaction and then retry it with the expectation that it will succeed when the committed transaction satisfies the set write concern.
elyse-mdb marked this conversation as resolved.
Show resolved Hide resolved
|
| To learn more about this method, see the :manual:`commitTransaction()
</reference/method/Session.commitTransaction/>` guide in the {+mdb-server+} manual.

* - ``abort_transaction()``
- | Ends the active transaction on the current client session.
|
| Raises an exception if the options are misconfigured or if there are other errors such as
a session with no transaction in progress.
|
| To learn more about this method, see the :manual:`abortTransaction()
</reference/method/Session.abortTransaction/>` guide in the {+mdb-server+} manual.

.. tip::

The ``mongocxx::client_session`` class also provides methods to retrieve and modify session properties.
To learn more, see `mongocxx::client_session <{+api+}/classmongocxx_1_1v__noabi_1_1client__session.html>`__ in the API documentation.

The following code uses the core API to perform a transaction that inserts documents into the ``movies`` and ``comments`` collections in the ``sample_mflix`` database.
This code performs the following actions:

1. Starts a session from the client using the ``start_session()`` method.
#. Creates an option object to prepare to set the write concern for the transaction operations.
To learn more about read and write semantics, see the :manual:`Read Concern/Write Concern/Read Preference </core/transactions/#read-concern-write-concern-read-preference>`
section in the {+mdb-server+} manual.
#. Calls the ``start_transaction()`` method to start a transaction, passing in the option object as an argument.
#. Runs operations to insert documents into collections in the ``sample_mflix`` database, passing the active
session to each operation.
If an operation encounters an error, the whole transaction is aborted. If the error has the label ``TransientTransactionError``,
the transaction is retried.
#. Commits the active transaction using the ``commit_transaction()`` method. If the commit encounters an error with the label ``UnknownTransactionCommitResult``, the commit is retried.

.. literalinclude:: /includes/write/transactions.cpp
:language: cpp
:dedent:
:start-after: start-core-api
:end-before: end-core-api

.. _cpp-transactions-addtl-info:

Additional Information
----------------------

To learn more about the concepts discussed in this guide, see the following pages in the
{+mdb-server+} manual:

- :manual:`Transactions </core/transactions/>`
- :manual:`Drivers API </core/transactions-in-applications/>`
- :manual:`Server Sessions </reference/server-sessions/>`
- :manual:`Causal Consistency </core/read-isolation-consistency-recency/#causal-consistency>`

To learn more about ACID complicance, see the :website:`ACID Properties in Database Management Systems </basics/acid-transactions>`
guide on the MongoDB website.

To learn more about insert operations, see the :ref:`Insert Documents <cpp-write-insert>` guide.

.. _api-docs-transaction:

API Documentation
~~~~~~~~~~~~~~~~~

To learn more about any of the types or methods discussed in this
guide, see the following API Documentation:

- `mongocxx::client <{+api+}/classmongocxx_1_1v__noabi_1_1client.html>`__
- `mongocxx::client_session <{+api+}/classmongocxx_1_1v__noabi_1_1options_1_1transaction.html>`__
- `mongocxx::options::transaction <{+api+}/classmongocxx_1_1options_1_1transaction.html>`__
- `start_session() <{+api+}/classmongocxx_1_1v__noabi_1_1client.html#a03535128dbe5be973a08764b7741f24e>`__
- `with_transaction() <{+api+}/classmongocxx_1_1v__noabi_1_1client__session.html#a3dcdf91bf72e69bf7e63a7c8ad859b93>`__
- `start_transaction() <{+api+}/classmongocxx_1_1v__noabi_1_1client__session.html#a9cc1c32d80a6cb1b0b21001d7990111b>`__
- `commit_transaction() <{+api+}/classmongocxx_1_1v__noabi_1_1client__session.html#ad2d1a8f7c77542db6ec7629d162761ff>`__
- `abort_transaction() <{+api+}/classmongocxx_1_1v__noabi_1_1client__session.html#aabb8247a655741af6fe0f78bef8116e1>`__
- `insert_one() <{+api+}/classmongocxx_1_1v__noabi_1_1collection.html#a839bedb5505e5ce75cbf384e2e2457bd>`__
Loading