Một máy chủ database duy nhất là một điểm chết duy nhất: nó hỏng thì cả ứng dụng ngừng. Streaming replication là cơ chế nền của PostgreSQL để giải bài toán đó — nó đẩy WAL (nhật ký ghi trước) từ máy chính (primary) sang một hoặc nhiều máy dự phòng (standby) theo thời gian thực, giữ một bản sao gần như đồng bộ. Bài này không nói lý thuyết suông: tôi dựng thật hai container PostgreSQL, cho chúng nhân bản với nhau, và đo từng bước.

Cơ chế: WAL là nguồn chân lý

PostgreSQL ghi mọi thay đổi vào WAL trước khi áp vào bảng. Streaming replication tận dụng chính điều đó: standby mở một kết nối tới primary, nhận luồng WAL, và phát lại (replay) nó liên tục để bảng của mình luôn giống primary. Vì standby luôn ở trạng thái "đang phục hồi", nó chỉ đọc — không nhận ghi trực tiếp.

-- TRÊN PRIMARY: bật replication
-- wal_level = replica  (mặc định PG16, không cần đổi)
CREATE ROLE replica_user WITH REPLICATION LOGIN PASSWORD '...';
SELECT pg_create_physical_replication_slot('slot1');
-- pg_hba.conf: host replication replica_user 0.0.0.0/0 md5

Ảnh chụp đoạn mã SQL nền tối minh hoạ streaming replication primary đẩy WAL sang standby theo thời gian thực PostgreSQL 16 standby phát lại WAL để giữ một bản sao nóng chỉ đọc, trên primary bật replication tạo user và slot wal_level replica mặc định PG16 CREATE ROLE replica_user WITH REPLICATION LOGIN PASSWORD SELECT pg_create_physical_replication_slot slot1 pg_hba.conf host replication replica_user md5, dựng standby bằng pg_basebackup -R tự ghi cấu hình pg_basebackup -h primary -U replica_user -D rep -Fp -Xs -P -R -S slot1 -R tạo standby.signal cộng primary_conninfo trong postgresql.auto.conf -S slot1 dùng slot để primary giữ WAL cho tới khi standby nhận, kiểm tra trạng thái nhân bản trên primary ai đang nhận WAL SELECT application_name state sync_state client_addr FROM pg_stat_replication trên standby đang ở chế độ recovery chỉ đọc SELECT pg_is_in_recovery t là đúng standby, async vs sync async mặc định primary commit ngay không chờ standby nhanh nhưng có thể mất vài giao dịch cuối nếu primary chết đột ngột sync synchronous_standby_names primary chờ standby ghi WAL mới commit không mất dữ liệu nhưng mỗi commit chậm hơn

Hình 1: Ba bước — cấu hình primary (user, slot, pg_hba), dựng standby bằng pg_basebackup -R, kiểm tra bằng pg_stat_replication và pg_is_in_recovery. Cùng đánh đổi async vs sync.

Dựng standby bằng pg_basebackup

Standby là một bản sao vật lý của primary tại một thời điểm, sau đó bắt kịp bằng WAL. Công cụ pg_basebackup làm cả hai:

pg_basebackup -h primary -U replica_user -D /rep -Fp -Xs -P -R -S slot1

Cờ -R rất tiện: nó tự tạo file standby.signal (báo PostgreSQL khởi động ở chế độ standby) và ghi primary_conninfo (địa chỉ primary để kết nối). Cờ -S slot1 gắn với replication slot đã tạo — slot đảm bảo primary giữ lại WAL cho tới khi standby xác nhận đã nhận, tránh việc standby bị rớt do primary xoá WAL quá sớm.

Khi tôi khởi động container standby với bản sao này, log cho thấy nó vào việc ngay:

Ảnh chụp bảng kết quả đo thật nền tối streaming replication chạy giữa hai container PostgreSQL 16, log standby lúc khởi động kết nối và bắt đầu nhận WAL LOG entering standby mode LOG consistent recovery state reached at 1B/7C000100 LOG database system is ready to accept read-only connections LOG started streaming WAL from primary at 1B/7D000000 on timeline 1, pg_stat_replication trên primary application_name walreceiver state streaming sync_state async client_addr 172.22.0.3, ghi trên primary đọc ngay trên replica thao tác INSERT 100000 dòng trên primary primary 100000 replica 100000 tức thì INSERT thêm 500000 dòng primary 600000 replica 600000 bắt kịp, replica là read-only thử ghi replica INSERT INTO rep_test id VALUES 999 ERROR cannot execute INSERT in a read-only transaction, chế độ và độ trễ pg_is_in_recovery replica t primary f độ trễ replay LSN chênh khi đang ghi mạnh 0 byte mạng docker nội bộ cực nhanh async bắt kịp tức thì mạng WAN thật sẽ có trễ đo được

Hình 2: Log standby cho thấy "started streaming WAL from primary". pg_stat_replication báo state=streaming, sync_state=async. 600 nghìn dòng ghi trên primary xuất hiện tức thì trên replica. Replica từ chối ghi. Độ trễ ~0 byte trên mạng nội bộ.

Đo thật: nó chạy

Sau khi standby lên, tôi đo trên hệ thống thật:

1. pg_stat_replication trên primary cho thấy đúng một walreceiver đang kết nối: state = streaming, sync_state = async, client_addr = 172.22.0.3 (địa chỉ standby). Đây là bằng chứng replication đang chạy.

2. Ghi trên primary, đọc trên replica. Tôi INSERT 100.000 dòng vào một bảng trên primary; ngay sau đó, replica cũng có đúng 100.000 dòng. Ghi thêm 500.000 nữa — replica bắt kịp lên 600.000. Dữ liệu chảy sang gần như tức thì.

3. Replica là chỉ đọc. Thử INSERT trực tiếp lên replica cho lỗi thật:

ERROR:  cannot execute INSERT in a read-only transaction

SELECT pg_is_in_recovery() trả về t trên replica (đang phục hồi) và f trên primary. Standby cố ý không nhận ghi — mọi thay đổi phải đi qua primary rồi mới chảy xuống.

4. Độ trễ. Đo pg_wal_lsn_diff giữa vị trí WAL hiện tại của primary và vị trí replica đã phát lại, ngay cả khi đang ghi mạnh: 0 byte. Trên mạng docker nội bộ cực nhanh, standby async bắt kịp tức thì. Cần thành thật ở đây: trên mạng WAN thật (standby ở data center khác), con số này sẽ khác 0 và đo được — đó chính là "replication lag" mà bạn theo dõi trong vận hành thật.

Async và sync: đánh đổi cốt lõi

Mặc định là async: primary commit ngay khi ghi WAL cục bộ, không chờ standby xác nhận. Nhanh — độ trễ commit không phụ thuộc mạng tới standby — nhưng nếu primary chết đột ngột trước khi standby kịp nhận, vài giao dịch cuối có thể mất.

Sync (synchronous_standby_names = 's1') thì ngược lại: primary chờ standby ghi WAL xong mới xác nhận commit. Không mất dữ liệu ngay cả khi primary chết — nhưng mỗi commit giờ phải chịu thêm một vòng đi-về mạng tới standby, nên chậm hơn, và nếu standby chết thì primary có thể treo chờ. Đây là đánh đổi kinh điển giữa độ bền và độ trễ: chọn theo mức mất mát bạn chấp nhận được.

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

Replication slot là con dao hai lưỡi. Slot đảm bảo primary không xoá WAL standby chưa nhận — tốt cho độ tin cậy. Nhưng nếu standby chết và không quay lại, primary sẽ giữ WAL mãi mãi, làm đầy đĩa pg_wal cho tới khi hết chỗ và primary sập. Luôn theo dõi standby còn sống, và cân nhắc max_slot_wal_keep_size để giới hạn.

Standby async có thể tụt lại khi tải ghi cao. Nếu primary ghi nhanh hơn mạng đẩy WAL, standby lag tăng dần. Với read replica (bài sau), lag nghĩa là truy vấn trên replica đọc dữ liệu hơi cũ. Theo dõi replay_lag trong pg_stat_replication.

Đây là nhân bản vật lý, toàn bộ cụm. Streaming replication sao chép cả instance (mọi database), ở mức byte của WAL. Nó không cho phép chọn lọc bảng hay database, cũng không nhân bản giữa các phiên bản PostgreSQL khác nhau. Cần nhân bản chọn lọc theo bảng thì dùng logical replication — một cơ chế khác.

Ba ý mang về

  1. Streaming replication đẩy WAL từ primary sang standby theo thời gian thực để có bản sao nóng chỉ đọc: dựng thật bằng pg_basebackup -R + replication slot, đo được pg_stat_replication báo state=streaming và 600 nghìn dòng ghi trên primary xuất hiện tức thì trên replica.
  2. Standby luôn ở chế độ chỉ đọc: pg_is_in_recovery() trả t, và thử ghi cho lỗi thật cannot execute INSERT in a read-only transaction — mọi thay đổi phải qua primary rồi mới chảy xuống.
  3. Async vs sync là đánh đổi độ trễ và độ bền: async (mặc định) commit nhanh nhưng có thể mất vài giao dịch cuối khi primary chết; sync không mất dữ liệu nhưng mỗi commit chậm hơn và có thể treo nếu standby chết — cùng với đó, canh replication slot để primary không giữ WAL đầy đĩa.

Phần sau ta khai thác chính bản sao nóng này để tăng khả năng phục vụ: Phần sau đo cách dùng read replica để phân tải đọc — định tuyến truy vấn đọc sang replica, và cạm bẫy đọc phải dữ liệu cũ do replication lag.