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


81

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

編集:

ケースはこれです:

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

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

回答:


145

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

本当に、一見シンプルです。CouchDBの多くの通常のトランザクションベースのシナリオをリフレーミングできます。ただし、CouchDBを学習するときは、RDBMSドメインの知識を捨てる必要があります。CouchをSQLベースの世界に成形しようとするよりも、より高いレベルから問題に取り組むことが役立ちます。

在庫を追跡する

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

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

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

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

まず、すべての記述子データ(名前、写真、説明、価格など)を含む「マスター製品」ドキュメントから始めます。それから私はのためのフィールドで、各特定のインスタンスに対して、「棚卸チケット」ドキュメントを追加したいproduct_keyclaimed_by。あなたはハンマーのモデルを販売し、販売するためにそれらの20を持っている場合は、次のようなキーを持つドキュメントかもしれませんhammer-1hammer-2利用可能な各ハンマーを表現するために、などを。

次に、使用可能なハンマーのリストを表示するビューを作成し、「合計」を表示できるreduce関数を使用します。これらは完全にカフから外れていますが、作業ビューがどのように見えるかを理解できるはずです。

地図

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

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

減らす

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

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

警告

この解決策は、あなたが提示した特定の問題について、合計で約3.5分の思考に相当します。これを行うためのより良い方法があるかもしれません!とはいえ、競合する更新を大幅に削減し、新しい更新との競合に対応する必要性を減らします。このモデルでは、複数のユーザーが主要な製品エントリのデータを変更しようとすることはありません。最悪の場合、複数のユーザーが1つのチケットを要求しようとします。ビューからそれらの複数のユーザーを取得した場合は、次のチケットに移動して再試行するだけです。

参照:https//wiki.apache.org/couchdb/Frequently_asked_questions#How_do_I_use_transactions_with_CouchDB.3F


4
順番に要求しようとする「チケット」を持つことが、マスターエンティティを更新するために単に読み取り/変更/書き込みを再試行するよりも大幅に改善されることは私にはわかりません。確かに、特に大量の在庫がある場合は、余分なオーバーヘッドの価値はないようです。
ニックジョンソン

4
私の見解では、チケットの規則は「簡単に」作成できます。マスターエントリの更新に失敗した場合は、ドキュメントを再読み込みし、操作を再実行してから保存する必要があります。チケットのことで、追加のデータを要求することなく、何かを「要求」することができます。
MrKurt 2008年

また、心配しているオーバーヘッドの種類によっても異なります。競合の増加と戦うか、追加のストレージ要件があります。チケットが購入記録を兼ねることもあることを考えると、あなたが思うほど多くのストレージの問題があるかどうかはわかりません。
MrKurt 2008年

2
製品ドキュメントの数量フィールドを編集しています。次に、たとえば数量= 2Kの場合、数千の「チケット」を作成する必要があります。次に、数量を減らして、いくつかのチケットを削除する必要があります。私にとっては完全にリラックスしていないように聞こえます。基本的なユースケースでは多くの頭痛の種です。何かが足りないかもしれませんが、以前に削除したトランザクションの動作を元に戻して、_bulk_docs?reject_on_conflict = trueのようにオプションにします。シングルマスター構成で非常に役立ちます。
サム

3
@mehaase:これを読んでください:guide.couchdb.org/draft/recipes.html、答えはcouchdbの内部データ構造に要約されます。「データを変更することはなく、新しいものを追加するだけです」。あなたのシナリオでは、それは借方のために口座から輸送中の口座への1つの(原子)トランザクションを作成し、輸送中の口座から前方(または後方)への2番目の(原子)取引を作成することを意味します。それが実際の銀行のやり方です。すべてのステップは常に文書化されています。
Fabian Zeindl 2012

26

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


21

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

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

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

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


5
しかし、アカウントから借方に記入されたが、テンションドキュメントが変更されていない場合はどうでしょうか。これらの2つのポイント間の障害シナリオは、それらがアトミックでない場合、永続的な不整合を引き起こしますよね?プロセスに関する何かがアトミックである必要があります。それがトランザクションのポイントです。
Ian Varley

はい、あなたは正しいです。この場合、緊張は解消されていませんが、矛盾が生じます。ただし、不整合は、テンションドキュメントの次のスキャンでこれが検出されるまで一時的なものです。これは、この場合のトレードであり、時間に関する結果整合性の一種です。最初にソースアカウントをデクリメントし、後でターゲットアカウントをインクリメントする限り、これは許容できます。ただし、注意してください。テンションドキュメントでは、RESTに加えてACIDトランザクションは提供されません。ただし、これらは純粋なRESTとACIDの間の適切なトレードオフになる可能性があります。
ordnungswidrig 2009

4
すべてのテンションドキュメントにタイムスタンプがあり、アカウントドキュメントに「last-tension-applied」フィールド(または適用されたテンションのリスト)があるとします。ソースアカウントから借方に記入するときは、「last-tension-applied」フィールドも更新します。これらの2つの操作は、同じドキュメント上にあるため、アトミックです。ターゲットアカウントにも同様のフィールドがあります。そうすれば、システムはどのテンションドキュメントがどのアカウントに適用されているかを常に知ることができます。
ジェシーハレット

1
ソース/宛先ドキュメントがすでに更新されているかどうかを検出するにはどうすればよいですか?手順1の後で失敗した後、再実行されて再び失敗した場合はどうなりますか?など、ソースアカウントを差し引いたままになりますか?
wump 2010年

1
@wump:テンションドキュメントがアカウントに適用されたことを記録する必要があります。たとえば、テンションドキュメントIDをいずれかのアカウントのリストプロパティに追加します。テンションドキュメントが触れたすべてのアカウントが更新されたら、テンションドキュメントを「完了」としてマークするか、削除します。その後、すべてのアカウントのリストからドキュメントIDを削除できます。
ordnungswidrig 2010年

6

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

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

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

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


5

OPの問題への対応として、Couchはおそらくここでは最良の選択ではありません。ビューを使用することは在庫を追跡するための優れた方法ですが、0にクランプすることは多かれ少なかれ不可能です。問題は、ビューの結果を読み取り、「hammer-1」アイテムを使用しても問題ないと判断し、それを使用するためのドキュメントを作成するときの競合状態です。問題は、ビューの結果が0を超えるhammer-1がある場合にのみ、ハンマーを使用するドキュメントを作成するアトミックな方法がないことです。100人のユーザー全員が同時にビューをクエリして1つのhammer-1を表示した場合、すべてのユーザーがハンマー1を使用するドキュメントを作成でき、結果として-99のhammer-1になります。実際には、競合状態はかなり小さくなります。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関数を使用します。

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

{
    "_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」ドキュメントなどと同様に、各ドキュメントを見ると、非常にはっきりと読み取れます。そして、ビューはすべてのハードワークを処理します。


興味深い戦略。CouchDBの初心者として、現在のハンマーの数を計算するには、ハンマーの在庫変更の会社の履歴全体に対してマップ/リデュースを実行する必要があるように思われます。これは何年にもわたる変更になる可能性があります。このパフォーマンスを向上させるCouchDBの組み込み機能はありますか?
chadrik 2016

はい、CouchDBのビューは、連続した永続的なmap / reduceのようなものです。大規模なデータセットを最初から作成するには時間がかかるのは正しいですが、新しいドキュメントが追加されると、既存のビューが更新されるだけで、ビュー全体を再計算する必要はありません。ビューにはスペースとCPUの両方の要件があることに注意してください。また、少なくとも私がCouchDBを専門的に使用していたとき(数年経ちました)、組み込みのreduce関数のみを使用することが非常に重要でした。_和。カスタムJavascriptリデュース関数は非常に遅かった
wallacer 2016

3

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

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

編集:予測どおり、バージョン0.9以降、バルクドキュメントはこのように機能しなくなりました。


これは、議論されている状況、つまり複数のユーザーからの単一のドキュメントでの競合では実際には役に立ちません。
カー

3
CouchDB 0.9以降、一括更新のセマンティクスが変更されました。
バリーワーク

0

トランザクションにはSQliteのような軽量ソリューションを使用するだけで、トランザクションが正常に完了したら、それを複製し、SQLiteで複製されたとマークします。

SQLiteテーブル

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.