The technique in Section 9.9.11, “Simulating Transactions” will work if your data will allow the use of a view to effectively roll-up the changes into a single operation. However, if your data and document structure do not allow it then you can use a multi-phase transaction process to perform the operation in a number of distinct stages.
This method is not reliant on views, but the document structure and update make it easy to find out if there are 'hanging' or trailing transactions that need to be processed without additional document updates. Using views and the Observe operation to monitor changes could lead to long wait times during the transaction process while the view index is updated.
To employ this method, you use a similar transaction record as in the previous example, but use the transaction record to record each stage of the update process.
Start with the same two account records:
{ "type" : "account", "account" : "James", "value" : 100, "transactions" : [] }
The record explicitly contains a transactions
field which contains an array of all the currently active
transactions on this record.
The corresponding record for the other account:
{ "type" : "account", "account" : "Alice", "value" : 200, "transactions" : [] }
Now perform the following operations in sequence:
Create a new transaction record that records the transaction information:
{ "type" : "transaction", "fromacct" : "Alice", "toacct" : "James", "value" : 100, "status" : "waiting" }
The core of the transaction record is the same, the
difference is the use of a status field
which will be used to monitor the progress of the
transaction.
Record the ID of the transaction, for example,
transact_20120717163.
Set the value of the status field in the
transaction document to 'pending':
{ "type" : "transaction", "fromacct" : "Alice", "toacct" : "James", "value" : 100, "status" : "pending" }
Find all transaction records in the
pending state using a suitable view:
function(doc, meta) { if (doc.type && doc.status && doc.type == "transaction" && doc.status == "pending" ) { emit([doc.fromacct,doc.toacct], doc.value); } }
Update the record identified in toacct
with the transaction information, ensuring that the
transaction is not already pending:
{ "type" : "account", "account" : "Alice", "value" : 100, "transactions" : ["transact_20120717163"] }
Repeat on the other account:
{ "type" : "account", "account" : "James", "value" : 200, "transactions" : ["transact_20120717163"] }
Update the transaction record to mark that the records have been updated:
{ "type" : "transaction", "fromacct" : "Alice", "toacct" : "James", "value" : 100, "status" : "committed" }
Find all transaction records in the
committed state using a suitable view:
function(doc, meta) { if (doc.type && doc.status && doc.type == "transaction" && doc.status == "committed" ) { emit([doc.fromacct, doc.toacct], doc.value); } }
Update the source account record noted in the transaction and remove the transaction ID:
{ "type" : "account", "account" : "Alice", "value" : 100, "transactions" : [] }
Repeat on the other account:
{ "type" : "account", "account" : "James", "value" : 200, "transactions" : [] }
Update the transaction record state to 'done'. This will remove the transaction from the two views used to identify unapplied, or uncommitted transactions.
Within this process, although there are multiple steps required, you can identify at each step whether a particular operation has taken place or not.
For example, if the transaction record is marked as 'pending', but the corresponding account records do not contain the transaction ID, then the record still needs to be updated. Since the account record can be updated using a single atomic operation, it is easy to determine if the record has been updated or not.
The result is that any sweep process that accesses the views defined in each step can determine whether the record needs updating. Equally, if an operation fails, a record of the transaction, and whether the update operation has been applied, also exists, allowing the changes to be reversed and backed out.