Ứng dụng của bạn đọc nhiều hơn ghi rất nhiều — trang sản phẩm, tìm kiếm, báo cáo, dashboard. Một máy chủ primary duy nhất gồng gánh cả ghi lẫn đọc sẽ đến lúc quá tải. Bài trước ta đã dựng một streaming replica — một bản sao nóng chỉ đọc. Bài này khai thác chính nó cho mục đích mở rộng: đưa truy vấn đọc sang replica để giải phóng primary, và cộng thêm năng lực đọc. Tôi đo thật trên hai node đang chạy để xem thông lượng tăng bao nhiêu.

Ý tưởng: tách đường ghi và đường đọc

Nguyên tắc đơn giản: ghi đi primary, đọc đi replica. Mọi INSERT/UPDATE/DELETE phải qua primary (replica từ chối ghi). Nhưng SELECT thì có thể đi tới bất kỳ replica nào — và mỗi replica gánh một phần tải đọc, nên tổng năng lực đọc của hệ thống tăng theo số node.

-- Ép một nhánh giao dịch chỉ đọc (dễ định tuyến sang replica)
SET default_transaction_read_only = on;

Ảnh chụp đoạn mã SQL nền tối minh hoạ read replica phân tải đọc sang bản sao giữ primary cho ghi PostgreSQL 16 nhiều truy vấn đọc là bài toán mở rộng theo chiều ngang, ý tưởng ghi đi primary đọc đi replica một hoặc nhiều app ghi INSERT UPDATE DELETE tới PRIMARY WAL sang REPLICA đọc SELECT mỗi replica gánh thêm một phần tải đọc tổng năng lực đọc tăng, định tuyến ở tầng ứng dụng tách theo loại truy vấn conn is_write query primary_pool replica_pool nhiều framework có sẵn đánh dấu transaction read-only sang replica SET default_transaction_read_only on ép nhánh chỉ đọc, hoặc định tuyến bằng proxy PgBouncer HAProxy Pgpool proxy đứng trước gửi SELECT sang replica còn lại sang primary ứng dụng chỉ nối tới proxy không cần biết topology, cạm bẫy đọc phải dữ liệu cũ replication lag ghi vào primary rồi đọc ngay từ replica có thể chưa thấy thay đổi replica chưa kịp phát lại WAL read-your-writes bị vỡ truy vấn cần nhất quán tuyệt đối đọc từ primary chi tiết bài sau

Hình 1: Ghi đi primary, đọc đi replica. Định tuyến ở tầng ứng dụng (theo loại truy vấn) hoặc bằng proxy. Cạm bẫy: đọc ngay sau khi ghi có thể thấy dữ liệu cũ do replication lag.

Đo thật: thông lượng đọc gấp 2,6 lần

Tôi có primary và replica đang streaming với cùng một triệu dòng dữ liệu. Truy vấn đọc cho kết quả giống hệt trên cả hai (SELECT count(*) WHERE gia > 500 → 500.272 ở cả primary lẫn replica). Giờ đo thông lượng đọc bằng pgbench (chế độ chỉ đọc, 8 client):

Ảnh chụp bảng kết quả đo thật nền tối phân tải đọc primary cộng replica pgbench -S 8 client PostgreSQL 16, cùng dữ liệu 1 triệu dòng đọc chạy được trên cả hai SELECT count từ sanpham WHERE gia lớn hơn 500 primary 500272 replica 500272 giống hệt, thông lượng đọc transactions mỗi giây kịch bản chỉ primary phục vụ đọc primary 26703 tps tổng 26703 chỉ replica phục vụ đọc replica 30486 tps tổng 30486 cả hai chạy song song primary 37856 replica 38220 tổng 76076, tách đọc ra hai node tổng thông lượng khoảng 76k tps gấp khoảng 2,6 lần một node thêm replica nữa thì đọc scale gần tuyến tính primary được rảnh tay cho ghi, ghi vẫn chỉ đi primary replica từ chối ghi replica INSERT ERROR cannot execute INSERT in a read-only transaction định tuyến SELECT sang replica INSERT UPDATE DELETE sang primary, đánh đổi phải nhớ replica có thể trễ lag đọc ngay sau khi ghi có thể thấy dữ liệu cũ đọc cần nhất quán tuyệt đối vẫn phải đi primary đọc phân tích nặng báo cáo tìm kiếm đưa hết sang replica rất hợp

Hình 2: Chỉ primary phục vụ đọc: 26.703 tps. Chỉ replica: 30.486 tps. Cả hai chạy song song: primary 37.856 + replica 38.220 = 76.076 tps — gấp ~2,6 lần một node. Ghi vẫn chỉ đi primary; replica từ chối ghi.

Con số thật:

  • Chỉ primary phục vụ đọc: 26.703 tps.
  • Chỉ replica phục vụ đọc: 30.486 tps (mỗi node đọc độc lập được).
  • Cả hai chạy song song: primary 37.856 + replica 38.220 = 76.076 tps cộng lại.

Khi tải đọc được chia ra hai node, tổng thông lượng đạt ~76 nghìn tps — gấp khoảng 2,6 lần so với một node đơn lẻ. Thêm replica thứ hai, thứ ba thì năng lực đọc scale gần như tuyến tính. Và quan trọng không kém: khi reads đổ sang replica, primary được rảnh tay để tập trung xử lý ghi — thứ mà không thể phân tán.

Định tuyến: ứng dụng hay proxy

Có hai cách phổ biến để "gửi đọc sang replica":

Ở tầng ứng dụng. Code chọn kết nối theo loại thao tác: ghi → pool tới primary, đọc → pool tới replica. Nhiều framework hỗ trợ sẵn (đánh dấu một giao dịch là read-only thì tự định tuyến sang replica). Cách này linh hoạt nhưng lập trình viên phải nhớ phân loại đúng.

Bằng proxy. Một proxy (PgBouncer/HAProxy/Pgpool-II) đứng trước, tự nhận diện SELECT và gửi sang replica, còn lại sang primary. Ứng dụng chỉ nối tới proxy, không cần biết có bao nhiêu replica hay chúng ở đâu. Đổi lại thêm một tầng phải vận hành và giám sát.

Đánh đổi cần cân nhắc

Cạm bẫy lớn nhất: đọc phải dữ liệu cũ. Replica async luôn trễ sau primary một chút (replication lag). Nếu bạn ghi vào primary rồi đọc ngay từ replica, có thể replica chưa kịp phát lại thay đổi đó — bạn thấy dữ liệu cũ. Đây là vấn đề "read-your-writes": người dùng vừa sửa hồ sơ, tải lại trang thấy dữ liệu cũ. Với thao tác cần nhất quán tuyệt đối (ngay sau khi ghi), phải đọc từ primary. Bài sau đo cụ thể độ trễ này và cách xử lý.

Không phải mọi truy vấn nên đi replica. Phù hợp nhất cho replica: báo cáo, phân tích nặng, tìm kiếm, dashboard — những thứ đọc nhiều và chấp nhận dữ liệu trễ vài giây. Không phù hợp: đọc ngay sau ghi trong cùng luồng nghiệp vụ, kiểm tra số dư trước khi trừ tiền, bất cứ thứ gì yêu cầu thấy trạng thái mới nhất tuyệt đối.

Replica cũng tiêu tài nguyên và cần vận hành. Mỗi replica là một máy chủ đầy đủ cần RAM, đĩa, giám sát. Nó không "miễn phí" — chỉ đáng khi tải đọc thật sự lớn. Với ứng dụng nhỏ, tối ưu truy vấn và index trên một node thường đủ và đơn giản hơn nhiều.

Ba ý mang về

  1. Read replica cộng thêm năng lực đọc theo chiều ngang: đo thật, tách tải đọc ra hai node cho tổng 76.076 tps (primary 37.856 + replica 38.220) — gấp ~2,6 lần một node đơn lẻ, và primary được giải phóng để lo phần ghi không phân tán được.
  2. Định tuyến ghi-sang-primary, đọc-sang-replica ở tầng ứng dụng (chọn pool theo loại truy vấn) hoặc bằng proxy (PgBouncer/HAProxy/Pgpool tự nhận diện SELECT) — replica từ chối ghi nên phân loại sai một câu ghi sẽ báo lỗi rõ ràng.
  3. Cạm bẫy là đọc phải dữ liệu cũ do replication lag: đọc ngay sau khi ghi có thể chưa thấy thay đổi (read-your-writes vỡ) — đưa báo cáo/phân tích/tìm kiếm sang replica, giữ các đọc cần nhất quán tuyệt đối ở primary.

Phần sau ta mổ xẻ chính con số quyết định tất cả: Phần sau đo replication lag thật — cách đo bằng LSN và thời gian, điều gì làm nó tăng, và hệ quả cụ thể lên tính đúng đắn của ứng dụng.