Database Reference
In-Depth Information
Note that the
retire_transaction
is
idempotent
: it can be called any number of times with
the same
txn_id
with the same effect as calling it once. This means that if we have a crash at
any point before removing the transaction object, a subsequent cleanup process can still retire
the transaction by simply calling
retire_transaction
again.
We now need to take care of transactions that have timed out, or where the commit or rollback
process has crashed in a periodic cleanup task:
def
def
cleanup_transactions
(
txn
,
max_txn_time
):
# Find & commit partially-committed transactions
for
for
txn
iin
db
.
transaction
.
find
({
'state'
:
'commit'
}, {
'_id'
:
1
}):
retire_transaction
(
txn
[
'_id'
])
# Move expired transactions to 'rollback' status:
cutoff
=
now
-
max_txn_time
db
.
transaction
.
update
(
{
'_id'
:
txnid
,
'state'
:
'new'
,
'ts'
: {
'$lt'
:
cutoff
} },
{
'$set'
: {
'state'
:
'rollback'
} })
# Actually rollback transactions
for
for
txn
iin
db
.
transaction
.
find
({
'state'
:
'rollback'
}):
rollback_transfer
()
Finally, in the case where we want to roll back a transfer, we must update the transaction ob-
ject and
undo
the effects of the transfer:
def
def
rollback_transfer
(
txn
):
db
.
accounts
.
update
(
{
'_id'
:
txn
[
'src'
],
'txns._id'
:
txn
[
'_id'
] },
{
'$inc'
: {
'balance'
:
txn
[
'amt'
] },
'$pull'
: {
'txns'
: {
'_id'
:
txn
[
'_id'
] } } })
db
.
accounts
.
update
(
{
'_id'
:
txn
[
'dst'
],
'txns._id'
:
txn
[
'_id'
] },
{
'$inc'
: {
'balance'
:
-
txn
[
'amt'
] },
'$pull'
: {
'txns'
: {
'_id'
:
txn
[
'_id'
] } } })
db
.
transaction
.
remove
({
'_id'
:
txn
[
'_id'
]})
Note in particular that the preceding code will only undo a transaction in an account if the
transaction is still stored in that account's
txns
array. This makes the rollback of the transac-
tion idempotent just like retiring a transaction via a commit.