トランザクション
MongoDB サーバーのバージョン4.0では、 マルチドキュメントトランザクションが導入されています。 (単一のドキュメント内の複数のフィールドの更新は、MongoDB のすべてのバージョンでアトミックです)。 Ruby ドライバー バージョン2.6.0 トランザクションのサポートを追加します。
トランザクションの使用
トランザクションを開始するには、アプリケーションにセッションが必要です。
トランザクションを使用する推奨方法は、 with_transaction
ヘルパー メソッドを利用することです。
session = client.start_session session.with_transaction do collection.insert_one({hello: 'world'}, session: session) end
with_transaction
ヘルパーは次の処理を実行します。
指定されたブロックを呼び出す前にトランザクションを開始し、ブロックが終了するとトランザクションをコミットします。
ブロック内のいずれかの操作またはコミット操作によって一時的なトランザクション エラーが発生した場合、ブロックやコミットが再度実行されます。
ブロックは複数回呼び出される可能性があるため、冪等である必要があります。
ブロックは、 commit_transaction
またはabort_transaction
を呼び出して、トランザクションを明示的にコミットまたは中止することができます。この場合、 with_transaction
はコミットや中止を試行しません(ただし、ブロックから発生した一時的なトランザクション エラーによってブロックが再試行される場合がある)。
トランザクションのコミット結果が不明な場合は、ブロックも再試行されます。 This may happen, for example, if the cluster undergoes an election during the commit. この場合、ブロックが再試行されると、トポロジーのプライマリ サーバーが変更されている可能性があります。
現在、 with_transaction
は、実行の開始から 120 秒が経過すると、ブロックとコミットの再試行を停止します。 この時間は構成できず、将来のドライバー バージョンで変更される可能性があります。 これは、 with_transactions
の全体的な実行時間が 120 秒以下になることを保証するものではありません。ただし、ウォール クロックの時間が 120 秒経過すると、それ以上の再試行は開始されないだけです。
トランザクションをより制御する必要がある場合は、 低レベル API も使用できます。
with_transaction
は、読み取り保証、書込み保証、読み込み設定(read preference)でstart_transaction
と同じオプションを取ります。
session = client.start_session session.with_transaction( read_concern: {level: :majority}, write_concern: {w: 3}, read: {mode: :primary} ) do collection.insert_one({hello: 'world'}, session: session) end
ブロック内のエラーの処理with_transaction
with_transaction
ブロック内のコマンドが失敗すると、サーバー上のトランザクションが中止される可能性があります。 この状況は通常、ドライバーによって透過的に処理されます。 しかし、アプリケーションがこのようなエラーをキャッチして再度発生しない場合、ドライバーはトランザクションが中止されたかどうかを判断できなくなります。 その後、ドライバーはブロックを無期限に再試行します。
この状況を回避するには、アプリケーションはwith_transaction
ブロック内で暗黙的にエラーを処理しないようにする必要があります。 アプリケーションがブロック内のエラーを処理する必要がある場合は、エラーを再度発生させる必要があります。
session.with_transaction do collection.insert_one({hello: 'world'}, session: session) rescue Mongo::Error::OperationFailure => e # Do something in response to the error raise e end
アプリケーションでエラーをカスタム方法で処理する必要がある場合は、代わりに低レベル API を使用する必要があります。
低レベル API
トランザクションは、セッションでstart_transaction
メソッドを呼び出すことで開始できます。
session = client.start_session session.start_transaction
トランザクションを開始するときに、読み取り保証、書込み保証、および読み込み設定(read preference)を指定することもできます。
session = client.start_session session.start_transaction( read_concern: {level: :majority}, write_concern: {w: 3}, read: {mode: :primary})
トランザクションで行われた変更を データベースに保持するには、トランザクションを明示的にコミットする必要があります。 オープン トランザクションでセッションが終了すると、そのトランザクションは 中止されます 。 トランザクションは明示的に中止されることもできます。
トランザクションをコミットまたは中止するには、セッション インスタンスでcommit_transaction
またはabort_transaction
を呼び出します。
session.commit_transaction session.abort_transaction
注:未処理のトランザクションは、 データベースなど、サーバー内のさまざまなオブジェクトをロックすることができます。 たとえば、次のスニペットの drop 呼び出しは、サーバーが期限切れでトランザクションを中止するまで、 transactionLifetimeLimitSeconds秒(デフォルトは60 )ハングします。
c1 = Mongo::Client.new(['127.0.0.1:27017']).use(:test_db) session = c1.start_session c1['foo'].insert_one(test: 1) session.start_transaction c1['foo'].insert_one({test: 2}, session: session) c2 = Mongo::Client.new(['127.0.0.1:27017']).use(:test_db) # hangs c2.database.drop
トランザクションはサーバー側のセッションに関連付けられているため、クライアントを閉じても、このクライアントが開始したトランザクションは中止されません。アプリケーションはabort_transaction
を呼び出すか、サーバー側でトランザクションがタイムアウトするまで待機する必要があります。 トランザクションをコミットまたは中止するだけでなく、アプリケーションはセッションを終了することもできます。これにより、このセッションではトランザクションが進行中の場合に中止されます。
session.end_session c2 = Mongo::Client.new(['127.0.0.1:27017']).use(:test_db) # ok c2.database.drop
Handling Errors
トランザクション内の コマンドが失敗した場合、サーバー上でトランザクションが中止される可能性があります。 トランザクションを中止するエラーのエラー ラベルにTransientTransactionError
が含まれていない。 このようなトランザクションをコミットしようとすると、 NoSuchTransaction
エラーで拒否されます。
コミットの再試行
トランザクションのコミットは失敗した場合に再試行できます 。 以下は、そのための Ruby コードです。
begin session.commit_transaction rescue Mongo::Error => e if e.label?('UnknownTransactionCommitResult') retry else raise end end
トランザクションのネスト
MongoDB はネストトランザクションをサポートしていません。 トランザクションがすでに進行中のときにstart_transaction
またはwith_transaction
を呼び出しようとすると、エラーが発生します。