トランザクションとセッション
Overview
このガイドでは、Mongoid を使用して トランザクション を実行する方法を学習できます。トランザクションを使用すると、トランザクション全体がコミットされた場合にのみ、データを変更する一連の操作を実行できます。 トランザクション内のいずれかの操作が成功しない場合、ドライバーはトランザクションを停止し、変更が反映される前にすべてのデータ変更を破棄します。 この特徴は アトミック性と呼ばれます。
MongoDBでは、トランザクションは論理 セッション 内で実行されます。セッションは 、順番に実行されるよう関連付けられた読み取り操作または書き込み操作のグループです。 セッションにより、一連の操作に対する因果整合性が有効になります。つまり、アプリケーション内のすべてのプロセスが因果関連の操作の順序に一致することを意味します。
セッションを使用すると、 ACID準拠のトランザクションで操作を実行でき、アトミック性、整合性、分離、耐久性を確保できます。MongoDBは、トランザクション操作で予期せぬエラーが発生した場合でも、その操作に関わるデータの一貫性が保たれることを保証します。
Mongoid では、次のいずれかの API を使用してトランザクションを実行できます。
高レベル トランザクションAPI : Mongoid がトランザクションのライフサイクルを管理します。このAPI はMongoid v9.0 以降で使用できます。
低レベルトランザクションAPI : トランザクションのライフサイクルを管理する必要があります。このAPI はMongoid v6.4 以降で使用できます。
セッションAPIセクションでは、トランザクションを実行せずにセッション内からデータに変更を加える方法について説明します。
高レベル トランザクションAPI
ハイレベルトランザクションAPI を使用して、トランザクションのライフサイクルを内部的に管理できます。 このAPI は、トランザクションをコミットまたは終了し、エラー処理ロジックを組み込んでいます。
トランザクションを開始するには、モデルのインスタンス、 モデルクラス、または Mongoid
モジュールで transaction
メソッドを呼び出します。
transaction
メソッドを呼び出すと、Mongoid は次のタスクを実行します。
クライアントにセッションを作成します。
セッションでトランザクションを開始します。
指定されたデータ変更を実行します。
エラーが発生しない場合はトランザクションをデータベースにコミットし、エラーが発生した場合はトランザクションを終了します。
セッションを閉じます。
トランザクションがコミットされる場合、Mongoid はトランザクション内で変更されたすべてのオブジェクトに対して任意の after_commit
コールバックを呼び出します。 エラーが発生し、トランザクションがロールバックされた場合、Mongoid はトランザクション内で変更されたすべてのオブジェクトに対して任意の after_rollback
コールバックを呼び出します。 これらのコールバックとその動作の詳細については、このガイドの「 コールバック 」セクションを参照してください。
例
この例では、次のモデルを使用して、書籍と映画について説明するドキュメントを表します。
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
次のコードは、さまざまなオブジェクトに対してトランザクションを実行し、複数のコレクションのデータを変更する方法を示しています。
# 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
クライアントの動作
各トランザクションは特定のクライアントに関連付けられているため、同じクライアントでの操作のみがトランザクションのスコープに含まれます。 トランザクション メソッド ブロック内で同じクライアントのオブジェクトを使用するようにします。
次の例では、異なるクライアントを使用するモデル クラスを定義し、元のクライアントに基づいて操作が実行される方法を示しています。
# 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
注意
Mongoid
モジュールで transaction
メソッドを呼び出すと、Mongoid は :default
クライアント を使用してトランザクションを作成します。
トランザクションの終了
トランザクション メソッド ブロック内で発生した例外によってトランザクションが終了し、データ変更がロールバックされます。 Mongoid は Mongoid::Errors::Rollback
例外を除くすべての例外を表示します。 アプリケーション内でこの例外を発生させると、例外を返さずにトランザクションを明示的に終了できます。 例、このトランザクション例外を実装すると、特定の条件が満たされない場合に、例外メッセージを生成せずにトランザクションを終了できます。
コールバック
このトランザクションAPI、after_commit
と after_rollback
のコールバックが導入されています。
Mongoid は、次の場合に作成、保存、または削除されたオブジェクトの after_commit
コールバックをトリガーします。
トランザクション内でオブジェクトが変更された場合は、トランザクションがコミットされた後。
オブジェクトがトランザクション ブロック外で変更された場合、オブジェクトが保持された後。
after_commit
コールバックは、他のすべてのコールバックが正常に実行された後にのみトリガーされます。 したがって、トランザクション外でオブジェクトが変更された場合、オブジェクトは永続化されますが、after_commit
コールバックはトリガーされない可能性があります。 これは、例Mongoid が after_save
コールバックで例外を発生させた場合、after_commit
をトリガーするにはこのコールバックが正常に完了する必要があるためです。
after_rollback
コールバックは、トランザクションが成功し、変更がロールバックされた場合に、トランザクション内で作成、保存、または削除されたオブジェクトに対してトリガーされます。 Mongoid では、トランザクション外で after_rollback
をトリガーしません。
低レベル トランザクションAPI
低レベルAPIを使用する場合は、トランザクションを開始する前にセッションを作成する必要があります。 モデルクラスまたはモデルのインスタンスで with_session
メソッドを呼び出すことで、セッションを作成できます。
その後、セッションで start_transaction
メソッドを呼び出すことでトランザクションを開始できます。 このAPI を使用する場合は、トランザクションを手動でコミットまたは終了する必要があります。 セッションインスタンスで commit_transaction
メソッドと abort_transaction
メソッドを使用して、トランザクション ライフサイクルを管理できます。
例
この例では、低レベルのトランザクションAPI を使用して次のアクションを実行します。
セッションを作成する
トランザクションを開始します
データ操作を実行します
トランザクションをコミットします、エラーが発生した場合は終了します
# 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
注意
セッションが終了し、オープンなトランザクションが含まれている場合、トランザクションは自動的に終了します。
トランザクションの再試行
最初に失敗した場合は、トランザクションのコミットを再試行できます。 次の例は、 Mongoid が UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL
例外を発生させた場合にトランザクションを再試行する方法を示しています。
begin session.commit_transaction rescue Mongo::Error => e if e.label?(Mongo::Error::UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL) retry else raise end end
オプション
トランザクションを開始するときに、読み取り保証 (read concern)、書込み保証 (write concern)、または読み込み設定 (read preference)を指定するには、start_transaction
メソッドにオプションを渡します。
session.start_transaction( read_concern: {level: :majority}, write_concern: {w: 3}, read: {mode: :primary} )
使用可能なトランザクション オプションの詳細については、 RubyドライバーAPIドキュメントの start_transaction を参照してください。
クライアントの動作
トランザクション内で操作を実行するには、操作はセッションが開始されたのと同じクライアントを使用する必要があります。 デフォルトでは 、すべての操作はデフォルトのクライアントを使用して実行されます。
別のクライアントを明示的に使用するには、 with
メソッドを使用します。
# 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
Mongoid では、トランザクションを実行するのと同様にセッションを使用できます。 モデルクラスまたはモデルのインスタンスで with_session
メソッドを呼び出し、ブロック内で一部の操作を実行できます。 ブロック内のすべての操作は単一のセッションのコンテキストで実行されます。
セッションを使用する場合、次の制限が適用されます。
スレッド間でセッションを共有することはできません。 セッションはスレッドセーフではありません。
セッションをネストすることはできません。 例、別のモデルクラスの
with_session
メソッドに渡されるブロック内のモデルクラスでwith_session
メソッドを呼び出すことはできません。 次のコードは、エラーが発生するネストされたセッションを示しています。Book.with_session(causal_consistency: true) do # Nesting sessions results in errors Film.with_session(causal_consistency: true) do ... end end セッション ブロック内で使用されるすべてのモデル クラスと インスタンスは、同じ ドライバークライアントを使用する必要があります。 例、ブロックで使用されるモデルに、
with_session
を呼び出したモデルまたはインスタンスとは異なるクライアントを指定すると、Mongoid はエラーを返します。
例
モデルクラスで with_session
メソッドを使用し、そのセッション オプションを渡して、セッションのコンテキストで操作のブロックを実行できます。
次のコードでは、Book
モデルでセッションを作成するときに操作順序を保証するために causal_consistency
オプションを有効にしてから、データ操作を実行します。
Book.with_session(causal_consistency: true) do Book.create! book = Person.first book.title = "Swann's Way" book.save end
使用可能なセッション オプションの詳細については、 RubyドライバーAPIドキュメントのセッションクラスコンストラクターの詳細を参照してください。
あるいは、モデルのインスタンスで with_session
メソッドを使用し、そのセッションにセッション オプションを渡して、セッションのコンテキストで操作のブロックを実行することもできます。
次のコードでは、Book
のインスタンスでセッションを作成するときに操作順序を保証するために causal_consistency
オプションを有効にしてから、データ操作を実行します。
book = Book.new book.with_session(causal_consistency: true) do book.title = 'Catch-22' book.save book.sellers << Shop.create! end