Docs Menu
Docs Home
/ / /
Mongoid
/

Transactions and Sessions

On this page

  • Overview
  • High-Level Transaction API
  • Example
  • Client Behavior
  • Ending Transactions
  • Callbacks
  • Low-Level Transaction API
  • Example
  • Transaction Retry
  • Options
  • Client Behavior
  • Session API
  • Examples
  • Additional Information

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.

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:

  1. Creates a session on the client.

  2. Starts a transaction on the session.

  3. Performs the specified data changes.

  4. Commits the transaction to the database if no errors occur, or ends the transaction if there is an error.

  5. 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.

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

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.

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.

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.

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.

This example uses the low-level transaction API to perform the following actions:

  1. Creates a session

  2. Starts a transaction

  3. Performs data operations

  4. 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.

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

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.

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

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 the with_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.

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

To learn more about transactions, see Transactions in the Server manual.

To learn more about performing CRUD operations, see the Perform Data Operations guide.

Back

Search Text