system-design

CQRS Pattern: Tách biệt Read/Write để tối ưu hiệu năng

Command Query Responsibility Segregation (CQRS) có thực sự cần thiết? Khi nào nên tách model Đọc và Ghi ra làm hai?

newspaper

Thắng Nguyễn

8 tháng 1, 2026 schedule 3 phút đọc
CQRS Pattern: Tách biệt Read/Write để tối ưu hiệu năng
Featured Image

Chúng ta thường quen với mô hình CRUD: Một Model User dùng cho cả việc tạo user mới (Create) và hiển thị user (Read). Nhưng ở các hệ thống lớn (như Facebook, Shopee), nhu cầu Đọc và Ghi rất khác nhau.

  • Ghi: Cần validate phức tạp, transaction an toàn. Tần suất ít (ví dụ: user post bài 1 lần/ngày).
  • Đọc: Cần cực nhanh, join nhiều bảng. Tần suất cao (ví dụ: 1 triệu người xem bài post đó).

Dùng chung một Database Schema cho cả hai việc trên sẽ dẫn đến sự thỏa hiệp kìm hãm lẫn nhau. CQRS giúp chúng ta “phân thân” hệ thống.

Command (Ghi) vs Query (Đọc)

Command Side

Chịu trách nhiệm thay đổi trạng thái hệ thống (Insert, Update, Delete).

  • Input: CreateOrderCommand, ApproveUserCommand.
  • Output: Void hoặc status (không trả về dữ liệu).
  • Database: Thường là Relational DB (MySQL/Postgres) được chuẩn hóa (3NF) để đảm bảo toàn vẹn dữ liệu.

Query Side

Chịu trách nhiệm hiển thị dữ liệu (Select).

  • Input: GetUserProfileQuery, GetSalesStatsQuery.
  • Output: DTO (Data Transfer Object) thuần túy.
  • Database: Có thể là NoSQL (Mongo), Search Engine (Elasticsearch) hoặc Redis cache. Dữ liệu được lưu dạng “phẳng” (Denormalized) sẵn để query cái ra ngay, không cần JOIN.

Đồng bộ dữ liệu thế nào?

Đây là phần khó nhất: Data Consistency. Khi Command Side ghi xong vào MySQL, làm sao dữ liệu bên Elasticsearch (Query Side) cập nhật?

Thường ta dùng mô hình Event-Driven:

  1. Command Handler xử lý xong -> Bắn event UserCreated.
  2. Một Worker lắng nghe event này -> Update dữ liệu sang Elasticsearch/Redis.

Độ trễ (lag) giữa lúc ghi và lúc đọc được gọi là Eventual Consistency. User vừa bấm Save xong, F5 ngay có thể chưa thấy data mới. Đây là sự đánh đổi chấp nhận được ở các hệ thống High Traffic.

Đừng lạm dụng CQRS (Over-engineering)

Tôi thường khuyên team:

  • Level 0 (Nên dùng ngay): Tách UserService thành UserWriteServiceUserReadService ở tầng Code, nhưng vẫn chung Database. Để logic code rõ ràng.
  • Level 1 (Cân nhắc): Chung Database nhưng tạo các bảng “Read Model” riêng (Materialized Views) cho các báo cáo phức tạp.
  • Level 2 (Chỉ khi cần Scale): Tách Database vật lý riêng (MySQL để ghi, Elastic để đọc). Hầu hết dự án vừa và nhỏ chưa cần đến mức này.

Kết luận

CQRS giải phóng nút thắt cổ chai về hiệu năng đọc (Read performance). Nó cho phép bạn scale độc lập: Nếu web đọc nhiều ghi ít, bạn chỉ cần thêm server cho cụm Query mà không ảnh hưởng cụm Command. Tuy nhiên, cái giá phải trả là sự phức tạp trong đồng bộ dữ liệu. Hãy cân nhắc kỹ.

quizQuick Quiz
Câu 1/3

CQRS viết tắt của cụm từ gì?

history_edu Góc học tập & giải trí

Thử Thách Kiến Thức Lịch Sử?

Khám phá hàng trăm câu hỏi trắc nghiệm lịch sử thú vị tại HistoQuiz. Vừa học vừa chơi, nâng cao kiến thức ngay hôm nay!

Chơi Ngay arrow_forward