Introduction
Every system we designed hit the same tension: writes need strong consistency and normalized data; reads need low latency and denormalized data. CQRS (Command Query Responsibility Segregation) is the formal name for separating those two concerns — and you’ve been using it throughout the series without calling it that.
The Problem CQRS Solves
Writes need:
→ Strong consistency, ACID transactions
→ Normalized data (no duplication)
→ Optimized for correctness
Reads need:
→ Low latency
→ Denormalized data (everything in one query)
→ Complex joins and aggregations
→ Optimized for speed
These requirements fight each other in a single database. Reads slow down writes. Writes lock tables that reads need. One model cannot optimize for both.
What CQRS Means
CQRS = Command Query Responsibility Segregation
Command = Write (insert, update, delete)
Query = Read (select)
Segregation = separate them completely
Instead of one model for both:
→ Command Model — optimized for writes
→ Query Model — optimized for reads
Example — E-Commerce Without CQRS
Single PostgreSQL handles everything:
Write — Place order:
→ Insert orders, update inventory, insert payment, update loyalty points
Read — Order dashboard:
→ JOIN orders + inventory + payments + users + products
→ Complex, slow at scale
→ Write locks slow reads, slow reads slow writes
→ Everything suffers
Same System With CQRS
Write side (Command):
→ PostgreSQL — normalized, ACID, correctness-first
Read side (Query):
→ Separate read databases — denormalized, speed-first
→ Dashboard, order history, analytics each get their own shape
Sync via events:
User places order → write to PostgreSQL
→ Publish Kafka event: "order.placed"
↓
Consumers update read models:
Read Model 1 — Redis:
"order:u456:recent" → latest 10 orders pre-computed
→ Dashboard loads instantly
Read Model 2 — Elasticsearch:
Full order details indexed
→ Search by product, date, status
Read Model 3 — ClickHouse:
Aggregated stats pre-computed
→ "Total revenue this month"
Visualized
Without CQRS:
User Request → Single DB ← reads and writes fight each other
With CQRS:
Write Request → Command Model (PostgreSQL)
↓ event
Kafka
↓
→ Query Model 1 (Redis) ← Read Request 1
→ Query Model 2 (Elasticsearch) ← Read Request 2
→ Query Model 3 (ClickHouse) ← Read Request 3
Each read model is shaped exactly for its query. No compromises.
CQRS in Systems We Already Designed
You’ve seen CQRS throughout the series — it just wasn’t named.
Feed System
Command Model:
→ Post saved to Cassandra (normalized)
Query Model:
→ Redis sorted set "feed:userId" — pre-computed, ordered post IDs
→ Read instantly, no joins
Write event → Kafka → Fan-out Service → updates Redis feeds
Notification System
Command Model:
→ Notification events in Cassandra
Query Model:
→ Redis counter "unread:userId" — pre-computed unread count
→ Read in 1ms without counting Cassandra rows
Write event → Kafka → increments Redis counter
URL Shortener
Command Model:
→ New URL saved to Cassandra
Query Model:
→ Redis cache "slug:abc123" → long URL
→ Write populates Redis read model
Google Drive
Command Model:
→ Operation log in Cassandra (event source)
Query Model 1:
→ Assembled document in Redis during meeting
Query Model 2:
→ Elasticsearch index for full-text search
The Consistency Trade-off
CQRS introduces eventual consistency between write and read models:
User places order at 10:00:00.000
→ Written to PostgreSQL immediately
→ Kafka event published
→ Redis read model updated at 10:00:00.050
Between .000 and .050:
→ Write model: order exists
→ Read model: order not yet visible
50ms window — acceptable for most features
Not acceptable for payment confirmation shown immediately
Read Your Own Writes
User places order → redirected to confirmation page
→ Page reads from read model
→ Read model not yet updated
→ User sees "order not found" ❌
Fix:
→ Return written data directly from command model response
→ Don't read from read model for immediate confirmation
→ Read model catches up in background
→ Next page load → read model ready ✅
When to Use CQRS
Use CQRS when:
- Read and write loads are very different (100k reads/sec vs 1k writes/sec)
- Read queries are complex — many joins, aggregations; denormalized model is much faster
- Multiple different read patterns — dashboard vs search need different shapes
- Write consistency is critical (payments, inventory) but reads can be eventually consistent
Don’t use CQRS when:
- Simple CRUD application
- Reads and writes are similar volume
- Small team — added complexity not worth it
- Strong consistency required on reads immediately
Event Sourcing (Often Paired With CQRS)
CQRS is often paired with Event Sourcing — which connects directly to Google Drive’s operation logs.
Normal database:
→ Store current state: "Account balance: ₹50,000"
→ History lost
Event Sourcing:
→ Store every event: "Deposited ₹10,000, Withdrew ₹5,000, Deposited ₹45,000"
→ Current state = replay all events
→ Complete audit trail
→ Reconstruct state at any point in time
Google Drive operation log = Event Sourcing:
→ Store every Insert/Delete operation
→ Current document = replay all operations
→ Snapshot = optimization to avoid replaying everything
Payment systems:
→ Every transaction is an event
→ Account balance = sum of all events
→ Regulatory compliance, replay to fix bugs
CQRS handles how you read vs write. Event Sourcing handles how you store changes. They work well together but are independent patterns.
How This Connects to Everything You’ve Learned
| Lesson | Role in CQRS |
|---|---|
| Message Queues (L8) | Kafka syncs command model → query models. Every CQRS system needs an event bus. |
| Caching (L6) | Redis is almost always the query model for hot read paths — pre-computed, denormalized. |
| Databases (L7) | Command → PostgreSQL or Cassandra. Query → Redis, Elasticsearch, ClickHouse. |
| CAP Theorem (L3) | Command model → CP. Query model → AP. CQRS lets different parts have different guarantees. |
| Google Drive | Operation log = event source. Redis assembly + Elasticsearch = read models. |
Key Takeaways
- CQRS separates read and write models — command model for correctness, query models for speed.
- Events keep them in sync — write → Kafka → consumers update each read model independently.
- You’ve used CQRS all along — feed Redis caches, notification counters, URL shortener cache, Google Drive operation logs.
- Eventual consistency is the trade-off — fix with read-your-own-writes for immediate confirmation flows.
- Event Sourcing stores events, not state — Google Drive operation logs and payment ledgers are the same idea.
One Line Summary
CQRS: use different models for reading and writing. Write model optimized for correctness. Read models optimized for speed. Keep them in sync via events.
Part of the system design series.