CQRS: Separating Reads and Writes for Maximum Performance
SD
ScaleDojo
May 11, 2026
2 min read542 words
Amazon's Product Page Problem
Amazon's product page shows: title, price, reviews, recommendations, inventory status, shipping estimates, and seller info. These come from 7+ different services. The read path needs heavily denormalized, pre-joined data for speed. But the write path (placing an order) needs normalized, ACID-transacted data for correctness. Using the same model for both is a fundamental mismatch. CQRS says: stop trying. Separate them completely.
CQRS Architecture
Traditional (Same Model for Read and Write):
[Client] --read/write--> [API] ---> [Same DB]
Problem: DB optimized for writes (normalized) is slow for reads.
Problem: DB optimized for reads (denormalized) loses write integrity.
CQRS (Separate Read and Write Paths):
WRITE SIDE (Commands): READ SIDE (Queries):
[Client] [Client]
| |
[Command API] [Query API]
| |
[Command Handler] [Query Handler]
| |
[Domain Model] [Read Model]
(validates business rules) (denormalized projections)
| |
[Write DB - PostgreSQL] [Read DB - Redis/Elastic]
(normalized, ACID) (optimized per query)
| ^
+---[Events via Kafka]-----------+
(sync read models asynchronously)
Commands vs Queries
Commands (Write Side):
PlaceOrder(user_id, items, payment_info)
CancelSubscription(subscription_id, reason)
TransferMoney(from_account, to_account, amount)
Commands express INTENT. They are validated.
They can be rejected (insufficient funds).
They change state. They produce events.
Queries (Read Side):
GetOrderSummary(order_id) -> {status, items, total}
ListUserPurchases(user_id, page) -> [{order}...]
GetDashboardStats(date_range) -> {revenue, orders, users}
Queries are pure reads. They NEVER change state.
They read denormalized, pre-computed projections.
They can be cached aggressively.
They can hit a DIFFERENT database entirely.
Synchronizing the Two Sides
Event-Driven Sync Flow:
1. Command: PlaceOrder(user=42, items=[...], total=$150)
2. Write DB: INSERT INTO orders (...) -- normalized
3. Publish: OrderPlaced event to Kafka
4. Event handlers update READ models:
Handler A: Update order-summary table (for GetOrderSummary)
Handler B: Update user-orders list (for ListUserPurchases)
Handler C: Update revenue dashboard (for GetDashboardStats)
Handler D: Update search index (for SearchOrders)
Each read model is a DIFFERENT shape of the same data,
optimized for its specific query pattern.
Trade-off: read models are EVENTUALLY consistent.
Write at T=0, read model updated at T=50ms to T=500ms.
For most UIs, this is imperceptible.
When CQRS Is Worth It
Use CQRS when: Skip CQRS when:
------------------------------------ -----------------------
Read/write ratio > 10:1 Simple CRUD app
Reads need different DB (search, cache) Same model works
Multiple query shapes from same data Single list/detail view
Write model has complex business rules Write = read model
You need independent read/write scale Both scale together
You are already using event sourcing State is mutable
Complexity cost: 2 models, event sync, eventual consistency
Do NOT use CQRS for a simple blog or to-do app.
Interview Tip
When discussing high-read systems, say: 'For a system with skewed read/write ratios like an e-commerce product page, I would use CQRS. The write side uses a normalized PostgreSQL database with ACID transactions for order placement. The read side uses denormalized projections in Redis for product pages and Elasticsearch for search. Events published via Kafka keep read models eventually consistent with writes. This lets me scale reads and writes independently and optimize each database for its access pattern.'
<Architecture overview
/blockquote>
Key Takeaway
CQRS separates command (write) and query (read) models. The write side enforces business rules on normalized data. The read side serves pre-computed, denormalized projections. They stay in sync via events. Use CQRS when read and write workloads have fundamentally different requirements.
No comments yet. Be the first to start the thread!
Related Articles
Enjoyed this content?
Your support keeps us creating free resources
We put a lot of hours into researching and writing these guides. If it helped you, consider buying us a coffee. Every bit goes toward keeping ScaleDojo's content free and growing.
$
One-time payment via Stripe. ScaleDojo account required.