Transactions and Sessions
On this page
Overview
In this guide, you can learn how to use Mongoid to perform transactions. Transactions allow you to perform a series of operations that change data only if the entire transaction is committed. If any operation in the transaction does not succeed, the driver stops the transaction and discards all data changes before they ever become visible. This feature is called atomicity.
In MongoDB, transactions run within logical sessions. A session is a grouping of related read or write operations that you want to run sequentially. Sessions enable causal consistency for a group of operations, which means that all processes in your application agree on the order of causally-related operations.
Sessions allow you to run operations in an ACID-compliant 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.
In Mongoid, you can perform transactions by using either of the following APIs:
High-Level Transaction API: Mongoid manages the life cycle of the transaction. You can use this API in Mongoid v9.0 and later.
Low-Level Transaction API: You must manage the life cycle of the transaction. You can use this API in Mongoid v6.4 and later.
The Session API section describes how to make changes to your data from within a session without performing a transaction.
High-Level Transaction API
You can use the High-Level Transaction API to internally manage the lifecycle of your transaction. This API either commits your transaction or ends it and incorporates error handling logic.
You can start a transaction by calling the transaction
method on
an instance of a model, on the model class, or on a Mongoid
module.
When you call the transaction
method, Mongoid performs the
following tasks:
Creates a session on the client.
Starts a transaction on the session.
Performs the specified data changes.
Commits the transaction to the database if no errors occur, or ends the transaction if there is an error.
Closes the session.
If your transaction is committed, Mongoid calls any after_commit
callbacks for all objects modified inside the transaction. If there is
an error and the transaction is rolled back, Mongoid calls any
after_rollback
callbacks for all objects modified inside the
transaction. To learn more about these callbacks and their behavior, see
the Callbacks section of this guide.
Example
This example uses the following models to represent documents that describe books and films:
class Book include Mongoid::Document field :title, type: String field :author, type: String field :length, type: Integer end class Film include Mongoid::Document field :title, type: String field :year, type: Integer end
The following code demonstrates how to perform a transaction on different objects to change data in multiple collections:
# Starts a transaction from the model class Book.transaction do # Saves new Book and Film instances to MongoDB Book.create(title: 'Covert Joy', author: 'Clarice Lispector') Film.create(title: 'Nostalgia', year: 1983) end # Starts a transaction from an instance of Book book = Book.create(title: 'Sula', author: 'Toni Morrison') book.transaction do # Saves a new field value to the Book instance book.length = 192 book.save! end # Starts a transaction from the Mongoid instance Mongoid.transaction do # Deletes the Book instance in MongoDB book.destroy end
Client Behavior
Only operations on the same client are in the scope of a transaction, because each transaction is attached to a specific client. Ensure that you use objects from the same client inside the transaction method block.
The following example defines model classes that use different clients and demonstrates how operations are run based on the origin client:
# Defines a class by using the :default client class Post include Mongoid::Document end # Defines a class by using the :encrypted_client class User include Mongoid::Document store_in client: :encrypted_client end # Starts a transaction on the :encrypted_client User.transaction do # Uses the same client, so the operation is in the transaction User.create! # Uses a different client, so it is *not* in the transaction Post.create! end
Note
When you call the transaction
method on the Mongoid
module,
Mongoid creates the transaction by using the :default
client.
Ending Transactions
Any exception raised inside the transaction method block ends the
transaction and rolls back data changes. Mongoid displays all
exceptions except for the Mongoid::Errors::Rollback
exception. You
can raise this exception in your application to explicitly end the
transaction without returning the exception to you. For example, you
might implement this transaction exception to end a transaction when a
certain condition is not met, but without raising an exception message.
Callbacks
This transaction API introduces the after_commit
and
after_rollback
callbacks.
Mongoid triggers the after_commit
callback for an object that was
created, saved, or deleted in the following cases:
After the transaction is committed if the object was modified inside the transaction.
After the object is persisted if the object was modified outside the transaction block.
The after_commit
callback is triggered only after all
other callbacks are performed successfully. Therefore, if an object is
modified outside of a transaction, it is possible that the object is then
persisted, but the after_commit
callback is not triggered. This
might occur, for example, if Mongoid raised an exception in the
after_save
callback because this callback must complete successfully
to trigger after_commit
.
The after_rollback
callback is triggered for an object that was
created, saved, or deleted inside a transaction, if the transaction was
unsuccessful and changes were rolled back. Mongoid never triggers
after_rollback
outside of a transaction.
To learn more about callbacks, see the Callbacks guide.
Low-Level Transaction API
When using the low-level API, you must create a session before
starting a transaction. You can create a session by calling the
with_session
method on a model class or an instance of a model.
Then, you can start a transaction by calling the start_transaction
method on a session. When using this API, you must manually commit or
end the transaction. You can use the commit_transaction
and
abort_transaction
methods on the session instance to manage the
transaction lifecycle.
Example
This example uses the low-level transaction API to perform the following actions:
Creates a session
Starts a transaction
Performs data operations
Commits the transaction, or ends it if there are errors
# Starts a session from the model class Book.with_session do |session| session.start_transaction # Creates a Book Book.create(title: 'Siddhartha', author: 'Hermann Hesse') # Commits the transaction session.commit_transaction rescue StandardError # Ends the transaction if there is an error session.abort_transaction end
Note
If a session ends and includes an open transaction, the transaction is automatically ended.
Transaction Retry
You can retry the transaction commit if it fails initially. The
following example demonstrates how to retry the transaction when Mongoid
raises the UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL
exception:
begin session.commit_transaction rescue Mongo::Error => e if e.label?(Mongo::Error::UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL) retry else raise end end
Options
You can specify a read concern, write concern or read
preference when starting a transaction by passing options to the
start_transaction
method:
session.start_transaction( read_concern: {level: :majority}, write_concern: {w: 3}, read: {mode: :primary} )
To learn more about the available transaction options, see start_transaction in the Ruby driver API documentation.
Client Behavior
To perform operations within a transaction, operations must use the same client that the session was initiated on. By default, all operations are performed by using the default client.
To explicitly use a different client, use the with
method:
# Specifies that the operation should use the "other" client instead of # the default client User.with(client: :other) do Post.with(client: :other) do Post.with_session do |session| session.start_transaction Post.create! Post.create! User.create! session.commit_transaction end end end
Session API
You can use sessions in Mongoid in a similar way that you can
perform a transaction. You can call the with_session
method on a
model class or on an instance of a model and perform some operations in
a block. All operations in the block will be performed in the context of
single session.
The following limitations apply when using sessions:
You cannot share a session across threads. Sessions are not thread-safe.
You cannot nest sessions. For example, you cannot call the
with_session
method on a model class within the block passed to thewith_session
method on another model class. The following code demonstrates a nested session that results in an error:Book.with_session(causal_consistency: true) do # Nesting sessions results in errors Film.with_session(causal_consistency: true) do ... end end All model classes and instances used within the session block must use the same driver client. For example, if you specify different a different client for a model used in the block than those of the model or instance that you called
with_session
on, Mongoid returns an error.
Examples
You can use the with_session
method on a model class and pass it session
options to perform a block of operations in the context of a session.
The following code enables the causal_consistency
option to
guarantee the order of operations when creating a session on the
Book
model, then performs data operations:
Book.with_session(causal_consistency: true) do Book.create! book = Person.first book.title = "Swann's Way" book.save end
To learn more about the available session options, see the Session class constructor details in the Ruby driver API documentation.
Alternatively, you can use the with_session
method on an instance of a
model and pass it session options to perform a block of operations in
the context of a session.
The following code enables the causal_consistency
option to
guarantee the order of operations when creating a session on an instance
of Book
, then performs data operations:
book = Book.new book.with_session(causal_consistency: true) do book.title = 'Catch-22' book.save book.sellers << Shop.create! end
Additional Information
To learn more about transactions, see Transactions in the Server manual.
To learn more about performing CRUD operations, see the Perform Data Operations guide.