web-dev-qa-db-ja.com

CouchDBでトランザクションとロックを実行できますか?

トランザクション(開始、コミット、またはロールバック)、ロック(更新の選択)を行う必要があります。ドキュメントモデルデータベースでどのように行うことができますか?

編集:

ケースは次のとおりです。

  • オークションサイトを運営したい。
  • また、購入を指示する方法も考えます。
  • 直接購入では、アイテムレコードの数量フィールドを減らす必要がありますが、数量がゼロより大きい場合のみです。そのため、ロックとトランザクションが必要です。
  • ロックやトランザクションなしで対処する方法がわかりません。

これをCouchDBで解決できますか?

79
user2427

いいえ。CouchDBは「楽観的同時実行性」モデルを使用します。最も簡単な言葉で言えば、これは更新と一緒にドキュメントバージョンを送信することを意味し、現在のドキュメントバージョンが送信したものと一致しない場合、CouchDBは変更を拒否します。

それは本当に一見シンプルです。 CouchDBの多くの通常のトランザクションベースのシナリオを再構築できます。ただし、CouchDBを学習するときは、RDBMSドメインの知識を捨てる必要があります。 CouchをSQLベースの世界にしようとするのではなく、より高いレベルから問題にアプローチすることが役立ちます。

在庫の追跡

概説した問題は、主に在庫の問題です。アイテムを説明するドキュメントがあり、「利用可能な数量」のフィールドが含まれている場合、次のように同時実行の問題を処理できます。

  1. ドキュメントを取得し、CouchDBが送信する_revプロパティに注意してください
  2. ゼロより大きい場合、数量フィールドをデクリメントします
  3. _revプロパティを使用して、更新されたドキュメントを送り返します
  4. _revが現在保存されている番号と一致する場合は、完了です!
  5. 競合がある場合(_revが一致しない場合)、最新のドキュメントバージョンを取得します

この場合、考えるべき障害シナリオは2つあります。最新のドキュメントバージョンの数量が0の場合、RDBMSの場合と同じように処理し、購入したいものを実際に購入できないことをユーザーに警告します。最新のドキュメントバージョンの数量が0より大きい場合、更新されたデータを使用して操作を繰り返し、最初からやり直します。これにより、RDBMSよりもやや多くの作業が必要になり、頻繁に競合する更新がある場合は少し面倒になります。

さて、私がちょうど答えたのは、RDBMSで行うのとほぼ同じ方法でCouchDBで行うことを前提としています。私はこの問題に少し異なるアプローチをするかもしれません:

まず、すべての記述子データ(名前、写真、説明、価格など)を含む「マスター製品」ドキュメントから始めます。次に、product_keyおよびclaimed_byのフィールドを持つ特定のインスタンスごとに「インベントリチケット」ドキュメントを追加します。ハンマーのモデルを販売していて、そのうちの20個を販売している場合、使用可能な各ハンマーを表すhammer-1hammer-2などのキーを持つドキュメントがあります。

次に、使用可能なハンマーのリストを提供するビューを作成し、「合計」を表示できるリデュース機能を使用します。これらは完全に外れていますが、実際のビューがどのように見えるかを知る必要があります。

マップ

function(doc) 
{ 
    if (doc.type == 'inventory_ticket' && doc.claimed_by == null ) { 
        emit(doc.product_key, { 'inventory_ticket' :doc.id, '_rev' : doc._rev }); 
    } 
}

これにより、プロダクトキーごとに利用可能な「チケット」のリストが表示されます。誰かがハンマーを購入したいときにこれらのグループを取得し、(id_revを使用して)更新の送信を繰り返して、1つを正常に要求するまで(以前に要求されたチケットは更新になります)エラー)。

Reduce

function (keys, values, combine) {
    return values.length;
}

このreduce関数は、要求されていないinventory_ticketアイテムの総数を返すだけなので、購入できる「ハンマー」の数を知ることができます。

注意事項

このソリューションは、あなたが提示した特定の問題に対する約3.5分の思考時間を表します。これを行うより良い方法があるかもしれません!とはいえ、競合する更新プログラムを大幅に削減し、新しい更新プログラムとの競合に対応する必要性を削減します。このモデルでは、複数のユーザーがプライマリ製品エントリーのデータを変更しようとすることはありません。最悪の場合、複数のユーザーが単一のチケットを要求しようとし、それらのビューからそれらのいくつかを取得した場合は、次のチケットに移動して再試行します。

リファレンス: https://wiki.Apache.org/couchdb/Frequently_asked_questions#How_do_I_use_transactions_with_CouchDB.3F

138
MrKurt

MrKurtの答えを拡大します。多くのシナリオでは、在庫チケットを順番に引き換える必要はありません。最初のチケットを選択する代わりに、残りのチケットからランダムに選択できます。多数のチケットと多数の同時リクエストがある場合、最初のチケットを取得しようとするすべてのユーザーと比較して、これらのチケットの競合が大幅に減少します。

24
Kerr

レストフルトランザクションの設計パターンは、システムに「テンション」を作成することです。銀行口座取引の一般的な使用例では、関連する両方の口座の合計を更新する必要があります。

  • 取引文書「10 USDを口座11223から口座88733に転送」を作成します。これにより、システムに緊張が生じます。
  • すべてのトランザクションドキュメントのテンションスキャンを解決するため、および
    • ソースアカウントがまだ更新されていない場合は、ソースアカウントを更新します(-10 USD)
    • ソースアカウントは更新されたが、トランザクションドキュメントにこれが表示されない場合は、トランザクションドキュメントを更新します(たとえば、ドキュメントに「sourcedone」フラグを設定します)
    • ターゲットアカウントがまだ更新されていない場合は、ターゲットアカウントを更新します(+10 USD)
    • ターゲットアカウントは更新されたが、トランザクションドキュメントにこれが表示されない場合は、トランザクションドキュメントを更新します
    • 両方のアカウントが更新されている場合は、トランザクションドキュメントを削除するか、監査のために保持できます。

張力のスキャンは、システムの張力の時間を短くするために、すべての「張力文書」のバックエンドプロセスで実行する必要があります。上記の例では、最初のアカウントが更新されたが、2番目のアカウントがまだ更新されていない場合、短時間の不整合が予想されます。これは、Couchdbが配布される場合に最終的な一貫性を扱うのと同じ方法で考慮される必要があります。

別の可能な実装では、トランザクションの必要性を完全に回避します。テンション文書を保存し、関係するすべてのテンション文書を評価することでシステムの状態を評価するだけです。上記の例では、これは、アカウントの合計が、このアカウントが関係するトランザクションドキュメントの合計値としてのみ決定されることを意味します。 Couchdbでは、これをmap/reduceビューとして非常にうまくモデル化できます。

20
ordnungswidrig

いいえ、CouchDBは一般にトランザクションアプリケーションには適していません。これは、クラスター化/レプリケートされた環境でのアトミック操作をサポートしていないためです。

CouchDBは、スケーラビリティを優先してトランザクション機能を犠牲にしました。アトミック操作を行うには、スケーラビリティを制限する中央調整システムが必要です。

1つのCouchDBインスタンスのみ、または特定のドキュメントを変更する全員が同じCouchDBインスタンスに接続することを保証できる場合は、競合検出システムを使用して、上記の方法を使用して一種の原子性を作成できますが、後でクラスターにスケールアップする場合または、Cloudantのようなホストされたサービスを使用すると、故障してシステムのその部分をやり直す必要があります。

したがって、私の提案は、アカウントの残高にCouchDB以外のものを使用することです。その方がはるかに簡単です。

5

OPの問題への対応として、Couchはおそらくここでは最良の選択ではありません。ビューを使用することは在庫を追跡するための優れた方法ですが、0に固定することは多かれ少なかれ不可能です。問題は、ビューの結果を読み取り、「hammer-1」アイテムを使用しても問題ないと判断し、それを使用するドキュメントを作成するときの競合状態です。問題は、ビューの結果がハンマー-1の数が0以上である場合に、ハンマーを使用するドキュメントを書くだけのアトミックな方法がないことです。 100人のユーザーがすべて同時にビューを照会し、ハンマー1を1つ表示すると、ハンマー1を使用するドキュメントをすべて作成でき、ハンマー1が-99になります。実際には、競合状態はかなり小さくなります-DBがlocalhostを実行している場合は非常に小さくなります。ただし、スケーリングしてオフサイトDBサーバーまたはクラスターを作成すると、問題はさらに顕著になります。とにかく、金銭に関連する重要なシステムで、この種の競合状態を持つことは受け入れられません。

MrKurtの応答の更新(日付がついているか、CouchDBの一部の機能を知らなかった可能性があります)

ビューは、CouchDBの残高/在庫などを処理する良い方法です。

ビューでdocidとrevを発行する必要はありません。ビューの結果を取得すると、これらの両方を無料で取得できます。特に辞書のような冗長形式でそれらを発行すると、ビューが不必要に大きくなります。

在庫残高を追跡するためのシンプルなビューは、このように見えるはずです(私の頭上からも)

function( doc )
{
    if( doc.InventoryChange != undefined ) {
        for( product_key in doc.InventoryChange ) {
            emit( product_key, 1 );
        }
    }
}

そして、reduce関数はさらにシンプルです

_sum

これは reduce関数に組み込まれています を使用します。これは、一致するキーを持つすべての行の値を合計するだけです。

このビューでは、どのドキュメントにもメンバー「InventoryChange」を含めることができます。このメンバーは、product_keyをそれらの合計在庫の変更にマップします。すなわち。

{
    "_id": "abc123",
    "InventoryChange": {
         "hammer_1234": 10,
         "saw_4321": 25
     }
}

10個のhammer_1234と25個のsaw_4321を追加します。

{
    "_id": "def456",
    "InventoryChange": {
        "hammer_1234": -5
    }
}

インベントリから5つのハンマーを燃やすでしょう。

このモデルでは、データを更新することはなく、追加するだけです。これは、更新の競合が発生する可能性がないことを意味します。データ更新のトランザクションの問題はすべてなくなります:)

このモデルのもう1つの優れた点は、DB内のどのドキュメントでもインベントリからアイテムを追加および削除できることです。これらのドキュメントには、他のあらゆる種類のデータを含めることができます。受け取った日時、倉庫、受け取り従業員などに関する一連のデータを含む「出荷」ドキュメントがあり、そのドキュメントがInventoryChangeを定義している限り、在庫を更新します。 「Sale」ドキュメントや「DamagedItem」ドキュメントなどと同様に、各ドキュメントを見ると、非常にはっきりと読んでいます。そして、ビューはすべてのハードワークを処理します。

5
wallacer

実際には、ある意味では可能です。 HTTP Document API を見て、「1つのリクエストで複数のドキュメントを変更する」という見出しまでスクロールします。

基本的に、URI/{dbname}/_ bulk_docsへの1回のポストリクエストで大量のドキュメントを作成/更新/削除でき、それらはすべて成功するか、すべて失敗します。ただし、ドキュメントでは、この動作が将来変更される可能性があることを警告しています。

編集:予測どおり、バージョン0.9からバルクドキュメントはこの方法で動作しなくなりました。

3
Evan

SQlite種類の軽量ソリューションをトランザクションに使用し、トランザクションが正常に完了したら、それを複製し、SQLiteで複製済みとしてマークします。

SQLiteテーブル

txn_id    , txn_attribute1, txn_attribute2,......,txn_status
dhwdhwu$sg1   x                    y               added/replicated

正常に複製されたトランザクションを削除することもできます。

0
Ravinder Payal