Consistency patterns
By Oleksandr Andrushchenko — Published on — Modified on
Consistency patterns describe how systems behave when multiple users, services, or processes read and write shared data at the same time.
Table of Contents
- What Is Consistency?
- Strong Consistency
- Eventual Consistency
- Weak Consistency
- Consistency in Microservices
- Distributed Transactions
- Choosing the Right Consistency Level
- Conclusion
What Is Consistency?
Consistency describes what users and services see after data changes. If one process updates a value, consistency defines when other processes are allowed to see that new value.
For example, if a user changes their email address, should the next screen immediately show the new email? Should another service see it right away? Can a cache still return the old value for a few seconds?
Different systems answer these questions differently. A banking system usually requires strict correctness. A metrics dashboard may tolerate a short delay.
Why Consistency Matters
Consistency matters because many bugs are not caused by missing data, but by data appearing at the wrong time or in the wrong order.
For example, imagine an e-commerce checkout flow. If inventory is not updated consistently, two customers may buy the last available item. If payment state is not consistent, an order may appear paid in one service and unpaid in another.
In other systems, weaker consistency is acceptable. A notification badge showing 4 unread messages instead of 5 for a few seconds is usually not a serious problem.
Consistency Is a Spectrum
Modern systems rarely use one consistency model everywhere. They usually combine multiple models depending on business importance.
A checkout service may use strong consistency for payment and inventory, eventual consistency for sending confirmation emails, and weak consistency for analytics counters.
Payment status -> strong consistency
Inventory count -> strong consistency
Email notification -> eventual consistency
Analytics dashboard -> weak consistency
The goal is not to make everything strongly consistent. The goal is to apply the right level of consistency to the right part of the system.
Strong Consistency
Strong consistency means that once a write is confirmed, later reads return the latest value.
This model is easiest to reason about because the system behaves as if there is one correct current state.
Transactions
Database transactions are one of the most common tools for strong consistency. A transaction groups multiple changes and commits them together.
For example, when a user places an order, the system may need to:
- Create the order.
- Reserve inventory.
- Create a payment record.
- Update account balance or order status.
These changes should not partially succeed. Either the important changes happen together, or the system rolls them back.
BEGIN;
INSERT INTO orders (id, user_id, status)
VALUES (1001, 123, 'CREATED');
UPDATE inventory
SET available_quantity = available_quantity - 1
WHERE product_id = 555
AND available_quantity > 0;
INSERT INTO payments (order_id, status)
VALUES (1001, 'PENDING');
COMMIT;
This is where relational databases like PostgreSQL are very strong.
Read-After-Write Consistency
Read-after-write consistency means that after a user writes data, that same user can immediately read the updated value.
For example, if a user changes their profile name from “Alex” to “Oleksandr,” the next page refresh should show “Oleksandr,” not the old value.
This is often more important than global strong consistency. The user who made the change expects to see it immediately, even if other users or replicas may see it slightly later.
When to Use Strong Consistency
Strong consistency should protect important business invariants.
- Account balances should not be wrong.
- Inventory should not be oversold.
- User permissions should not be stale during sensitive actions.
- Payment status should not be ambiguous.
- Unique usernames or emails should not be duplicated.
Strong consistency usually requires coordination, locking, quorum reads, synchronous replication, or single-writer designs. These mechanisms improve correctness but can increase latency and reduce availability.
Eventual Consistency
Eventual consistency means that replicas or downstream systems may temporarily disagree, but if no new updates happen, they eventually converge to the same state.
This model is common in distributed databases, caches, search indexes, event-driven systems, and multi-region architectures.
How Eventual Consistency Works
In an eventually consistent system, a write may be accepted by one component first and then propagated to others asynchronously.
User updates profile
│
▼
Primary database updated
│
├── Cache invalidation event
├── Search index update event
└── Analytics event
For a short time, the primary database may contain the new value while the cache, search index, or analytics system still contains the old value.
Eventual Consistency Example
Imagine a user updates their profile picture.
The main database stores the new image URL immediately, but the CDN cache may still serve the old image for a few minutes. The search index may also show the old profile metadata until the indexing job catches up.
This is usually acceptable. The important guarantee is that the system eventually converges to the new profile picture everywhere.
Conflict Handling
Eventual consistency becomes harder when multiple writes happen at the same time in different places.
For example, if a shopping cart is updated from a phone and a laptop at the same time, the system must decide how to merge the changes.
| Conflict Strategy | How It Works | Example |
|---|---|---|
| Last write wins | The newest update replaces older updates | User profile display name |
| Merge | Combine changes from both versions | Shopping cart items |
| Manual resolution | Human or business workflow resolves conflict | Conflicting document edits |
Choosing the wrong conflict strategy can create data loss. For example, last-write-wins may be acceptable for a profile bio, but dangerous for a shopping cart.
Weak Consistency
Weak consistency provides fewer guarantees. Reads may return stale, missing, or out-of-order data, and the system may not promise exactly when all components will agree.
This sounds dangerous, but it is useful for workloads where exact correctness is not required.
Where Weak Consistency Works
Weak consistency works well for analytics, metrics, logs, recommendations, counters, and observability pipelines.
For example, a dashboard showing request count for the last minute does not usually need exact real-time accuracy. If the number is delayed by a few seconds, the system is still useful.
Weak Consistency Example
A recommendation engine may process user behavior asynchronously.
User clicks product
│
▼
Event sent to analytics pipeline
│
▼
Recommendation model updates later
The recommendation result may lag behind the latest user action. That is acceptable because recommendations are approximate by nature.
The system favors throughput and availability over immediate correctness.
Consistency in Microservices
Microservices make consistency more complicated because data is split across services. Each service may own its own database, and updating multiple services in one atomic transaction becomes difficult.
Local Transactions
A common pattern is to keep strong consistency inside one service boundary and use asynchronous events between services.
For example, the order service can create an order in its own database transaction. After the transaction commits, other services can react to an OrderCreated event.
Order Service
│
├── Local transaction
│ └── orders table
│
└── Emits OrderCreated event
Billing Service
└── reacts asynchronously
Email Service
└── reacts asynchronously
This avoids distributed transactions while keeping each service internally consistent.
Outbox Pattern
The outbox pattern helps ensure that a database change and an event are recorded together.
def create_order(conn, order_id, payload):
with conn:
conn.execute(
"INSERT INTO orders (id, status) VALUES (?, ?)",
(
order_id,
"CREATED",
),
)
conn.execute(
"INSERT INTO outbox (event_type, payload) VALUES (?, ?)",
(
"OrderCreated",
payload,
),
)
# A separate worker publishes events from the outbox table.
The important idea is that the order and the event record are saved in the same local transaction. If the transaction commits, both exist. If it rolls back, neither exists.
A background worker can later publish the event to Kafka, SNS, SQS, RabbitMQ, or another messaging system.
Saga Pattern
The saga pattern coordinates a business workflow across multiple services using a sequence of local transactions and compensating actions.
For example, an order workflow may look like this:
1. Create order
2. Reserve inventory
3. Authorize payment
4. Send confirmation email
If payment authorization fails, the system may need to release the reserved inventory and mark the order as failed.
Payment failed
│
├── Release inventory
└── Mark order as failed
This is not the same as rolling back one database transaction. It is a business-level compensation.
Distributed Transactions
Distributed transactions try to provide strong consistency across multiple services, databases, or network boundaries.
They are possible, but they are often expensive and operationally risky.
Why Distributed Transactions Are Hard
Distributed transactions require coordination between multiple systems. If one participant is slow, unavailable, or uncertain, the whole transaction may block.
For example, imagine an order service, payment service, and inventory service participating in the same distributed transaction. If the coordinator fails at the wrong moment, some resources may remain locked while the system tries to recover.
That is why many modern architectures avoid distributed transactions across services.
Compensating Actions
Instead of trying to make everything atomic across the network, many systems use compensating actions.
For example:
- If payment fails, release inventory.
- If shipment fails, refund payment.
- If account creation fails after billing setup, cancel the billing subscription.
This approach accepts that workflows may partially progress, then uses business logic to correct the state.
Choosing the Right Consistency Level
No single consistency model fits every workload. The correct choice depends on business impact, failure tolerance, latency requirements, and scale.
| Data or Operation | Recommended Consistency | Reason |
|---|---|---|
| Account balance | Strong | Incorrect values can cause financial loss |
| Inventory reservation | Strong | Prevents overselling |
| User profile update | Read-after-write / eventual | User should see own changes quickly; global propagation can lag |
| Email notification | Eventual | Can be sent asynchronously after the main transaction |
| Search index | Eventual | Indexing can lag behind source database |
| Metrics dashboard | Weak | Approximate or delayed data is acceptable |
| Recommendation engine | Weak / eventual | Freshness improves quality but is rarely critical |
The practical rule is simple:
Use strong consistency for business invariants, eventual consistency for distributed workflows, and weak consistency for analytics or approximate data.
Conclusion
Consistency is not one setting that applies to the whole system. It is a design choice that should be made per data path and per business requirement.
Strong consistency is useful when correctness matters immediately, such as payments, balances, permissions, and inventory. Eventual consistency is useful when systems need to scale, communicate asynchronously, or tolerate temporary differences. Weak consistency is useful when speed and availability matter more than exact correctness.
Most large-scale systems are not strongly consistent everywhere. They are strongly consistent where correctness matters and eventually consistent where scale, availability, or loose coupling matters.
The best system designs treat consistency as a spectrum, not a binary choice.
Comments (0)