Couchbase Serverはトランザクションをサポートしていませんが、その効果を適切なドキュメントとビュー定義を書くことでシュミレートすることができ、これは単一ドキュメントに適用される更新を必要としていても効果があります。
たとえば、一般的な銀行業務アプリケーションを考え、ドキュメント構造を次のようにします:
{ "account" : "James", "value" : 100 }
もうひとつの口座に対応するレコード:
{ "account" : "Alice", "value" : 200 }
各口座の残高を取得するには、次のmap()
を利用します:
function(doc, meta) { if (doc.account && doc.value) { emit(doc.account,doc.value); } }
reduce()
関数はビルトインの_sum
関数を使用できます。
問い合わせのとき、group_level
の1を使用して、口座の残高を表示します:
{"rows":[ {"key":"Alice","value":200}, {"key":"James","value":100} ] }
口座内の金額は口座名と値でもうひとつのレコードをシステムに追加することによってのみ更新できます。たとえば、レコードを追加します:
{ "account" : "James", "value" : 50 }
ビューへの再問い合わせは各口座の残高を更新します:
{"rows":[ {"key":"Alice","value":200}, {"key":"James","value":150} ] }
しかし、AliceがJamesに$100送金したい場合、2つのレコードの更新が必要です:
Aliceの口座から$100減らす更新のレコード
Jameの口座へ$100追加する更新のレコード
残念なことに、ステップ1とステップ2の間に問題が発生すると、トランザクションの整合性が損なわれる可能性があります。Jamesのレコードの更新なしに、Aliceの口座から引き落とすことがありえます。
あるレコードだけ作成(もしくは更新)している間、このトランザクションをシュミレートするには、取引記録とビューの組み合わせを使用しなければなりません。取引記録はこのようになります:
{ "fromacct" : "Alice", "toacct" : "James", "value" : 100 }
上記はある口座から別の口座への資金移動を記録します。ビューは、取引記録を処理するために更新され、各アカウントの値を更新するemit()
で行を出力します。
function(doc, meta) { if (doc.fromacct) { emit(doc.fromacct, -doc.value); emit(doc.toacct, doc.value); } else { emit(doc.account, doc.value); } }
上記のmap()
は2つの偽の行を効果的に生成し、ひとつの行は送金元の口座から金額を引き、もうひとつの行は送金先の口座に金額を追加します。そして、結果ビューでは最終的な残高を計算するために各口座の取引記録を集計するreduce()
関数を使用します。
{"rows":[ {"key":"Alice","value":100}, {"key":"James","value":250} ] }
プロセスをとおして、ひとつのレコードのみが生成され、そのためレコード更新による一時的な問題は存在する格納データを壊したり、使えなくすることなしにとらえることができます。