into a separate back-end component, you preserve the
availability of your customer-facing component. The
lower availability of the message processor may be acceptable for business requirements.
Suppose, however, that 2PC is simply never acceptable
in your system. How can this problem be solved? First,
you need to understand the concept of idempotence. An
operation is considered idempotent if it can be applied
one time or multiple times with the same result. Idempotent operations are useful in that they permit partial
failures, as applying them repeatedly does not change the
final state of the system.
The selected example is problematic when looking
for idempotence. Update operations are rarely idempotent. The example increments balance columns in place.
Applying this operation more than once obviously will
result in an incorrect balance. Even update operations
that simply set a value, however, are not idempotent
with regard to order of operations. If the system cannot
guarantee that updates will be applied in the order they
are received, the final state of the system will be incorrect.
More on this later.
In the case of balance
updates, you need a way to
track which updates have
been applied successfully
and which are still outstanding. One technique is
to use a table that records
the transaction identifiers
that have been applied.
The table shown in
figure 6 tracks the transaction ID, which balance has
been updated, and the user
ID where the balance was
applied. Now our sample
pseudocode is as shown in
figure 7 .
This example depends
upon being able to peek a
message in the queue and
remove it once success-
f ully processed. This can
be done with two indepen-
d ent transactions if neces-
FIG 7 s ary: one on the message
q ueue and one on the user
d atabase. Queue operations
a re not committed unless
U pdateTa ble
updates_applied
trans_id
balance
user_id
FIG 6
dequeue each message and apply the information to the
user table. The example appears to solve all of the issues,
but there is a problem. The message persistence is on the
transaction host to avoid a 2PC during queuing. If the
message is dequeued inside a transaction involving the
user host, we still have a 2PC situation.
One solution to the 2PC in the message-processing
component is to do nothing. By decoupling the update
B e gintransaction
Insert into transaction(id, seller_id, buyer_id, amount);
Queue message “update user(“seller”, seller_id, amount)”;
Queue message “update user(“buyer”, buyer_id, amount)”;
E nd transaction
F or each message in queue
Peek message
Begin transaction
Select count(*) as processed where trans_id=message.trans_id
and balance=message.balance and user_id=message.user_id
If processed == 0
If message.balance == “seller”
Update user set amt_sold=amt_sold + message.amount
where id=message.id;
Else
Update user set amt_bought=amt_bought + message.amount
where id=message.id;
End if
Insert into updates_applied
( message.trans_id, message.balance, message.user_id);
End if
End transaction
If transaction successful
Remove message from queue
End if