Bringing Transactional Guarantees to MongoDB

Post on 04-Jul-2015

195 views 0 download

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.

Transcript of Bringing Transactional Guarantees to MongoDB

Bringing Transactional Guarantees to MongoDB

Paul Robinson paul.robinson@redhat.com

@pfrobinson

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: "paul.robinson@redhat.com" voucher: 3000, invoices: { {"Stumpjumper", "L"}, } }

Transactions with Document Stores

Update balance and create an order atomically

{ id: "<ObjectID1>", username: "paul.robinson", email: "paul.robinson@redhat.com" 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 paul.robinson@redhat.com, @pfrobinson