CQRS Explained: Separating Reads and Writes for Scalable Systems
Understand CQRS (Command Query Responsibility Segregation) — why separating read and write models enables scalability, with practical implementation.
CQRS (Command Query Responsibility Segregation)
CQRS is an architectural pattern that separates the data model used for writes (commands) from the data model used for reads (queries), allowing each to be optimized independently for its specific workload.
What It Really Means
In a traditional CRUD application, the same data model serves both reads and writes. You insert a row into a normalized relational table, and you query that same table for the UI. This works fine for simple applications, but at scale, reads and writes have fundamentally different requirements.
Writes need strong consistency, validation, and complex business logic. Reads need fast response times, denormalized data for the UI, and often serve 10-100x more traffic than writes. Optimizing a single model for both is a compromise — normalize for write integrity and reads require expensive joins, denormalize for read speed and writes become error-prone.
CQRS, formalized by Greg Young around 2010 (building on Bertrand Meyer's Command-Query Separation principle), proposes using two separate models: a write model optimized for accepting and validating commands, and a read model (one or more) optimized for serving queries. The two models are connected through an event or synchronization mechanism.
CQRS is used by financial trading platforms, e-commerce systems with complex product catalogs, social media feeds, and any system where read and write workloads have very different scaling requirements.
How It Works in Practice
The Two Sides
Command Side (Write Model):
- Receives commands:
CreateOrder,UpdateUserProfile,TransferFunds - Validates business rules and invariants
- Persists state changes to the write database
- Publishes domain events:
OrderCreated,ProfileUpdated,FundsTransferred - Optimized for consistency and correctness
Query Side (Read Model):
- Receives queries:
GetOrderDetails,SearchProducts,GetUserDashboard - Reads from a denormalized read database (or materialized views)
- Optimized for speed and the specific query patterns of the UI
- Updated asynchronously by consuming domain events from the command side
Real-World: E-Commerce Product Catalog
Write model (normalized PostgreSQL):
productstable with core attributesproduct_variantstable (sizes, colors)product_categoriesjoin tablepricing_rulestable with complex logic
Read models (optimized for specific views):
- Product listing (Elasticsearch): Denormalized documents with name, price, image, category, rating — one document per product for fast search
- Product detail page (Redis): Pre-computed JSON with all variants, reviews summary, related products — served in <10ms
- Admin dashboard (PostgreSQL materialized view): Aggregated sales data, inventory levels
When a product manager updates a price, the command side validates business rules (no price below cost, approval required for >20% changes), persists to PostgreSQL, and publishes PriceUpdated. Event consumers update Elasticsearch and Redis within milliseconds.
Real-World: Financial Trading
A trading platform processes thousands of trades per second (write-heavy) while serving real-time portfolio views to millions of users (read-heavy).
- Write model: Append-only trade log with strict validation (position limits, regulatory checks)
- Read model 1: Real-time position aggregation (in-memory cache)
- Read model 2: Historical trade search (Elasticsearch)
- Read model 3: Regulatory reporting (columnar database)
Implementation
Trade-offs
Advantages
- Independent scaling: Scale read replicas to 100 nodes while keeping writes on 3 nodes
- Optimized query performance: Read models can be denormalized, pre-computed, and served from caches
- Flexibility: Different read models for different consumers (mobile app, admin dashboard, analytics)
- Simpler write logic: Command handlers focus on validation and business rules without worrying about query optimization
Disadvantages
- Eventual consistency: Read models are updated asynchronously, so queries may return stale data for a brief period
- Complexity: Two data models, event publishing, event consumers, and synchronization logic — significantly more infrastructure than CRUD
- Data duplication: The same data exists in multiple forms, increasing storage and maintenance costs
- Event schema evolution: Changing the event format requires updating all read model consumers
Common Misconceptions
-
"CQRS requires Event Sourcing" — CQRS and Event Sourcing are independent patterns that are often used together but do not depend on each other. You can implement CQRS with a standard relational write database and sync the read model through change data capture or domain events.
-
"CQRS means two databases" — At its simplest, CQRS can be a write model with normalized tables and a read model with denormalized views in the same database. Separate databases are common but not required.
-
"Every application needs CQRS" — Most applications do fine with CRUD. CQRS adds significant complexity. It pays off when read and write workloads have very different scaling requirements, or when you need multiple specialized read models.
-
"CQRS eliminates the need for caching" — Read models are a form of materialized cache, but you may still want additional caching layers (CDN, Redis) depending on latency requirements.
-
"The read model must mirror the write model's schema" — The whole point of CQRS is that they are different. The read model should be shaped by query needs, not by the write model's structure.
How This Appears in Interviews
CQRS appears in system design interviews for data-intensive applications:
- "Design a news feed system" — the write model stores posts and social graph; the read model is a pre-computed, denormalized feed per user, updated asynchronously. This is classic CQRS. See our system design interview guide.
- "How do you handle the read-write ratio of 100:1?" — separate read and write paths, scale read replicas independently, use CQRS with denormalized read stores.
- "What about consistency between the read and write models?" — explain eventual consistency, the propagation delay, and techniques like read-your-writes consistency for the user who just made a change.
- "When would you NOT use CQRS?" — simple CRUD apps, small teams, low traffic, and when the added complexity is not justified.
Related Concepts
- Event Sourcing — Often paired with CQRS for event-driven architectures
- Saga Pattern — Cross-service coordination in CQRS-based systems
- Eventual Consistency — The consistency model between read and write models
- Database Sharding — Scaling the write model horizontally
- System Design Interview Guide — Framework for architecture decisions
- Algoroq Pricing — Practice architecture and design interview questions
GO DEEPER
Learn from senior engineers in our 12-week cohort
Our Advanced System Design cohort covers this and 11 other deep-dive topics with live sessions, assignments, and expert feedback.