はじめに
トランザクションは、データベースにおける処理の論理的なグループであり、複数のドキュメントにわたる読み取りや書き込みなどの1つ以上の操作をカプセル化します。個々のステートメントとして実行されるのではなく、データベースは操作のグループを一貫した単位として解釈し、処理することができます。これにより、密接に関連する多くのステートメントを通してデータセットの一貫性を確保するのに役立ちます。
このガイドでは、まずトランザクションとは何か、ドキュメントモデルでいつ使用すべきか、そしてMongoDBで概念的にどのように機能するかについて説明します。
MongoDBを使用している場合は、PrismaのMongoDBコネクタをチェックしてください!Prisma Clientを使用して、本番環境のMongoDBデータベースを安心して管理できます。
MongoDBとPrismaの作業を開始するには、ゼロから始めるガイド、または既存のプロジェクトに追加する方法を参照してください。
トランザクションとは?
トランザクションは、複数のステートメントを単一の操作として処理するためにグループ化し、分離する方法です。個々のコマンドがサーバーに送信されるたびに実行されるのではなく、トランザクションではコマンドがまとめられ、他のリクエストとは別のコンテキストで実行されます。
分離性はトランザクションの重要な要素です。トランザクション内で実行されるステートメントは、トランザクション自体の環境にのみ影響します。トランザクション内部からは、ステートメントがデータを変更すると、その結果はすぐに表示されます。外部からは、トランザクションがコミットされるまで変更は行われず、コミットされた時点でトランザクション内のすべてのアクションが一度に可視化されます。
これらの機能は、原子性と分離性を提供することで、データベースがACID準拠を達成するのに役立ちます。これらが一緒になって、データベースが一貫性を維持するのを助けます。さらに、トランザクションでの変更は、不揮発性ストレージにコミットされるまで成功として返されず、これが永続性を提供します。
MongoDBは、マルチドキュメントACIDトランザクションおよび分散マルチドキュメントACIDトランザクションをサポートしています。本質的に、ドキュメントモデルは関連データを単一のドキュメントにまとめて保存することを可能にします。ドキュメントモデルは、アトミックなドキュメント更新と組み合わせることで、ほとんどのユースケースでトランザクションの必要性をなくします。ただし、マルチドキュメント、マルチコレクションのMongoDBトランザクションが最良の選択となるケースもあります。
ドキュメントモデルでトランザクションを使用する場合
ドキュメントモデルは、トランザクションが対象とする多くのニーズを本質的に解決します。それでも、ドキュメントモデルにおいてもトランザクションの利用が必要となる顕著なユースケースがいくつかあります。
トランザクションを必要とするアプリケーションは、通常、異なる関係者間で値が交換されるものです。例としては、「System of Record(記録システム)」または「Line of Business(基幹業務)」アプリケーションなどが挙げられます。
マルチドキュメントトランザクションの利点は、銀行アプリケーションや決済処理、商品の所有権が移転されるサプライチェーンや出荷システム、あるいはeコマースなど、資金の移動を伴うシステムに特化しています。これらの例は、通常、複数のストリームにわたる変更をまとめてパッケージ化し、トランザクションが提供するオールオアナッシングのアプローチによる強力な一貫性を必要とします。
トランザクションの利用方法
MongoDBはトランザクションを使用するための2つのAPIを提供しています。1つ目はリレーショナルデータベースに似た構文を持つコアAPIです。2つ目のコールバックAPIは、MongoDBでトランザクションを使用する推奨されるアプローチです。
両方のAPIの比較は、以下の表にまとめるのが最も適切です
コアAPI | コールバックAPI |
---|---|
トランザクションを開始およびコミットするための明示的な呼び出しが必要です | トランザクションを開始し、指定された操作を実行し、コミットします(またはエラー時に中止します) |
TransientTransactionError およびUnknownTransactionCommitResult に対するエラー処理ロジックを自動的には組み込みませんが、カスタムエラー処理を統合する機能を提供します。 | TransientTransactionError およびUnknownTransactionCommmitResult に対するエラー処理ロジックを自動的に組み込みます。 |
特定のトランザクションのために、明示的な論理セッションをAPIに渡す必要があります。 | 特定のトランザクションのために、明示的な論理セッションをAPIに渡す必要があります。 |
MongoDBとPrismaの作業を開始するには、ゼロから始めるガイド、または既存のプロジェクトに追加する方法を参照してください。
トランザクションセッションの作成
トランザクションは通常、アプリケーション言語の適切なMongoDBドライバーを使用して、APIメソッドのいずれかを介して外部アプリケーションから記述および実行されます。
デモンストレーションのために、MongoDBシェルを介したトランザクションの作成手順を説明します。トランザクションが実際にどのように機能するかを概念化するのに役立つように、例のデータベースとコレクションを使用します。
注: トランザクションセッションが最初のstartTransaction()
メソッドの実行後60秒を超えて実行されると、MongoDBは自動的に操作を中止します。これは、トランザクションが通常、シェルで人がコマンドを発行するのではなく、自動的に動作するアプリケーションで行われるためです。以下の手順は、トランザクションセッションの最初から最後までを概念化するためのものです。
まず、literature
というシンプルなデータベースがあり、その中にauthors
というコレクションがあります。簡単なfind()
クエリを実行すると、著者名とそのタイトルを含むドキュメント構造を確認できます。
db.authors.find()
[{_id: ObjectId("620397dd4b871fc65c193106"),first_name: 'James',last_name: 'Joyce',title: 'Ulysses'},{_id: ObjectId("620398016ed0bb9e23785973"),first_name: 'William',last_name: 'Gibson',title: 'Neuromancer'},{_id: ObjectId("6203981d6ed0bb9e23785974"),first_name: 'George',last_name: 'Orwell',title: 'Homage to Catalonia'},{_id: ObjectId("620398516ed0bb9e23785975"),first_name: 'James',last_name: 'Baldwin',title: 'The Fire Next Time'}]
データベースが用意できたので、MongoDBシェルで直接トランザクションを操作する手順を示します。また、あるセッションで実行されているトランザクションが外部ソースから検出されない可能性があることも示します。
まず、APIを使用するのと同じように、セッションを作成する必要があります。
var session = db.getMongo().startSession()
この新しく作成されたsession
変数は、セッションオブジェクトを格納します。
次のステップは、startTransaction()
メソッドを呼び出してトランザクションを開始することです。
session.startTransaction({ "readConcern": { "level": "snapshot" },"writeConcern": { "w": "majority }})
startTransaction()
メソッドには、readConcern
とwriteConcern
という2つのオプションがあります。これらのオプションの詳細については、MongoDBのドキュメントで確認できます。トランザクションがwriteConcern
の「majority」でコミットされた場合、多数コミットされたデータのスナップショットからデータを返すように、readConcern
のlevel
をsnapshot
に設定します。
w: "majority"
の書き込み懸念と、"snapshot"
の読み込み懸念レベルでコミットすると、操作が多数コミットされたデータの同期されたスナップショットを持つことが保証されます。この書き込みと読み込みの懸念の構成は、特定の要件がない場合の良いデフォルトと考えることができます。
メソッドは成功した場合何も返さず、エラーが発生した場合はトランザクションを中止する必要があります。これについては後ほど説明します。
トランザクションセッション内での作業
トランザクションセッションが開始されたので、前のセクションのsession
変数のコンテキスト内でステートメントを実行する必要があります。
構文を簡単にするために、セッション内でコレクションを変数で表現すると便利です。以下のように行います。
var authors = session.getDatabase('literature').getCollection('authors')
この新しく作成された変数は、トランザクションセッションの外部のシェルで作業する際のdb.authors
と同じように機能します。これを検証するには、2つ目のシェルウィンドウを開き、クラスターに接続してdb.authors.find()
を実行します。どちらのステートメントも同じドキュメントを返します。
セッション内で、外部アプリケーションが何をするかをシミュレートし、データベースにレコードを追加してみましょう。authors
コレクションに対して、以下の方法でこれを行うことができます。
authors.insertOne( {"first_name": "Virginie","last_name": "Despentes","title": "Vernon Subutex") })
MongoDBは成功を返します
{acknowledged: true,insertedId: ObjectId("6203a075c374636bc6976baa")}
セッション内で今すぐauthors.find()
を実行すると、追加された情報を含む以前の結果が返されます。
[{_id: ObjectId("620397dd4b871fc65c193106"),first_name: 'James',last_name: 'Joyce',best_title: 'Ulysses'},{_id: ObjectId("620398016ed0bb9e23785973"),first_name: 'William',last_name: 'Gibson',best_title: 'Neuromancer'},{_id: ObjectId("6203981d6ed0bb9e23785974"),first_name: 'George',last_name: 'Orwell',best_title: 'Homage to Catalonia'},{_id: ObjectId("620398516ed0bb9e23785975"),first_name: 'James',last_name: 'Baldwin',best_title: 'The Fire Next Time'},{_id: ObjectId("6203a075c374636bc6976baa"),first_name: 'Virginie',last_name: 'Despentes',best_title: 'Vernon Subutex'}]
このトランザクションセッションはまだコミットされていないため、セッション外部のMongoDBシェルでは同じ結果が返されることはありません。確認のために、別のMongoDBシェルインスタンスでdb.authors.find()
を実行すると、元のドキュメントが返されます。
[{_id: ObjectId("620397dd4b871fc65c193106"),first_name: 'James',last_name: 'Joyce',best_title: 'Ulysses'},{_id: ObjectId("620398016ed0bb9e23785973"),first_name: 'William',last_name: 'Gibson',best_title: 'Neuromancer'},{_id: ObjectId("6203981d6ed0bb9e23785974"),first_name: 'George',last_name: 'Orwell',best_title: 'Homage to Catalonia'},{_id: ObjectId("620398516ed0bb9e23785975"),first_name: 'James',last_name: 'Baldwin',best_title: 'The Fire Next Time'}]
この違いは、トランザクションが現在も曖昧な状態にあることを示しています。トランザクションは成功してデータベースにコミットされるか、失敗して中止されるかのいずれかです。いずれにしても、データベースは一貫した状態になります。新しいレコードが含まれるか、トランザクションセッションが開始される前と同じ状態にロールバックされるかのどちらかです。
セッション内でcommitTransaction()
メソッドを実行してトランザクションをコミットし、データベースを新しい一貫した状態にします。
session.commitTransaction()
これで、新しいレコードは、トランザクションセッションが行われていたMongoDBシェルと、トランザクション外部のMongoDBシェルの両方のインスタンスに存在することになります。
トランザクションの中止
セッションの開始からコミットまでを終えたので、トランザクションの代替結果について見ていきましょう。
このパスは開始は同じですが、コミットポイントでのみ変化します。トランザクションが行っている変更を破棄したい場合は、セッションシェルで次のようにabortTransaction()
メソッドを使用します。
session.abortTransaction()
このメソッドはトランザクションセッションをキャンセルし、潜在的な変更をすべて破棄します。新しい一貫した状態になる代わりに、データベースは変更をロールバックし、トランザクションセッションが最初に開始されたときと同じ状態を維持します。
結論
このガイドでは、トランザクションとは何か、MongoDBでトランザクションが最も適しているユースケースについて説明しました。また、MongoDBシェルでのトランザクションセッションのプロセスを概念的に説明し、外部アプリケーションがどのように動作するかを理解できるようにしました。
トランザクションはリレーショナルデータベースにとって基本的な必要性であり、MongoDBのようなドキュメントモデルデータベースの特定のユースケースでも必要とされます。ドキュメントモデルに関しては、一般的には、一緒にアクセスされるデータは一緒に保存されるべきであるという推奨があります。しかし、常にそうとは限らないため、トランザクションの基本を知っておくことが重要です。
MongoDBを使用している場合は、PrismaのMongoDBコネクタをチェックしてください!Prisma Clientを使用して、本番環境のMongoDBデータベースを安心して管理できます。
MongoDBとPrismaの作業を開始するには、ゼロから始めるガイド、または既存のプロジェクトに追加する方法を参照してください。
よくある質問
はい、MongoDBトランザクションはNode.jsで使用できます。
MongoDBは、「Node.jsでMongoDBトランザクションを使用する方法」に関する役立つ入門ガイドを提供しています。
ACIDは、原子性(Atomicity)、一貫性(Consistency)、分離性(Isolation)、永続性(Durability)の頭文字をとったものです。これらの特性はすべて、データベースを有効な状態に保つことに関連しています。
トランザクションは、複数のデータベース操作がまとめて実行されるものであるため、予期せぬエラーが発生した場合にデータベースの有効性を確保することが重要です。ACID準拠のトランザクションは、無効なデータベース状態から保護します。
MongoDBトランザクションはセッション内に存在します。startSession()
を使用してセッションを作成し、session.startTransaction()
に続けてトランザクション操作をコミットする準備をします。
意図的に変更をロールバックするには、session.abortTransaction()
メソッドを使用してセッションで開始された操作を破棄できます。これはsession.commitTransaction()
メソッドの前に実行する必要があります。
MongoDBを使用する際には、考慮すべきいくつかのトランザクションのベストプラクティスがあります。MongoDBは60秒を超えるトランザクションを自動的に中止するため、トランザクションの実行時間を開始から60秒以内に抑えるように最適化する必要があります。
1つのトランザクションで行われる操作の数は、トランザクション内で変更されるドキュメントが1,000個を超えないようにすべきです。さらに、1つのトランザクション内で複数の操作が行われるため、適切な読み込みと書き込みの懸念を選択することが重要です。MongoDBは、これらを含む多くの情報を網羅した詳細なベストプラクティスガイドを提供しています。
はい、MongoDB 4.0以降のバージョンでは、マルチドキュメントACIDトランザクションをサポートしています。