This is a talk I gave at QCON ArchSummit in Shenzhen.
The microservice architecture structures an application as a set of loosely coupled, collaborating services. Maintaining data consistency is challenging since each service has its own database to ensure loose coupling. To make matters worse, for a variety of reasons distributed transactions using JTA are not an option for modern applications.
In this talk we describe an alternative transaction model known as a saga. You will learn about the benefits and drawbacks of using sagas. We describe how sagas are eventually consistent rather than ACID and what this means for developers. You will learn how to design and implement sagas in a Java application.
SpotFlow: Tracking Method Calls and States at Runtime
Ā
ArchSummit Shenzhen - Using sagas to maintain data consistency in a microservice architecture
1. @crichardson
Using sagas to maintain data
consistency in a microservice
architecture
Chris Richardson
Founder of Eventuate.io
Founder of the original CloudFoundry.com
Author of POJOs in Action
@crichardson
chris@chrisrichardson.net
http://eventuate.io
9. @crichardson
About Chris
Founder of a startup that is creating
an open-source/SaaS platform
that simpliļ¬es the development of
transactional microservices
(http://eventuate.io)
19. @crichardson
Cannot use ACID transactions
BEGIN TRANSACTION
ā¦
SELECT ORDER_TOTAL
FROM ORDERS WHERE CUSTOMER_ID = ?
ā¦
SELECT CREDIT_LIMIT
FROM CUSTOMERS WHERE CUSTOMER_ID = ?
ā¦
INSERT INTO ORDERS ā¦
ā¦
COMMIT TRANSACTION
20. @crichardson
Cannot use ACID transactions
BEGIN TRANSACTION
ā¦
SELECT ORDER_TOTAL
FROM ORDERS WHERE CUSTOMER_ID = ?
ā¦
SELECT CREDIT_LIMIT
FROM CUSTOMERS WHERE CUSTOMER_ID = ?
ā¦
INSERT INTO ORDERS ā¦
ā¦
COMMIT TRANSACTION
Private to the
Order Service
Private to the
Customer Service
21. @crichardson
Cannot use ACID transactions
BEGIN TRANSACTION
ā¦
SELECT ORDER_TOTAL
FROM ORDERS WHERE CUSTOMER_ID = ?
ā¦
SELECT CREDIT_LIMIT
FROM CUSTOMERS WHERE CUSTOMER_ID = ?
ā¦
INSERT INTO ORDERS ā¦
ā¦
COMMIT TRANSACTION
Private to the
Order Service
Private to the
Customer Service
Distributed transactions
25. @crichardson
2PC is not an option
Guarantees consistency
BUT
2PC coordinator is a single point of failure
26. @crichardson
2PC is not an option
Guarantees consistency
BUT
2PC coordinator is a single point of failure
Chatty: at least O(4n) messages, with retries O(n^2)
27. @crichardson
2PC is not an option
Guarantees consistency
BUT
2PC coordinator is a single point of failure
Chatty: at least O(4n) messages, with retries O(n^2)
Reduced throughput due to locks
28. @crichardson
2PC is not an option
Guarantees consistency
BUT
2PC coordinator is a single point of failure
Chatty: at least O(4n) messages, with retries O(n^2)
Reduced throughput due to locks
Not supported by many NoSQL databases (or message brokers)
29. @crichardson
2PC is not an option
Guarantees consistency
BUT
2PC coordinator is a single point of failure
Chatty: at least O(4n) messages, with retries O(n^2)
Reduced throughput due to locks
Not supported by many NoSQL databases (or message brokers)
CAP theorem 2PC impacts availability
30. @crichardson
2PC is not an option
Guarantees consistency
BUT
2PC coordinator is a single point of failure
Chatty: at least O(4n) messages, with retries O(n^2)
Reduced throughput due to locks
Not supported by many NoSQL databases (or message brokers)
CAP theorem 2PC impacts availability
ā¦.
33. @crichardson
Saga
Use Sagas instead of 2PC
Distributed transaction
Service A Service B
Service A
Local
transaction
Service B
Local
transaction
Service C
Local
transaction
X Service C
36. @crichardson
Order Service
Create Order Saga
Local transaction
Order
state=PENDING
createOrder()
Customer Service
Local transaction
Customer
reserveCredit()
createOrder()
37. @crichardson
Order Service
Create Order Saga
Local transaction
Order
state=PENDING
createOrder()
Customer Service
Local transaction
Customer
reserveCredit()
Order Service
Local transaction
Order
state=APPROVED
approve
order()
createOrder()
43. @crichardson
Order Service
Create Order Saga - rollback
Local transaction
Order
createOrder()
Customer Service
Local transaction
Customer
reserveCredit()
Order Service
Local transaction
Order
reject
order()
createOrder()
FAIL
Insufļ¬cient credit
45. @crichardson
Sagas complicate API design
Request initiates the saga. When to send back the response?
Option #1: Send response when saga completes:
+ Response speciļ¬es the outcome
- Reduced availability
46. @crichardson
Sagas complicate API design
Request initiates the saga. When to send back the response?
Option #1: Send response when saga completes:
+ Response speciļ¬es the outcome
- Reduced availability
Option #2: Send response immediately after creating the saga
(recommended):
+ Improved availability
- Response does not specify the outcome. Client must poll or be
notiļ¬ed
47. @crichardson
Revised Create Order API
createOrder()
returns id of newly created order
NOT fully validated
getOrder(id)
Called periodically by client to get outcome of validation
48. @crichardson
Minimal impact on UI
UI hides asynchronous API from the user
Saga will usually appear instantaneous (<= 100ms)
If it takes longer UI displays āprocessingā popup
Server can push notiļ¬cation to UI
50. @crichardson
Sagas complicate the
business logic
Changes are committed by each step of the saga
Other transactions see āinconsistentā data, e.g. Order.state =
PENDING more complex logic
51. @crichardson
Sagas complicate the
business logic
Changes are committed by each step of the saga
Other transactions see āinconsistentā data, e.g. Order.state =
PENDING more complex logic
Interaction between sagas and other operations
e.g. what does it mean to cancel a PENDING Order?
āInterruptā the Create Order saga
Wait for the Create Order saga to complete?
53. @crichardson
How to sequence the saga
transactions?
After the completion of transaction Ti āsomethingā must
decide what step to execute next
Success: which T(i+1) - branching
Failure: C(i - 1)
54. @crichardson
Order Service
Orchestration-based saga coordination
Local transaction
Order
state=PENDING
createOrder()
Customer Service
Local transaction
Customer
reserveCredit()
Order Service
Local transaction
Order
state=APPROVED
approve
order()
createOrder()
CreateOrderSaga
82. @crichardson
Create Order Saga messaging
Order Service
Message Broker
Customer Service
Begin TXN
Read data
Send reply
Commit TXN
Credit Reserved
Credit Limit Exceeded
Create Order
Saga
Orchestrator
reserve credit
Customer Service
Request Channel
Customer Service
Reply Channel
Order
create()
approve()
reject()
83. @crichardson
Create Order Saga messaging
Order Service
Message Broker
Customer Service
Begin TXN
Read data
Send reply
Commit TXN
Credit Reserved
Credit Limit Exceeded
Create Order
Saga
Orchestrator
reserve credit
Customer Service
Request Channel
Customer Service
Reply Channel
Order
create()
approve()
reject()
Commands
84. @crichardson
Create Order Saga messaging
Order Service
Message Broker
Customer Service
Begin TXN
Read data
Send reply
Commit TXN
Credit Reserved
Credit Limit Exceeded
Create Order
Saga
Orchestrator
reserve credit
Customer Service
Request Channel
Customer Service
Reply Channel
Order
create()
approve()
reject()
Commands
Replies
86. @crichardson
Option #1: Use database
table as a message queue
See BASE: An Acid Alternative, http://bit.ly/ebaybase
Customer
Service
ORDER_ID CUSTOMER_ID TOTAL
99
CUSTOMER_CREDIT_RESERVATIONS table
101 1234
ID TYPE DATA DESTINATION
MESSAGE table
84784 OrderCreated {ā¦} ā¦
INSERT INSERT
Message
Publisher
QUERY
Message
Broker
Publish
Local transaction
87. @crichardson
Option #1: Use database
table as a message queue
ACID
transaction
See BASE: An Acid Alternative, http://bit.ly/ebaybase
DELETE
Customer
Service
ORDER_ID CUSTOMER_ID TOTAL
99
CUSTOMER_CREDIT_RESERVATIONS table
101 1234
ID TYPE DATA DESTINATION
MESSAGE table
84784 OrderCreated {ā¦} ā¦
INSERT INSERT
Message
Publisher
QUERY
Message
Broker
Publish
Local transaction
88. @crichardson
Option #1: Use database
table as a message queue
ACID
transaction
See BASE: An Acid Alternative, http://bit.ly/ebaybase
DELETE
Customer
Service
ORDER_ID CUSTOMER_ID TOTAL
99
CUSTOMER_CREDIT_RESERVATIONS table
101 1234
ID TYPE DATA DESTINATION
MESSAGE table
84784 OrderCreated {ā¦} ā¦
INSERT INSERT
Message
Publisher
QUERY
Message
Broker
Publish
Local transaction
?
90. @crichardson
Option #2: Event sourcing:
event-centric persistence
Service
Event Store
save events
and
publish
Event table
Entity type
Event
id
Entity
id
Event
data
Event
type
Every state change event
91. @crichardson
Option #2: Event sourcing:
event-centric persistence
Service
Event Store
save events
and
publish
Event table
Entity type
Event
id
Entity
id
Event
data
Event
type
Order 901101 ā¦OrderCreated
Every state change event
92. @crichardson
Option #2: Event sourcing:
event-centric persistence
Service
Event Store
save events
and
publish
Event table
Entity type
Event
id
Entity
id
Event
data
Order 902101 ā¦OrderApproved
Event
type
Order 901101 ā¦OrderCreated
Every state change event
93. @crichardson
Option #2: Event sourcing:
event-centric persistence
Service
Event Store
save events
and
publish
Event table
Entity type
Event
id
Entity
id
Event
data
Order 902101 ā¦OrderApproved
Order 903101 ā¦OrderShipped
Event
type
Order 901101 ā¦OrderCreated
Every state change event
94. @crichardson
Summary
Microservices tackle complexity and accelerate development
Database per service is essential for loose coupling
Use sagas to maintain data consistency across services
Use transactional messaging to make sagas reliable