Course contentsShow
← Databases & SQL
Lesson 1728 of 1,95316. Distributed Data ArchitecturesFree lesson

CQRS (Command Query Responsibility Segregation)

Separate read and write models to optimize performance for different types of access patterns.

In this lesson, you will learn how to decouple your database’s read and write operations to achieve independent scaling and performance optimization.

Moving Beyond CRUD

In traditional relational database designs, we often use the same schema (and often the same tables) for both writes (Commands) and reads (Queries). While this works for simple applications, it hits a wall when your application’s read patterns (e.g., complex reporting, search) significantly differ from your write patterns (e.g., atomic transactional updates).

Command Query Responsibility Segregation (CQRS) solves this by strictly separating these two concerns.

The Core Concept

  • Command Model: Optimized for write performance, data integrity, and complex business logic. This side focuses on enforcing constraints and ensuring the state of your entities is valid.
  • Query Model: Optimized for read speed. This model typically stores data in a format ready for display, such as a flattened "denormalized" view. By the time the data reaches the Query Model, all complex joins and transformations are already computed.

A Practical Illustration

Imagine a social media platform. The Command side handles a user updating their profile, where we ensure referential integrity and perform validation:

-- Command Model: Strict, normalized, high integrity
UPDATE user_profiles 
SET bio = 'New Bio', updated_at = NOW() 
WHERE user_id = 123;

The Query side, however, needs to serve high-traffic profile pages. Instead of joining multiple tables, the Query side consumes an event or update from the Command side to maintain a read-optimized table:

-- Query Model: Optimized for reads (Flattened schema)
-- No joins needed, just a single SELECT
SELECT display_name, bio, follower_count 
FROM materialized_user_views 
WHERE user_id = 123;

Why Do This?

Because these two models are separate, you can scale them differently. You might run your Command database on a high-IOPS instance to handle writes, while your Query side is scaled across multiple read replicas optimized for caching. Because the models serve different masters, you stop forcing your read patterns to conform to the limitations of your write schema.

Key Takeaway: By segregating models, you eliminate the conflict between optimizing for transactional integrity and optimizing for query performance.