トランザクション
Overview
Node.js ドライバーを使用して MongoDB でトランザクションを実行する方法については、次のガイドを参照してください。 トランザクションは 、一緒に成功させるための一連の操作で構成される作業単位であり、1 つ以上の操作が失敗した場合はまとめて失敗します。 この動作はアトミック性と 呼ばれます。 アトミック性とは、1 つ以上の操作で構成されるトランザクションが同時に発生するプロパティであり、他のクライアントはそれらを個別の操作として監視できず、いずれかの操作が失敗しても変更は生じません。
MongoDB の 1 つのドキュメントに対するすべての書込み (write) 操作はアトミックであるため、複数のドキュメントを変更するアトミックな変更を行う必要がある場合( マルチドキュメントトランザクションと呼ばれます)、トランザクションの最もメリットが得られます。 単一ドキュメントに対する書込み操作と同様に、 マルチドキュメントトランザクションはACID に準拠しています 。つまり、MongoDB は、予期しないエラーが発生した場合でも、トランザクション操作に関係するデータの一貫性が保たれることを保証します。 ACID トランザクションに関する MongoDB の記事の詳細については、こちらの MongoDB に関する記事をご覧ください。
ドライバーを使用してマルチドキュメントトランザクションを実行できます。
注意
マルチドキュメントトランザクションを実行するには、MongoDB バージョン 4.0 以降を使用する必要があります。
制限事項の詳細なリストについては、サーバー マニュアルの「トランザクションと操作」セクションを参照してください。
MongoDB では、マルチドキュメントトランザクションはクライアント セッション 中に実行されます。 クライアント セッションは、順番に実行されるよう関連付けられた読み取り操作または書込み操作のグループです。 毎回新しいクライアントをインスタンス化するのではなく、クライアントを複数のセッションやトランザクションで再利用することをお勧めします。
過半数の読み取りおよび書込み保証(write concern)と組み合わせると、ドライバーは操作間の因果一貫性を保証できます。 詳細については、 「クライアント セッションと因果整合性の保証」に関するサーバー マニュアル ガイドを参照してください。
ドライバーを使用して MongoDB でマルチドキュメントトランザクションを実行する方法の詳細については、このガイドの次のセクションを参照してください。
トランザクション API
Core APIを使用して、ドライバーとのトランザクションを実装します。 Core API を使用するには、トランザクションの開始点とコミット点を宣言します。
Core API
Core API には、トランザクションを開始、キャンセル、コミットするためのメソッドが用意されています。 トランザクションをコミットするときは、操作からの変更を不可分的に行うための リクエストをサーバーに送信します。 この API を使用する場合は、サーバーによって返された特定のトランザクション エラーを手動で処理する必要があります。
これらのエラーの詳細については、「 TransientTransactionErrorとUnknownTransactionCommitResultを参照してください。
トランザクションを開始、キャンセル、コミットするには、 Session
オブジェクトで対応するメソッドを呼び出します。
startTransaction()
commitTransaction()
abortTransaction()
サンプル トランザクションの実装については、「 Core API の例」を参照してください。
Transaction Settings
トランザクションをインスタンス化するときに、次のオプションを指定して、そのトランザクションのデフォルトの動作を設定できます。
設定 | 説明 |
---|---|
readConcern | 読み取り操作がレプリカセットから取得するデータの整合性をチェックする方法を指定します。 詳細については、サーバー マニュアルの「読み取り保証」を参照してください。 |
writeConcern | 書込み (write) を確認する条件を指定します。 詳細については、サーバー マニュアルの「書込み保証」を参照してください。 |
readPreference | 詳細については、サーバー マニュアルの「読み込み設定」を参照してください。 |
maxCommitTimeMS | トランザクション上でのコミット アクションの実行を許可する最大時間をミリ秒単位で指定します。 |
値を指定しない場合、ドライバーはクライアント設定を使用します。
次のようなコードを使用して、Core API でトランザクション オプションを指定できます。
const transactionOptions = { readPreference: 'primary', readConcern: { level: 'local' }, writeConcern: { w: 'majority' }, maxCommitTimeMS: 1000 }; session.startTransaction(transactionOptions);
例
カスタマーがオンライン ストアから商品を購入するシナリオを考えてみましょう。 購入を記録するには、アプリケーションは在庫、カスタマーの注文に関連する情報を更新し、注文詳細を登録する必要があります。 データの更新を次のように整理するとします。
コレクション | 操作 | 変更の説明 |
---|---|---|
orders | insert | 購入情報を記録する |
customers | update | 注文 ID を追加して、カスタマーに関連付けます |
inventory | update | 注文商品の減算 |
在庫アイテムの量が不足している場合、注文を完了できなかった場合、支払いシステムがオフラインの場合など、さまざまな方法で購入が失敗する可能性があります。
支払いが失敗した場合は、トランザクションを使用して、そのデータに依存する他の操作でデータ整合性の問題が発生する可能性のある部分的な更新を公開しないようにできます。
サンプル データ
コード例では、マルチドキュメント支払いトランザクションを実行するために、 testdb
データベース内の次のサンプル データが必要です。
カスタマーとその注文を説明する
customers
コレクション内のドキュメント。それぞれが食材の数量と説明を追跡する
inventory
コレクション内のドキュメント。
この例のcustomers
コレクション内のドキュメントには、次のものが含まれています。
{ _id: 98765, orders: [] }
この例のinventory
コレクション内のドキュメントには、次のものが含まれています。
[ { name: "sunblock", sku: 5432, qty: 85 }, { name: "beach towel", sku: 7865, qty: 41 } ]
これらのコード例ではorders
コレクションに対して操作も実行されますが、事前のサンプル ドキュメントは必要ありません。
コード例では、 cart
とpayment
変数を使用して、購入されたアイテムのサンプル リストと注文支払いの詳細を次のように表します。
const cart = [ { name: 'sunblock', sku: 5432, qty: 1, price: 5.19 }, { name: 'beach towel', sku: 7865, qty: 2, price: 15.99 } ]; const payment = { customer: 98765, total: 37.17 };
重要
次のセクションの例では、トランザクションの外部でコレクションを作成するか、MongoDB 4.4以降を使用している必要があります。 トランザクション内でのコレクション作成の詳細については、「 トランザクション内でのコレクションとインデックスの作成 」ガイドを参照してください。
Core API実装
このセクションのコード例は、Core API を使用してセッション内でマルチドキュメント支払いトランザクションを実行する方法を示しています。 この関数では、次の操作を実行する方法が表示されます。
セッションを開始する
トランザクション オプションを指定してトランザクションを開始
同じセッションでデータ操作を実行
トランザクションをコミットするか、ドライバーでエラーが発生した場合はキャンセルします
セッションを終了する
1 async function placeOrder(client, cart, payment) { 2 const transactionOptions = { 3 readConcern: { level: 'snapshot' }, 4 writeConcern: { w: 'majority' }, 5 readPreference: 'primary' 6 }; 7 8 const session = client.startSession(); 9 try { 10 session.startTransaction(transactionOptions); 11 12 const ordersCollection = client.db('testdb').collection('orders'); 13 const orderResult = await ordersCollection.insertOne( 14 { 15 customer: payment.customer, 16 items: cart, 17 total: payment.total, 18 }, 19 { session } 20 ); 21 22 const inventoryCollection = client.db('testdb').collection('inventory'); 23 for (let i=0; i<cart.length; i++) { 24 const item = cart[i]; 25 26 // Cancel the transaction when you have insufficient inventory 27 const checkInventory = await inventoryCollection.findOne( 28 { 29 sku: item.sku, 30 qty: { $gte: item.qty } 31 }, 32 { session } 33 ) 34 if (checkInventory === null) { 35 throw new Error('Insufficient quantity or SKU not found.'); 36 } 37 38 await inventoryCollection.updateOne( 39 { sku: item.sku }, 40 { $inc: { 'qty': -item.qty }}, 41 { session } 42 ); 43 } 44 45 const customerCollection = client.db('testdb').collection('customers'); 46 await customerCollection.updateOne( 47 { _id: payment.customer }, 48 { $push: { orders: orderResult.insertedId }}, 49 { session } 50 ); 51 await session.commitTransaction(); 52 console.log('Transaction successfully committed.'); 53 54 } catch (error) { 55 if (error instanceof MongoError && error.hasErrorLabel('UnknownTransactionCommitResult')) { 56 // add your logic to retry or handle the error 57 } 58 else if (error instanceof MongoError && error.hasErrorLabel('TransientTransactionError')) { 59 // add your logic to retry or handle the error 60 } else { 61 console.log('An error occured in the transaction, performing a data rollback:' + error); 62 } 63 await session.abortTransaction(); 64 } finally { 65 await session.endSession(); 66 } 67 }
当該のセッションで実行したい各 CRUD 操作にセッション オブジェクトを渡す必要があることに注意してください。
catch
ブロックのコードとコメントは、サーバー トランザクション エラーを識別する方法と、それを処理するロジックを配置する場所を示しています。 次のサンプル インポート ステートメントに示すように、コードにドライバーのMongoError
型を必ず含めてください。
const { MongoError, MongoClient } = require('mongodb');
トランザクションを実行した後にコレクションに含まれる内容を確認するには、「支払いトランザクションの結果」セクションを参照してください。
支払いトランザクションの結果
アプリケーションが支払いトランザクションを完了した場合、データベースにはすべての更新が含まれます。例外によってトランザクションが中断された場合は、データベースに変更はいずれも存在しません。
customers
コレクションには、注文フィールドに注文 ID が追加されたカスタマー ドキュメントが含まれている必要があります。
{ "_id": 98765, "orders": [ "61dc..." ] }
inventory
[ { "_id": ..., "name": "sunblock", "sku": 5432, "qty": 84 }, { "_id": ..., "name": "beach towel", "sku": 7865, "qty": 39 } ]
orders
コレクションには、注文と支払いの情報が含まれています。
[ { "_id": "...", "customer": 98765, "items": [ { "name": "sunblock", "sku": 5432, "qty": 1, "price": 5.19 }, { "name": "beach towel", "sku": 7865, "qty": 2, "price": 15.99 } ], "total": 37.17 } ]