SlideShare a Scribd company logo
The dual write problem
Jeppe Cramon - @jeppec
Cloud Create ApS
@jeppec
Essential complexity of 2 way integration
Component
Warehouse
Component
Order
Component
Billing
UI
Send-
Invoice
Save-Order
Order:Save-Order()è
call Warehouse:Reserve-Items()
call Billing:Send-Invoice()
commit()
Reserve-
Items
Local transaction between
the 3 Components
@jeppec
Let’s (micro)service’ify this
@jeppec
Accidental complexity from distributed service integration
Warehouse
Service
Order
Service
Billing
Service
UI
Send-
Invoice
Save-Order
Reserve-
Items
Local transaction between 2
local “Services”
DUAL WRITE
using Remote call
@jeppec
Microservices turns a
local functional call
into a distributed problem
@jeppec
With cross SERVICE integration
we’re bound by the laws of
distributed computing
@jeppec
The 11 Fallacies of Distributed Computing
These fallacies are assumptions architects, designers and developers
of distributed systems are likely to make. The fallacies will be proven
wrong in the long run - resulting in all sorts of troubles and pains for
the solution and architects who made the assumptions.
1. The network is reliable.
2. Latency is zero.
3. Bandwidth is infinite.
4. The network is secure.
5. Topology doesn't change.
6. There is one administrator.
7. Transport cost is zero.
8. The network is homogeneous.
9. The System is atomic/monolithic
10. The System is Finished
11. Business logic can and should be centralized
See https://arnon.me/wp-content/uploads/Files/fallacies.pdf
@jeppec
Order:Save-Order()è
call Warehouse:Reserve-Items()
call Billing:Send-Invoice()
if (Billing:Call-Failed:Too-Busy?)
Wait-A-While()
call Billing:Send-Invoice()
if (Billing:Call-Failed:Too-Busy?)
Wait-A-Little-While-Longer()
call Billing:Send-Invoice()
if (Billing:Call-Failed:IO-Error?)
Save-We-Need-Check-If-Call-Billing-Succeded-After-All
AND We-Need-To-Retry call Order:Save-Order and call Warehouse:Reserve-Items
AND Tell-Customer-That-This-Operation-Perhaps-Went-Well
if (Billing:Call-Went-Well?)
commit()
Accidental complexity from distributed service integration
Warehouse
Service
Order
Service
Billing
Service
UI
Send-
Invoice
Save-Order
Reserve-
Items
Local transaction between 2
Components
@jeppec
Remote Call
Aka. RPC/REST/SOAP/GraphQL…
@jeppec
What’s the challenge using
state mutating
Request/Response calls
between
distributed components?
@jeppec
Synchronous Request/Response
lowers our tolerance for faults
• When you get an IO error
• When servers crash or restarts
• When databases are down
• When deadlocks occurs in our databases
• Do you retry?
With synchronous Request/Response we can loose business data if there’s no automatic retry mechanism.
Also if the operation we retry isn’t idempotent* we risk having the side effect multiple times!
Client Server
Processing
The same message can be
processed more than once
*Idempotence describes the quality of an
operation in which result and state does
not change if the operation is performed
more than 1 time
Request
Processing
Duplicated Request
Duplicated Response
Response
@jeppec
Idempotence
Don’t press the button twice!
@jeppec
Ensuring
transactional
consistency
becomes much harder
@jeppec
Sales system
Sales
Update customer status (e.g. Gold customer)
Bookkeeping
Deliver goods
Delivery
system
Deliveries
Customer/
CRM system
Customers
SAP
Bookkeeping
Complete
Purchase
Transaction
Coordinator
Transactional
Resource
Request-to-Prepare
Commit
Prepared
Done
Prepare
Phase
Commit
Phase
2 Phase Commit
@jeppec
What’s wrong with distributed transactions?
• Transactions lock resources while active
• Services are autonomous
• Can’t be expected to finish within a certain time interval
• Locking keeps other transactions from completing their
job
• Locking doesn’t scale
• X Phase Commit is fragile by design
How to handle the dual
write problem?
@jeppec
Dual write problem
Order
Service
Billing
Service
Client
Send-
Invoice
Save-Order
Mutating
Remote call
Expected
transaction
boundary No simple solution (except a classic monolith)
• Local-commit-then-publish
• If the app crashes after local-
commit then the publish
operation isn’t performed
• Publish-then-local-commit
• If the local-commit fails then
we’ve already formed the publish
operation
• Billing Service may receive
published message BEFORE the
Order Service has committed
its transaction
@jeppec
Orchestration
@jeppec
Dual write problem
Orchestration / Process Manager / Saga*
Order
Service
Billing
Service
Client
Send-
Invoice
Transaction
boundary
Transaction
boundary
Save-Order
• Requires only local transactions
• Single coordinator
• Increases eventual consistency
• Requires idempotent operations
due to retries
• Requires compensations /
rollback
• Tighter coupling as the
orchestrator instructs other
services about what to do
* This isn’t the real Saga pattern
@jeppec
Choreography
@jeppec
Dual write problem
Choreography
public class OrderService {
public void handle(AcceptOrder cmd) {
orderRepository.add(new Order(cmd.orderId,
cmd.orderLines));
eventTopic.publish(new OrderAccepted(cmd.orderId,
cmd.orderLines));
}
}
public class BillingService {
public void handle(OrderAccepted cmd) {
invoiceRepository.add(new Invoice(cmd.orderId,
cmd.orderLines));
emailGateway.send(new OrderAcceptedEmail(cmd.orderId,
cmd.orderLines));
}
}
@jeppec
Dual write problem
Choreography
Order
Service
Billing
Service
Client
Send-
Invoice
Save-Order
Transaction
boundary
Transaction
boundary
• No Single coordinator, but global
system state and coordination
logic is scattered across all
participants
• Requires only local transactions
• Increases eventual consistency
• Requires idempotent operations
due to retries
• Looser coupling (if events are
used)
• Suffers from a Dual Write problem
it self
Events/Commands
Queue
@jeppec
Outbox pattern
For Choreography
@jeppec
Dual write problem
Choreography with Outbox pattern
Order
Service
Billing
Service
Client
Send-
Invoice
Save-Order
Transaction
boundary
Transaction
boundary
• You can either use a Change
Data Capture (CDC) approach
e.g. using a database Write
Ahead Log (WAL)
• Risk of CRUD events!
• Or a manual OutBox pattern
where Save-Order writes data
and the event/command into
the OutBox within the same
database. Typically requires
polling and coordination +
recovery options in a multi cluster
setup
Events/Commands
Change
Data
Capture
/
Polling
Async Push or Poll
@jeppec
Saga pattern
A failure management pattern
@jeppec
Trip reservation as a Saga
Start
Rent Car Book Hotel Reserve Flight
Cancel Hotel
Cancel Car
Book
Car Rental Hotel Rental
WorkQueue:
- Queue: “Rent Car”
- Queue: “Book Hotel”
- Queue: “Reserve Flight”
Routing slip
Flight Booking
@jeppec
Trip reservation as a Saga
Start
Rent Car Book Hotel Reserve Flight
Cancel Hotel
Cancel Car
Book Book
Car Rental Hotel Rental
WorkQueue:
- Queue: “Book Hotel”
- Queue : “Reserve Flight”
WorkStack:
- ReservationId: ABC123
Cancellation: “Cancel Car”
Routing slip Flight Booking
@jeppec
Trip reservation as a Saga
Start
Rent Car Book Hotel Reserve Flight
Cancel Hotel
Cancel Car
Book Book
Car Rental Hotel Rental
WorkQueue:
- Queue : “Reserve Flight”
WorkStack:
- Hotel ReservationId: XYZ
Cancellation: “Cancel Hotel”
- Car ReservationId: ABC123
Cancellation: “Cancel Car”
Routing slip
Book
Flight Booking
@jeppec
Trip reservation as a Saga
Start
Rent Car Book Hotel Reserve Flight
Cancel Hotel
Cancel Car
Book Book Book
Car Rental Hotel Rental
WorkStack:
- Hotel ReservationId: XYZ
Cancellation: “Cancel Hotel”
- Car ReservationId: ABC123
Cancellation: “Cancel Car”
Routing slip
Flight Booking
@jeppec
Trip reservation as a Saga
Start
Rent Car Book Hotel Reserve Flight
Cancel Hotel
Cancel Car
Book Book Book
Car Rental Hotel Rental
Cancel
WorkStack:
- Hotel ReservationId: XYZ
Cancellation: “Cancel Hotel”
- Car ReservationId: ABC123
Cancellation: “Cancel Car”
Routing slip
Flight Booking
@jeppec
Trip reservation as a Saga
Start
Rent Car Book Hotel Reserve Flight
Cancel Hotel
Cancel Car
Book Book Book
Car Rental Hotel Rental
Cancel
Cancel
WorkStack:
- Car ReservationId: ABC123
Cancellation: “Cancel Car”
Routing slip
Flight Booking
@jeppec
Trip reservation as a Saga
Start
Rent Car Book Hotel Reserve Flight
Cancel Hotel
Cancel Car
Book Book Book
Car Rental Hotel Rental
Cancel
Cancel
Cancelled
Flight Booking
@jeppec
Event Sourcing
@jeppec
Dual write problem
Choreography with Event Sourcing
Order
Service
Billing
Service
Client
Send-
Invoice
Save-Order
Transaction
boundary
Transaction
boundary
Event Store
Async
Push
or Pull
• Requires only local transactions
• Natively event driven: The Service
consumes and produces events
• Proven Audit Trail
• Flexible and adaptable
• Captures business intentions as
events
• Requires idempotent operations
@jeppec
Event Sourcing
Business-Objects/Aggregates track their own Domain Events
and derive their state from said Events
OrderCreated ProductAdded ProductAdded ProductRemoved ProductAdded OrderAccepted
Time
07:39
Time
07:40
Time
07:41
Time
07:45
Time
07:46
Time
07:50
@jeppec
Event Replaying
Type Aggregate
Identifier
GlobalOrder Event
Order
Timestamp Event
Identifier
EventType SerializedEvent
Order 14237 100 0 2014-01-06 7:39 {Guid-1} OrderCreated <serialized event>…
Order 14237 101 1 2014-01-06 7:40 {Guid-2} ProductAdded <serialized event>…
Order 14237 102 2 2014-01-06 7:41 {Guid-3} ProductAdded <serialized event>…
Order 14237 103 3 2014-01-06 7:45 {Guid-4} ProductRemoved <serialized event>…
Order 14237 104 4 2014-01-06 7:46 {Guid-5} ProductAdded <serialized event>…
Order 14237 105 5 2014-01-06 7:50 {Guid-6} OrderAccepted <serialized event>…
Order
@jeppec
Event Replaying
Type Aggregate
Identifier
GlobalOrder Event
Order
Timestamp Event
Identifier
EventType SerializedEvent
Order 14237 100 0 2014-01-06 7:39 {Guid-1} OrderCreated <serialized event>…
Order 14237 101 1 2014-01-06 7:40 {Guid-2} ProductAdded <serialized event>…
Order 14237 102 2 2014-01-06 7:41 {Guid-3} ProductAdded <serialized event>…
Order 14237 103 3 2014-01-06 7:45 {Guid-4} ProductRemoved <serialized event>…
Order 14237 104 4 2014-01-06 7:46 {Guid-5} ProductAdded <serialized event>…
Order 14237 105 5 2014-01-06 7:50 {Guid-6} OrderAccepted <serialized event>…
Order
Orderline
@jeppec
Event Replaying
Type Aggregate
Identifier
GlobalOrder Event
Order
Timestamp Event
Identifier
EventType SerializedEvent
Order 14237 100 0 2014-01-06 7:39 {Guid-1} OrderCreated <serialized event>…
Order 14237 101 1 2014-01-06 7:40 {Guid-2} ProductAdded <serialized event>…
Order 14237 102 2 2014-01-06 7:41 {Guid-3} ProductAdded <serialized event>…
Order 14237 103 3 2014-01-06 7:45 {Guid-4} ProductRemoved <serialized event>…
Order 14237 104 4 2014-01-06 7:46 {Guid-5} ProductAdded <serialized event>…
Order 14237 105 5 2014-01-06 7:50 {Guid-6} OrderAccepted <serialized event>…
Order
Orderline
Orderline
@jeppec
Event Replaying
Type Aggregate
Identifier
GlobalOrder Event
Order
Timestamp Event
Identifier
EventType SerializedEvent
Order 14237 100 0 2014-01-06 7:39 {Guid-1} OrderCreated <serialized event>…
Order 14237 101 1 2014-01-06 7:40 {Guid-2} ProductAdded <serialized event>…
Order 14237 102 2 2014-01-06 7:41 {Guid-3} ProductAdded <serialized event>…
Order 14237 103 3 2014-01-06 7:45 {Guid-4} ProductRemoved <serialized event>…
Order 14237 104 4 2014-01-06 7:46 {Guid-5} ProductAdded <serialized event>…
Order 14237 105 5 2014-01-06 7:50 {Guid-6} OrderAccepted <serialized event>…
Order
Orderline
@jeppec
Event Replaying
Type Aggregate
Identifier
GlobalOrder Event
Order
Timestamp Event
Identifier
EventType SerializedEvent
Order 14237 100 0 2014-01-06 7:39 {Guid-1} OrderCreated <serialized event>…
Order 14237 101 1 2014-01-06 7:40 {Guid-2} ProductAdded <serialized event>…
Order 14237 102 2 2014-01-06 7:41 {Guid-3} ProductAdded <serialized event>…
Order 14237 103 3 2014-01-06 7:45 {Guid-4} ProductRemoved <serialized event>…
Order 14237 104 4 2014-01-06 7:46 {Guid-5} ProductAdded <serialized event>…
Order 14237 105 5 2014-01-06 7:50 {Guid-6} OrderAccepted <serialized event>…
Order
Orderline
Orderline
@jeppec
Event Replaying
Type Aggregate
Identifier
GlobalOrder Event
Order
Timestamp Event
Identifier
EventType SerializedEvent
Order 14237 100 0 2014-01-06 7:39 {Guid-1} OrderCreated <serialized event>…
Order 14237 101 1 2014-01-06 7:40 {Guid-2} ProductAdded <serialized event>…
Order 14237 102 2 2014-01-06 7:41 {Guid-3} ProductAdded <serialized event>…
Order 14237 103 3 2014-01-06 7:45 {Guid-4} ProductRemoved <serialized event>…
Order 14237 104 4 2014-01-06 7:46 {Guid-5} ProductAdded <serialized event>…
Order 14237 105 5 2014-01-06 7:50 {Guid-6} OrderAccepted <serialized event>…
Order
Accepted: true
Orderline
Orderline
@jeppec
Client handled subscriptions
RSocketEvent
StreamSubscription
Local storage
EventStore
RSocketEvent
StreamSubscription
Local storage
EventStreamSubscription
Message
EventStreamSubscription
Message
EventStoreEventStreamPublisher
EventStoreEventStreamPublisher
Event
Event
Event Event
Supports
Single Instance
Subscriber,
which ensures
that only one
instance of
Subscriber B
has an active
subscription.
Other
instances of
the same
subscriber are
hot-standby
<<Topic Subscriber>>
Customer_Service:Some_Ac:OrderEvents
<<Topic Publisher>>
Sales_Service:OrderEvents
RSocketServer
tcp://subscribe-event-stream
A
B Subscriber B
RSocket Request/Stream
Event-Stream
Subscriber A
RSocket Request/Stream
Event-Stream
Flux<PersistedEvent> eventStream(long fromInclusiveGlobalOrder,
Option<String> subscriptionId)
@jeppec
ReactiveBus Pub/Sub
JdbcEventStores eventStores = ….
var customerAggregateType = AggregateType.from("Customer", CustomerId.class);
var customerEventStore = eventStores.getEventStoreFor(customerAggregateType);
var streamName = EventStreamName.from("CustomersStream");
reactiveBus.addEventStreamPublisher(new EventStoreEventStreamPublisher(streamName, customerEventStore, unitOfWorkFactory));
Publisher:
reactiveBus.subscribeToEventStream(
SubscriberId.from(”SalesService->CustomerEvents"),
EventStreamName.from("CustomersStream"),
EventStreamSubscriptionParameters.parameters(GlobalOrderSubscribeFromToken.fromStartOfStream()))
.doOnNext(payload -> {
System.out.println(”Received: " + e.payload.getClass() + "@" + e.globalOrder);
}).subscribe();
Subscriber:
@jeppec
Pub/Sub – Subscriber event handling
reactiveBus.subscribeToEventStream(SubscriberId.from(”SalesService->OrderEvents"),
EventStreamName.from(”OrdersStream"),
EventStreamSubscriptionParameters.parameters(GlobalOrderSubscribeFromToken.fromStartOfStream()))
.doOnNext(payload -> {
if (event instanceof OrderAdded e) {
.. = e.orderNumber;
} else if (event instanceof ProductAddedToOrder e) {
… = e.productId;
} else if (event instanceof OrderAccepted) {
…accepted = true;
}
).subscribe();
@jeppec
Pub/Sub – Subscriber event handling
@EventHandler
private void handle(OrderAdded e, EventMessage message) {
….
}
@EventHandler
private void handle(ProductAddedToOrder e) {
.…
}
@EventHandler
private void handle(OrderAccepted e) {
….
}
@jeppec
For more
see
Blog: https://cramonblog.wordpress.com/
Homepage: http://cloudcreate.dk/
Twitter: @jeppec

More Related Content

PPTX
PPTX
Stability Patterns for Microservices
PPTX
Microservices Part 3 Service Mesh and Kafka
PDF
Apache Kafka Architecture & Fundamentals Explained
PDF
CQRS and event sourcing
PDF
Implementing Domain Events with Kafka
PPTX
Updated: Should you be using an Event Driven Architecture
PDF
Producer Performance Tuning for Apache Kafka
Stability Patterns for Microservices
Microservices Part 3 Service Mesh and Kafka
Apache Kafka Architecture & Fundamentals Explained
CQRS and event sourcing
Implementing Domain Events with Kafka
Updated: Should you be using an Event Driven Architecture
Producer Performance Tuning for Apache Kafka

What's hot (20)

PPTX
Introduction to Apache Kafka
PPTX
From cache to in-memory data grid. Introduction to Hazelcast.
PDF
Apache kafka 모니터링을 위한 Metrics 이해 및 최적화 방안
PPTX
Squirreling Away $640 Billion: How Stripe Leverages Flink for Change Data Cap...
PDF
LINE's messaging service architecture underlying more than 200 million monthl...
PPSX
Event Sourcing & CQRS, Kafka, Rabbit MQ
PPTX
Kafka 101
PDF
Cassandra serving netflix @ scale
PDF
Introducing Saga Pattern in Microservices with Spring Statemachine
PDF
PPTX
Kafka presentation
PDF
Scalability, Availability & Stability Patterns
PDF
A Thorough Comparison of Delta Lake, Iceberg and Hudi
PDF
Apache Kafka Fundamentals for Architects, Admins and Developers
PPTX
Kafka replication apachecon_2013
PDF
Dual write strategies for microservices
PDF
Introduction and Overview of Apache Kafka, TriHUG July 23, 2013
PPTX
Introduction to Storm
PPTX
Apache Kafka at LinkedIn
PDF
Real-Life Use Cases & Architectures for Event Streaming with Apache Kafka
Introduction to Apache Kafka
From cache to in-memory data grid. Introduction to Hazelcast.
Apache kafka 모니터링을 위한 Metrics 이해 및 최적화 방안
Squirreling Away $640 Billion: How Stripe Leverages Flink for Change Data Cap...
LINE's messaging service architecture underlying more than 200 million monthl...
Event Sourcing & CQRS, Kafka, Rabbit MQ
Kafka 101
Cassandra serving netflix @ scale
Introducing Saga Pattern in Microservices with Spring Statemachine
Kafka presentation
Scalability, Availability & Stability Patterns
A Thorough Comparison of Delta Lake, Iceberg and Hudi
Apache Kafka Fundamentals for Architects, Admins and Developers
Kafka replication apachecon_2013
Dual write strategies for microservices
Introduction and Overview of Apache Kafka, TriHUG July 23, 2013
Introduction to Storm
Apache Kafka at LinkedIn
Real-Life Use Cases & Architectures for Event Streaming with Apache Kafka
Ad

Similar to The Dual write problem (20)

PPTX
Event Driven Architecture (Integration Tech Event 2019)
PPTX
Should you be using an event driven architecture - IDA IT (short version)
PDF
SOA, Microservices and Event Driven Architecture
PPTX
What 5 years of implementation microservices has taught me
PDF
Long running processes in DDD
PDF
Inventing the future Business Programming Language
PDF
The Art of The Event Streaming Application: Streams, Stream Processors and Sc...
PPTX
Kakfa summit london 2019 - the art of the event-streaming app
PPT
Sapbasic
PPT
Sap overview posted by Parikshit Sanghavi
PPTX
Should you be using an event driven architecture?
PPTX
Event Driven Architectures
PDF
CQRS + Event Sourcing
PPTX
Don't call us - we'll push - on cross tier push architecture (NLJUG JFall 201...
PDF
Complex Event Processing: What?, Why?, How?
PDF
Data Microservices with Spring Cloud
PPTX
Event Driven Architectures - Phoenix Java Users Group 2013
PPTX
SBJUG - Building Beautiful Batch Jobs
PPTX
Microservices in the Apache Kafka Ecosystem
PDF
Event Sourcing, Stream Processing and Serverless (Benjamin Stopford, Confluen...
Event Driven Architecture (Integration Tech Event 2019)
Should you be using an event driven architecture - IDA IT (short version)
SOA, Microservices and Event Driven Architecture
What 5 years of implementation microservices has taught me
Long running processes in DDD
Inventing the future Business Programming Language
The Art of The Event Streaming Application: Streams, Stream Processors and Sc...
Kakfa summit london 2019 - the art of the event-streaming app
Sapbasic
Sap overview posted by Parikshit Sanghavi
Should you be using an event driven architecture?
Event Driven Architectures
CQRS + Event Sourcing
Don't call us - we'll push - on cross tier push architecture (NLJUG JFall 201...
Complex Event Processing: What?, Why?, How?
Data Microservices with Spring Cloud
Event Driven Architectures - Phoenix Java Users Group 2013
SBJUG - Building Beautiful Batch Jobs
Microservices in the Apache Kafka Ecosystem
Event Sourcing, Stream Processing and Serverless (Benjamin Stopford, Confluen...
Ad

Recently uploaded (20)

PPTX
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
PDF
Review of recent advances in non-invasive hemoglobin estimation
PDF
KodekX | Application Modernization Development
PDF
Agricultural_Statistics_at_a_Glance_2022_0.pdf
PPTX
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
PDF
Network Security Unit 5.pdf for BCA BBA.
PPTX
PA Analog/Digital System: The Backbone of Modern Surveillance and Communication
PPTX
20250228 LYD VKU AI Blended-Learning.pptx
PDF
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
PDF
Bridging biosciences and deep learning for revolutionary discoveries: a compr...
PDF
Approach and Philosophy of On baking technology
PDF
Reach Out and Touch Someone: Haptics and Empathic Computing
PDF
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
PDF
Modernizing your data center with Dell and AMD
PPTX
A Presentation on Artificial Intelligence
PDF
Building Integrated photovoltaic BIPV_UPV.pdf
PPTX
Big Data Technologies - Introduction.pptx
PPTX
Digital-Transformation-Roadmap-for-Companies.pptx
DOCX
The AUB Centre for AI in Media Proposal.docx
PDF
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
Review of recent advances in non-invasive hemoglobin estimation
KodekX | Application Modernization Development
Agricultural_Statistics_at_a_Glance_2022_0.pdf
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
Network Security Unit 5.pdf for BCA BBA.
PA Analog/Digital System: The Backbone of Modern Surveillance and Communication
20250228 LYD VKU AI Blended-Learning.pptx
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
Bridging biosciences and deep learning for revolutionary discoveries: a compr...
Approach and Philosophy of On baking technology
Reach Out and Touch Someone: Haptics and Empathic Computing
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
Modernizing your data center with Dell and AMD
A Presentation on Artificial Intelligence
Building Integrated photovoltaic BIPV_UPV.pdf
Big Data Technologies - Introduction.pptx
Digital-Transformation-Roadmap-for-Companies.pptx
The AUB Centre for AI in Media Proposal.docx
Build a system with the filesystem maintained by OSTree @ COSCUP 2025

The Dual write problem

  • 1. The dual write problem Jeppe Cramon - @jeppec Cloud Create ApS
  • 2. @jeppec Essential complexity of 2 way integration Component Warehouse Component Order Component Billing UI Send- Invoice Save-Order Order:Save-Order()è call Warehouse:Reserve-Items() call Billing:Send-Invoice() commit() Reserve- Items Local transaction between the 3 Components
  • 4. @jeppec Accidental complexity from distributed service integration Warehouse Service Order Service Billing Service UI Send- Invoice Save-Order Reserve- Items Local transaction between 2 local “Services” DUAL WRITE using Remote call
  • 5. @jeppec Microservices turns a local functional call into a distributed problem
  • 6. @jeppec With cross SERVICE integration we’re bound by the laws of distributed computing
  • 7. @jeppec The 11 Fallacies of Distributed Computing These fallacies are assumptions architects, designers and developers of distributed systems are likely to make. The fallacies will be proven wrong in the long run - resulting in all sorts of troubles and pains for the solution and architects who made the assumptions. 1. The network is reliable. 2. Latency is zero. 3. Bandwidth is infinite. 4. The network is secure. 5. Topology doesn't change. 6. There is one administrator. 7. Transport cost is zero. 8. The network is homogeneous. 9. The System is atomic/monolithic 10. The System is Finished 11. Business logic can and should be centralized See https://arnon.me/wp-content/uploads/Files/fallacies.pdf
  • 8. @jeppec Order:Save-Order()è call Warehouse:Reserve-Items() call Billing:Send-Invoice() if (Billing:Call-Failed:Too-Busy?) Wait-A-While() call Billing:Send-Invoice() if (Billing:Call-Failed:Too-Busy?) Wait-A-Little-While-Longer() call Billing:Send-Invoice() if (Billing:Call-Failed:IO-Error?) Save-We-Need-Check-If-Call-Billing-Succeded-After-All AND We-Need-To-Retry call Order:Save-Order and call Warehouse:Reserve-Items AND Tell-Customer-That-This-Operation-Perhaps-Went-Well if (Billing:Call-Went-Well?) commit() Accidental complexity from distributed service integration Warehouse Service Order Service Billing Service UI Send- Invoice Save-Order Reserve- Items Local transaction between 2 Components
  • 10. @jeppec What’s the challenge using state mutating Request/Response calls between distributed components?
  • 11. @jeppec Synchronous Request/Response lowers our tolerance for faults • When you get an IO error • When servers crash or restarts • When databases are down • When deadlocks occurs in our databases • Do you retry? With synchronous Request/Response we can loose business data if there’s no automatic retry mechanism. Also if the operation we retry isn’t idempotent* we risk having the side effect multiple times! Client Server Processing The same message can be processed more than once *Idempotence describes the quality of an operation in which result and state does not change if the operation is performed more than 1 time Request Processing Duplicated Request Duplicated Response Response
  • 14. @jeppec Sales system Sales Update customer status (e.g. Gold customer) Bookkeeping Deliver goods Delivery system Deliveries Customer/ CRM system Customers SAP Bookkeeping Complete Purchase Transaction Coordinator Transactional Resource Request-to-Prepare Commit Prepared Done Prepare Phase Commit Phase 2 Phase Commit
  • 15. @jeppec What’s wrong with distributed transactions? • Transactions lock resources while active • Services are autonomous • Can’t be expected to finish within a certain time interval • Locking keeps other transactions from completing their job • Locking doesn’t scale • X Phase Commit is fragile by design
  • 16. How to handle the dual write problem?
  • 17. @jeppec Dual write problem Order Service Billing Service Client Send- Invoice Save-Order Mutating Remote call Expected transaction boundary No simple solution (except a classic monolith) • Local-commit-then-publish • If the app crashes after local- commit then the publish operation isn’t performed • Publish-then-local-commit • If the local-commit fails then we’ve already formed the publish operation • Billing Service may receive published message BEFORE the Order Service has committed its transaction
  • 19. @jeppec Dual write problem Orchestration / Process Manager / Saga* Order Service Billing Service Client Send- Invoice Transaction boundary Transaction boundary Save-Order • Requires only local transactions • Single coordinator • Increases eventual consistency • Requires idempotent operations due to retries • Requires compensations / rollback • Tighter coupling as the orchestrator instructs other services about what to do * This isn’t the real Saga pattern
  • 21. @jeppec Dual write problem Choreography public class OrderService { public void handle(AcceptOrder cmd) { orderRepository.add(new Order(cmd.orderId, cmd.orderLines)); eventTopic.publish(new OrderAccepted(cmd.orderId, cmd.orderLines)); } } public class BillingService { public void handle(OrderAccepted cmd) { invoiceRepository.add(new Invoice(cmd.orderId, cmd.orderLines)); emailGateway.send(new OrderAcceptedEmail(cmd.orderId, cmd.orderLines)); } }
  • 22. @jeppec Dual write problem Choreography Order Service Billing Service Client Send- Invoice Save-Order Transaction boundary Transaction boundary • No Single coordinator, but global system state and coordination logic is scattered across all participants • Requires only local transactions • Increases eventual consistency • Requires idempotent operations due to retries • Looser coupling (if events are used) • Suffers from a Dual Write problem it self Events/Commands Queue
  • 24. @jeppec Dual write problem Choreography with Outbox pattern Order Service Billing Service Client Send- Invoice Save-Order Transaction boundary Transaction boundary • You can either use a Change Data Capture (CDC) approach e.g. using a database Write Ahead Log (WAL) • Risk of CRUD events! • Or a manual OutBox pattern where Save-Order writes data and the event/command into the OutBox within the same database. Typically requires polling and coordination + recovery options in a multi cluster setup Events/Commands Change Data Capture / Polling Async Push or Poll
  • 25. @jeppec Saga pattern A failure management pattern
  • 26. @jeppec Trip reservation as a Saga Start Rent Car Book Hotel Reserve Flight Cancel Hotel Cancel Car Book Car Rental Hotel Rental WorkQueue: - Queue: “Rent Car” - Queue: “Book Hotel” - Queue: “Reserve Flight” Routing slip Flight Booking
  • 27. @jeppec Trip reservation as a Saga Start Rent Car Book Hotel Reserve Flight Cancel Hotel Cancel Car Book Book Car Rental Hotel Rental WorkQueue: - Queue: “Book Hotel” - Queue : “Reserve Flight” WorkStack: - ReservationId: ABC123 Cancellation: “Cancel Car” Routing slip Flight Booking
  • 28. @jeppec Trip reservation as a Saga Start Rent Car Book Hotel Reserve Flight Cancel Hotel Cancel Car Book Book Car Rental Hotel Rental WorkQueue: - Queue : “Reserve Flight” WorkStack: - Hotel ReservationId: XYZ Cancellation: “Cancel Hotel” - Car ReservationId: ABC123 Cancellation: “Cancel Car” Routing slip Book Flight Booking
  • 29. @jeppec Trip reservation as a Saga Start Rent Car Book Hotel Reserve Flight Cancel Hotel Cancel Car Book Book Book Car Rental Hotel Rental WorkStack: - Hotel ReservationId: XYZ Cancellation: “Cancel Hotel” - Car ReservationId: ABC123 Cancellation: “Cancel Car” Routing slip Flight Booking
  • 30. @jeppec Trip reservation as a Saga Start Rent Car Book Hotel Reserve Flight Cancel Hotel Cancel Car Book Book Book Car Rental Hotel Rental Cancel WorkStack: - Hotel ReservationId: XYZ Cancellation: “Cancel Hotel” - Car ReservationId: ABC123 Cancellation: “Cancel Car” Routing slip Flight Booking
  • 31. @jeppec Trip reservation as a Saga Start Rent Car Book Hotel Reserve Flight Cancel Hotel Cancel Car Book Book Book Car Rental Hotel Rental Cancel Cancel WorkStack: - Car ReservationId: ABC123 Cancellation: “Cancel Car” Routing slip Flight Booking
  • 32. @jeppec Trip reservation as a Saga Start Rent Car Book Hotel Reserve Flight Cancel Hotel Cancel Car Book Book Book Car Rental Hotel Rental Cancel Cancel Cancelled Flight Booking
  • 34. @jeppec Dual write problem Choreography with Event Sourcing Order Service Billing Service Client Send- Invoice Save-Order Transaction boundary Transaction boundary Event Store Async Push or Pull • Requires only local transactions • Natively event driven: The Service consumes and produces events • Proven Audit Trail • Flexible and adaptable • Captures business intentions as events • Requires idempotent operations
  • 35. @jeppec Event Sourcing Business-Objects/Aggregates track their own Domain Events and derive their state from said Events OrderCreated ProductAdded ProductAdded ProductRemoved ProductAdded OrderAccepted Time 07:39 Time 07:40 Time 07:41 Time 07:45 Time 07:46 Time 07:50
  • 36. @jeppec Event Replaying Type Aggregate Identifier GlobalOrder Event Order Timestamp Event Identifier EventType SerializedEvent Order 14237 100 0 2014-01-06 7:39 {Guid-1} OrderCreated <serialized event>… Order 14237 101 1 2014-01-06 7:40 {Guid-2} ProductAdded <serialized event>… Order 14237 102 2 2014-01-06 7:41 {Guid-3} ProductAdded <serialized event>… Order 14237 103 3 2014-01-06 7:45 {Guid-4} ProductRemoved <serialized event>… Order 14237 104 4 2014-01-06 7:46 {Guid-5} ProductAdded <serialized event>… Order 14237 105 5 2014-01-06 7:50 {Guid-6} OrderAccepted <serialized event>… Order
  • 37. @jeppec Event Replaying Type Aggregate Identifier GlobalOrder Event Order Timestamp Event Identifier EventType SerializedEvent Order 14237 100 0 2014-01-06 7:39 {Guid-1} OrderCreated <serialized event>… Order 14237 101 1 2014-01-06 7:40 {Guid-2} ProductAdded <serialized event>… Order 14237 102 2 2014-01-06 7:41 {Guid-3} ProductAdded <serialized event>… Order 14237 103 3 2014-01-06 7:45 {Guid-4} ProductRemoved <serialized event>… Order 14237 104 4 2014-01-06 7:46 {Guid-5} ProductAdded <serialized event>… Order 14237 105 5 2014-01-06 7:50 {Guid-6} OrderAccepted <serialized event>… Order Orderline
  • 38. @jeppec Event Replaying Type Aggregate Identifier GlobalOrder Event Order Timestamp Event Identifier EventType SerializedEvent Order 14237 100 0 2014-01-06 7:39 {Guid-1} OrderCreated <serialized event>… Order 14237 101 1 2014-01-06 7:40 {Guid-2} ProductAdded <serialized event>… Order 14237 102 2 2014-01-06 7:41 {Guid-3} ProductAdded <serialized event>… Order 14237 103 3 2014-01-06 7:45 {Guid-4} ProductRemoved <serialized event>… Order 14237 104 4 2014-01-06 7:46 {Guid-5} ProductAdded <serialized event>… Order 14237 105 5 2014-01-06 7:50 {Guid-6} OrderAccepted <serialized event>… Order Orderline Orderline
  • 39. @jeppec Event Replaying Type Aggregate Identifier GlobalOrder Event Order Timestamp Event Identifier EventType SerializedEvent Order 14237 100 0 2014-01-06 7:39 {Guid-1} OrderCreated <serialized event>… Order 14237 101 1 2014-01-06 7:40 {Guid-2} ProductAdded <serialized event>… Order 14237 102 2 2014-01-06 7:41 {Guid-3} ProductAdded <serialized event>… Order 14237 103 3 2014-01-06 7:45 {Guid-4} ProductRemoved <serialized event>… Order 14237 104 4 2014-01-06 7:46 {Guid-5} ProductAdded <serialized event>… Order 14237 105 5 2014-01-06 7:50 {Guid-6} OrderAccepted <serialized event>… Order Orderline
  • 40. @jeppec Event Replaying Type Aggregate Identifier GlobalOrder Event Order Timestamp Event Identifier EventType SerializedEvent Order 14237 100 0 2014-01-06 7:39 {Guid-1} OrderCreated <serialized event>… Order 14237 101 1 2014-01-06 7:40 {Guid-2} ProductAdded <serialized event>… Order 14237 102 2 2014-01-06 7:41 {Guid-3} ProductAdded <serialized event>… Order 14237 103 3 2014-01-06 7:45 {Guid-4} ProductRemoved <serialized event>… Order 14237 104 4 2014-01-06 7:46 {Guid-5} ProductAdded <serialized event>… Order 14237 105 5 2014-01-06 7:50 {Guid-6} OrderAccepted <serialized event>… Order Orderline Orderline
  • 41. @jeppec Event Replaying Type Aggregate Identifier GlobalOrder Event Order Timestamp Event Identifier EventType SerializedEvent Order 14237 100 0 2014-01-06 7:39 {Guid-1} OrderCreated <serialized event>… Order 14237 101 1 2014-01-06 7:40 {Guid-2} ProductAdded <serialized event>… Order 14237 102 2 2014-01-06 7:41 {Guid-3} ProductAdded <serialized event>… Order 14237 103 3 2014-01-06 7:45 {Guid-4} ProductRemoved <serialized event>… Order 14237 104 4 2014-01-06 7:46 {Guid-5} ProductAdded <serialized event>… Order 14237 105 5 2014-01-06 7:50 {Guid-6} OrderAccepted <serialized event>… Order Accepted: true Orderline Orderline
  • 42. @jeppec Client handled subscriptions RSocketEvent StreamSubscription Local storage EventStore RSocketEvent StreamSubscription Local storage EventStreamSubscription Message EventStreamSubscription Message EventStoreEventStreamPublisher EventStoreEventStreamPublisher Event Event Event Event Supports Single Instance Subscriber, which ensures that only one instance of Subscriber B has an active subscription. Other instances of the same subscriber are hot-standby <<Topic Subscriber>> Customer_Service:Some_Ac:OrderEvents <<Topic Publisher>> Sales_Service:OrderEvents RSocketServer tcp://subscribe-event-stream A B Subscriber B RSocket Request/Stream Event-Stream Subscriber A RSocket Request/Stream Event-Stream Flux<PersistedEvent> eventStream(long fromInclusiveGlobalOrder, Option<String> subscriptionId)
  • 43. @jeppec ReactiveBus Pub/Sub JdbcEventStores eventStores = …. var customerAggregateType = AggregateType.from("Customer", CustomerId.class); var customerEventStore = eventStores.getEventStoreFor(customerAggregateType); var streamName = EventStreamName.from("CustomersStream"); reactiveBus.addEventStreamPublisher(new EventStoreEventStreamPublisher(streamName, customerEventStore, unitOfWorkFactory)); Publisher: reactiveBus.subscribeToEventStream( SubscriberId.from(”SalesService->CustomerEvents"), EventStreamName.from("CustomersStream"), EventStreamSubscriptionParameters.parameters(GlobalOrderSubscribeFromToken.fromStartOfStream())) .doOnNext(payload -> { System.out.println(”Received: " + e.payload.getClass() + "@" + e.globalOrder); }).subscribe(); Subscriber:
  • 44. @jeppec Pub/Sub – Subscriber event handling reactiveBus.subscribeToEventStream(SubscriberId.from(”SalesService->OrderEvents"), EventStreamName.from(”OrdersStream"), EventStreamSubscriptionParameters.parameters(GlobalOrderSubscribeFromToken.fromStartOfStream())) .doOnNext(payload -> { if (event instanceof OrderAdded e) { .. = e.orderNumber; } else if (event instanceof ProductAddedToOrder e) { … = e.productId; } else if (event instanceof OrderAccepted) { …accepted = true; } ).subscribe();
  • 45. @jeppec Pub/Sub – Subscriber event handling @EventHandler private void handle(OrderAdded e, EventMessage message) { …. } @EventHandler private void handle(ProductAddedToOrder e) { .… } @EventHandler private void handle(OrderAccepted e) { …. }
  • 46. @jeppec For more see Blog: https://cramonblog.wordpress.com/ Homepage: http://cloudcreate.dk/ Twitter: @jeppec