Transactions
A transaction groups several writes so that all of them happen or none of them do. One commit waits for one write to disk, about 0.4 ms here. With an outbox, an order and its event commit together, so no event goes missing.
See it on the board
The same traffic runs on two boards. The simulator measured both when this page was built.
| Measure | Goal | Order and event written apart | Transactional outbox on the orders database |
|---|---|---|---|
| Data mismatches (Reads) | under 1,000 | 39,155 Fail | 599 Pass |
| Data age | under 8 s | 79.9 s Fail | 5.2 s Pass |
| Monthly cost | 24 USD/mo | 24 USD/mo | |
| p99 latency | 23 ms | 23 ms | |
| Error rate | 6.3% | 6.3% |
Key numbers
| Number | Value |
|---|---|
| Write one commit to disk | 0.4 ms |
| Database operation (Postgres), p99 | 2 ms |
| Writes per second for each vCPU (Postgres) | 2,500 writes/s |
| Time between saved publisher positions, default | 30 s |
Outbox · 10 orders · 10 events · 0 lost
How it works
Many actions change more than one thing. An order saves a row and must also tell other services. If the service writes the row and then sends a message, a crash between the two steps leaves a row without its message. If it sends first, a failed write leaves a message about an order that does not exist.
A transaction closes this gap. All its writes commit together, or none of them do. Other readers never see half of the change. The database records the commit on disk before it confirms, so a confirmed commit survives a crash.
The network is outside the transaction: a database cannot commit a message in another system. The outbox pattern solves this. The service writes the order and an event row to an outbox table in the same transaction. A separate publisher reads the outbox and sends each event. It saves its position, so after a stop it continues from that point. It can send an event twice, so consumers must ignore repeats.
Isolation is the other half. Strong isolation stops two transactions from seeing each other's partial work, but more transactions must then wait or retry.
In the levels you add an outbox to the orders database so that each commit leads to its event, also after a failure.