Upload
paul-robinson
View
194
Download
0
Embed Size (px)
DESCRIPTION
MongoDB and similar document-based NoSQL datastores tend to offer limited transaction support. And with good reason, as using an ACID (Atomicity, Consistency, Isolation, Durability) transaction to make updates to multiple documents (potentially over multiple resources) can limit scalability. But there are alternative transaction models that can be used in favor of removing transactions altogether. In this presentation, you’ll see: How to use a compensating-transaction approach to provide many of the ACID guarantees without the scalability limitations that an ACID approach could bring. Cases in which ACID transactions might not be appropriate—in particular, why ACID transaction support for multiple document updates is rarely offered in a NoSQL datastore. How to use a compensating transaction as an alternative. How to develop applications that make transactional updates to multiple documents in a MongoDB datastore. How reliability can be built upon the primitives provided by MongoDB and how the middleware can abstract this from the developer. The majority of this talk will include a code example that uses the Narayana compensating-transactions API, which greatly simplifies the development of applications that need this transaction model.
Citation preview
Agenda
•ACID Transactions
•Compensating Transactions
•Code Example
•Today and Planned
Transactions with RDBS
Update balance and create an order atomically
id username item size
0 0 Stumpjumper L
id username email voucher
0 paul.robinson paul… 3000
Invoices
Users
id username item size
0 0 Stumpjumper L
1 0 Zesty L
id username email voucher
0 paul.robinson paul… 200
Invoices
Users
{ id: "<ObjectID1>", username: "paul.robinson", email: "[email protected]" voucher: 3000, invoices: { {"Stumpjumper", "L"}, } }
Transactions with Document Stores
Update balance and create an order atomically
{ id: "<ObjectID1>", username: "paul.robinson", email: "[email protected]" voucher: 200, invoices: { {"Stumpjumper", "L"}, {"Zesty", "L"} } }
But, sometimes this isn’t possible…
Change Multiple Documents{ user: ‘Paul’ balance: 1000 }
{ user: ‘Fred’ balance: 0 }
{ user: ‘Paul’ balance: 700 }
{ user: ‘Fred’ balance: 300 }
E.g. Money Transfer, audited delete, integration
Why doesn’t MongoDB support multi-document transactions?
Scaling MongoDB
Router (mongos)
Shard
Master
D1
Slave 1
D1
Slave 2
D1
Shard
Master
D2
Slave 1
D2
Slave 2
D2
Multi-Document Transactions
Client
Shard
D1Router
(mongos)Shard
D2
D1
Multi-Document Transactions
Client
Shard
D1Router
(mongos)Shard
D2
Multi-Document Transactions
Client
Shard
D1Router
(mongos)Shard
D2
Client D1?
Client
Multi-Document Transactions
ClientD2
Shard
D1Router
(mongos)Shard
D2
Client D1?
Client
Multi-Document Transactions
Client
Shard
D1Router
(mongos)Shard
D2
Client D1?
D1Client
Multi-Document Transactions
Client
Shard
D1Router
(mongos)Shard
D2
Done
ClientD1
(Multi-document) ACID doesn’t scale …
Don’t throw out transactions altogether!
Extended Transactions
• Umbrella term
• Alternatives to ACID
Guarantees
ACIDNone Extended
ACID Vs Compensating Transactions
• ACID 1. Lock each resource 2. Commit/rollback each resource !
• Compensating 1. Commit each resource 2. Confirm/compensate each resource
Compensating Transactions
• Eventually consistent
• Relaxed isolation
• Can move locks to application logic
• Support Long running tasks
Transaction OptionsAtomic Atomic Batch Traditional
ACIDCompensating!Transactions
Single Doc ✓ ✓ ✓ ✓Multi Doc O ✓ ✓ ✓Sharding ✓ O O ✓
Multi stores O O ✓ ✓ACID ✓ ✓ ✓ O
App Unaware ✓ ✓ ✓ O
Existing Patterns
• Compensating Transaction
• Just a pattern
• Needs implementing
• Recovery is hard!
MongoDB
Application (incl transaction and recovery code)
Narayana’s Solution in WildFly 8
• Middleware
• Transaction Manager driven
• Annotation-based API
• Based on Sagas
MongoDB
Narayana
Application
Protocol Details
Starting State{ user: ‘A’ bal: 1000 }
{ user: ‘B’ bal: 0 }
Log Compensation Handler 1{ user: ‘A’ bal: 1000 }
{ user: ‘A’ bal: 1000 }
{ user: ‘B’ bal: 0 }
{ user: ‘B’ bal: 0 }
<txid> pending <comp1>
Update Document 1{ user: ‘A’ bal: 1000 }
{ user: ‘A’ bal: 1000 }
{ user: ‘A’ bal: 700 tx: <txid> }
{ user: ‘B’ bal: 0 }
{ user: ‘B’ bal: 0 }
{ user: ‘B’ bal: 0 }
<txid> pending <comp1>
<txid> pending <comp1>
Log Compensation Handler 2{ user: ‘A’ bal: 1000 }
{ user: ‘A’ bal: 1000 }
{ user: ‘A’ bal: 700 tx: <txid> }
{ user: ‘A’ bal: 700 tx: <txid> }
{ user: ‘B’ bal: 0 }
{ user: ‘B’ bal: 0 }
{ user: ‘B’ bal: 0 }
{ user: ‘B’ bal: 0 }
<txid> pending <comp1>
<txid> pending <comp1>
<txid> pending <comp1> <comp2>
Update Document 2{ user: ‘A’ bal: 1000 }
{ user: ‘A’ bal: 1000 }
{ user: ‘A’ bal: 700 tx: <txid> }
{ user: ‘A’ bal: 700 tx: <txid> }
{ user: ‘A’ bal: 700 tx: <txid> }
{ user: ‘B’ bal: 0 }
{ user: ‘B’ bal: 0 }
{ user: ‘B’ bal: 0 }
{ user: ‘B’ bal: 0 }
{ user: ‘B’ bal: 300 tx: <txid> }
<txid> pending <comp1>
<txid> pending <comp1>
<txid> pending <comp1> <comp2>
<txid> pending <comp1> <comp2>
Update Transaction Log{ user: ‘A’ bal: 1000 }
{ user: ‘A’ bal: 1000 }
{ user: ‘A’ bal: 700 tx: <txid> }
{ user: ‘A’ bal: 700 tx: <txid> }
{ user: ‘A’ bal: 700 tx: <txid> }
{ user: ‘B’ bal: 0 }
{ user: ‘B’ bal: 0 }
{ user: ‘B’ bal: 0 }
{ user: ‘B’ bal: 0 }
{ user: ‘B’ bal: 300 tx: <txid> }
<txid> pending <comp1>
<txid> pending <comp1>
<txid> pending <comp1> <comp2>
<txid> pending <comp1> <comp2>
{ user: ‘A’ bal: 700 tx: <txid> }
{ user: ‘B’ bal: 300 tx: <txid> }
<txid> committing <comp1> <comp2>
Confirm Document 1{ user: ‘A’ bal: 1000 }
{ user: ‘A’ bal: 1000 }
{ user: ‘A’ bal: 700 tx: <txid> }
{ user: ‘A’ bal: 700 tx: <txid> }
{ user: ‘A’ bal: 700 tx: <txid> }
{ user: ‘B’ bal: 0 }
{ user: ‘B’ bal: 0 }
{ user: ‘B’ bal: 0 }
{ user: ‘B’ bal: 0 }
{ user: ‘B’ bal: 300 tx: <txid> }
<txid> pending <comp1>
<txid> pending <comp1>
<txid> pending <comp1> <comp2>
<txid> pending <comp1> <comp2>
{ user: ‘A’ bal: 700 tx: <txid> }
{ user: ‘B’ bal: 300 tx: <txid> }
<txid> committing <comp1> <comp2>
{ user: ‘A’ bal: 700 tx: <txid> }
{ user: ‘B’ bal: 300 tx: <txid> }
<txid> committing <comp1> <comp2>
Confirm Document 2{ user: ‘A’ bal: 1000 }
{ user: ‘A’ bal: 1000 }
{ user: ‘A’ bal: 700 tx: <txid> }
{ user: ‘A’ bal: 700 tx: <txid> }
{ user: ‘A’ bal: 700 tx: <txid> }
{ user: ‘B’ bal: 0 }
{ user: ‘B’ bal: 0 }
{ user: ‘B’ bal: 0 }
{ user: ‘B’ bal: 0 }
{ user: ‘B’ bal: 300 tx: <txid> }
<txid> pending <comp1>
<txid> pending <comp1>
<txid> pending <comp1> <comp2>
<txid> pending <comp1> <comp2>
{ user: ‘A’ bal: 700 tx: <txid> }
{ user: ‘B’ bal: 300 tx: <txid> }
<txid> committing <comp1> <comp2>
{ user: ‘A’ bal: 700 tx: <txid> }
{ user: ‘B’ bal: 300 tx: <txid> }
<txid> committing <comp1> <comp2>
{ user: ‘A’ bal: 700 }
{ user: ‘B’ bal: 300 tx: <txid> }
<txid> committing <comp1> <comp2>
Completed{ user: ‘A’ bal: 1000 }
{ user: ‘A’ bal: 1000 }
{ user: ‘A’ bal: 700 tx: <txid> }
{ user: ‘A’ bal: 700 tx: <txid> }
{ user: ‘A’ bal: 700 tx: <txid> }
{ user: ‘B’ bal: 0 }
{ user: ‘B’ bal: 0 }
{ user: ‘B’ bal: 0 }
{ user: ‘B’ bal: 0 }
{ user: ‘B’ bal: 300 tx: <txid> }
<txid> pending <comp1>
<txid> pending <comp1>
<txid> pending <comp1> <comp2>
<txid> pending <comp1> <comp2>
{ user: ‘A’ bal: 700 tx: <txid> }
{ user: ‘B’ bal: 300 tx: <txid> }
<txid> committing <comp1> <comp2>
{ user: ‘A’ bal: 700 tx: <txid> }
{ user: ‘B’ bal: 300 tx: <txid> }
<txid> committing <comp1> <comp2>
{ user: ‘A’ bal: 700 }
{ user: ‘B’ bal: 300 tx: <txid> }
<txid> committing <comp1> <comp2>
{ user: ‘A’ bal: 700 }
{ user: ‘B’ bal: 300 !}
Code Example
Money Transfer
• Each user has a balance
• Move money between users
• Update both documents atomically
public class BankingService { ! @Inject AccountManager accountManager; ! @Compensatable public void transferMoney(String fromAccount, String toAccount, Integer amount) { ! accountManager.debitAccount(fromAccount, amount); accountManager.creditAccount(toAccount, amount); }
Service
public class AccountManager { ! @Inject AccountDAO accountDAO; @Inject private CompensationManager compensationManager; @Inject CreditData creditData; ! @TxCompensate(UndoCredit.class) public void creditAccount(String account, Integer amount) { ! //High value transfers (over 500) are not allowed with this service if (amount > 500) compensationManager.setCompensateOnly() && return; creditData.setToAccount(account); creditData.setAmount(amount); accountDAO.incrementBalance(account, amount); } …
Account Manager (credit part)
… @Inject DebitData debitData; ! @TxCompensate(UndoDebit.class) public void debitAccount(String account, Integer amount) { ! debitData.setFromAccount(account); debitData.setAmount(amount); ! accountDAO.incrementBalance(account, -1 * amount); } }
Account Manager (debit part)
@CompensationScoped public class CreditData implements Serializable { ! private String toAccount; private Integer amount; … } !@CompensationScoped public class DebitData implements Serializable { ! private String fromAccount; private Integer amount; … }
Compensation Data
public class UndoCredit implements CompensationHandler { ! @Inject CreditData creditData; @Inject AccountDAO accountDAO; ! public void compensate() { ! if (creditData.getToAccount() != null) { accountDAO.incrementBalance( creditData.getToAccount(), -1 * creditData.getAmount()); } } }
Compensation Handler (Credit)
What we have today
• Compensating-Transactions API
• MongoDB Example
• Other Quickstarts
• Blog post series
• Ships with WildFly 8.Final+
What’s Planned
• MongoDB RM
• Compensating state Recovery
• Throughput/Scalability study
• RDBMS integration
• Other language support
Getting Started• Explore the quickstarts http://github.com/jbosstm/quickstart/
• Give feedback (forum) http://community.jboss.org/en/jbosstm
• Track issues http://issues.jboss.org/browse/JBTM
• Subscribe to Blog http://jbossts.blogspot.co.uk/
• Contact me [email protected], @pfrobinson