Bài trước ta thấy read replica tăng thông lượng đọc, nhưng kèm một cảnh báo: đọc từ replica có thể thấy dữ liệu cũ. Con số đứng sau lời cảnh báo đó là replication lag — khoảng cách giữa những gì primary đã ghi và những gì replica đã phát lại. Bài này không nói chung chung: tôi đo lag bằng cả byte lẫn giây, rồi cố ý làm nó phình lên để bạn thấy tận mắt hệ quả — replica trả về dữ liệu cũ trong khi primary đã có dữ liệu mới.

Lag là gì, và ba mức của nó

Nhớ lại cơ chế: primary ghi thay đổi vào WAL, gửi WAL sang replica, replica phát lại (replay) để cập nhật bảng của mình. Mỗi bước có độ trễ riêng, nên PostgreSQL cho ba con số:

  • write_lag: từ khi primary ghi tới khi replica nhận được WAL.
  • flush_lag: tới khi replica ghi WAL xuống đĩa (an toàn dữ liệu).
  • replay_lag: tới khi replica phát lại xong — tức dữ liệu thấy được khi bạn đọc.

Đọc từ replica quan tâm nhất tới replay_lag, vì đó là lúc dữ liệu thực sự hiện ra trong truy vấn.

-- Đo từ PRIMARY:
SELECT application_name,
  pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS lag_byte,
  write_lag, flush_lag, replay_lag
FROM pg_stat_replication;

Ảnh chụp đoạn mã SQL nền tối minh hoạ replication lag đo độ trễ nhân bản và hệ quả lên tính đúng đắn PostgreSQL 16 lag là khoảng cách giữa WAL primary đã ghi và WAL replica đã phát lại, đo lag từ primary theo byte và theo thời gian SELECT application_name pg_wal_lsn_diff pg_current_wal_lsn replay_lsn AS lag_byte write_lag flush_lag replay_lag FROM pg_stat_replication replay_lag thời gian từ khi primary ghi tới khi replica phát lại, đo lag từ replica SELECT now trừ pg_last_xact_replay_timestamp AS lag_thoi_gian pg_wal_lsn_diff pg_last_wal_receive_lsn pg_last_wal_replay_lsn AS byte_chua_replay lag_thoi_gian replica đang trễ primary bao lâu, ba mức lag đừng nhầm write_lag primary gửi replica nhận được WAL flush_lag replica ghi WAL xuống đĩa an toàn dữ liệu replay_lag replica phát lại xong dữ liệu thấy được khi đọc đọc từ replica quan tâm replay_lag, cái gì làm lag tăng tải ghi cao trên primary WAL sinh nhanh hơn replica replay kịp mạng chậm xa giữa primary và replica WAN replica yếu CPU đĩa hoặc đang chạy truy vấn đọc nặng dài xung đột replay query dài trên replica chặn phát lại

Hình 1: Đo lag từ primary (pg_stat_replication) và từ replica (pg_last_xact_replay_timestamp). Ba mức write/flush/replay — đọc từ replica quan tâm replay_lag. Và những nguyên nhân làm lag tăng.

Đo thật: làm lag phình lên và thấy dữ liệu cũ

Ở điều kiện bình thường trên lab của tôi, replica bám sát primary: replay_lag chỉ khoảng 0,002 giây (2 mili giây), primary và replica đều thấy 1.000 dòng. Lag gần như bằng không.

Để thấy hệ quả, tôi cố ý làm replica chậm lại bằng pg_wal_replay_pause() — tạm dừng việc phát lại WAL (mô phỏng một replica quá tải hoặc mạng nghẽn). Rồi ghi 500.000 dòng vào primary:

Ảnh chụp bảng kết quả đo thật nền tối replication lag và hệ quả PostgreSQL 16 hai container, baseline replay bình thường lag gần như bằng 0 primary 1000 replica 1000 replay_lag 0,00197 giây khoảng 2 ms, tạm dừng replay trên replica rồi ghi 500000 dòng vào primary pg_wal_replay_pause is_paused paused sau khi ghi 500k primary 501000 replica đang dừng replay 1000 kẹt ở mốc cũ, lag đo được 66 MB WAL chưa phát lại lag thời gian 2,33 giây và tăng dần đọc từ replica lúc này thấy dữ liệu cũ thiếu 500000 dòng vừa ghi, resume replay replica bắt kịp pg_wal_replay_resume replica lên 501000 dòng trong dưới 1 giây lag về 0 byte, read-your-writes trong điều kiện mạng nội bộ cực nhanh 200 lần ghi primary rồi đọc replica ngay 0 trên 200 lần đọc trượt trên link local sub-ms replica bắt kịp kịp thời nhưng trên WAN tải cao lag lên hàng giây như demo pause đọc ngay sau ghi rất dễ thấy dữ liệu cũ

Hình 2: Baseline replay_lag ~2 ms. Sau khi dừng replay và ghi 500k: primary có 501.000 dòng, replica kẹt ở 1.000 (66 MB WAL chưa phát lại, lag 2,33 giây và tăng). Resume: bắt kịp trong dưới 1 giây, lag về 0. Read-your-writes trên link local nhanh: 0/200 trượt.

Kết quả thật rất rõ:

  • Primary: 501.000 dòng.
  • Replica (đang dừng replay): vẫn 1.000 dòng — kẹt cứng ở mốc cũ.
  • Lag đo được: 66 MB WAL chưa phát lại, và lag thời gian 2,33 giây tăng dần.

Đây chính là "đọc phải dữ liệu cũ" thành hiện thực: nếu ứng dụng đọc bảng này từ replica ngay lúc đó, nó sẽ thiếu 500.000 dòng vừa ghi — một sai lệch tính đúng đắn, không phải chỉ chậm. Khi tôi pg_wal_replay_resume(), replica bắt kịp lên 501.000 dòng trong dưới một giây và lag về 0 byte.

Trung thực về điều kiện lab

Tôi cũng thử kịch bản "read-your-writes" trong điều kiện bình thường (không dừng replay): ghi một dòng vào primary rồi đọc ngay từ replica, lặp 200 lần. Kết quả: 0/200 lần replica trượt — nó luôn kịp thấy dòng vừa ghi.

Đây là con số thật và cần nói thẳng: trên mạng docker nội bộ với độ trễ dưới một mili giây, replica bắt kịp gần như tức thì, nên race hiếm khi xảy ra. Nhưng điều đó không có nghĩa vấn đề không tồn tại — demo dừng replay ở trên chứng minh lag có thể lên hàng giây rất dễ dàng. Trên hệ thống thật (replica ở data center khác, tải ghi cao, replica đang chạy truy vấn phân tích nặng), lag hàng giây là chuyện thường, và khi đó "đọc ngay sau khi ghi" từ replica sẽ thường xuyên thấy dữ liệu cũ.

Cái gì làm lag tăng, và cách giảm

Nguyên nhân chính: tải ghi cao trên primary (WAL sinh nhanh hơn replica replay kịp), mạng chậm hoặc xa (WAN), replica yếu về CPU/đĩa, hoặc một truy vấn đọc dài trên replica chặn việc phát lại (xung đột replay). Con số replay_lag trong pg_stat_replication là thứ cần giám sát liên tục.

Giảm lag: cho replica phần cứng đủ mạnh (nhất là đĩa), đặt replica gần primary về mạng, và với truy vấn đọc dài trên replica cân nhắc max_standby_streaming_delay (đánh đổi giữa để query chạy xong hay ưu tiên replay). Nếu ứng dụng thật sự cần đọc thấy ghi của chính mình, đừng chống lag — hãy đọc từ primary cho những truy vấn đó, hoặc dùng synchronous replication (bài sau).

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

Giám sát lag là bắt buộc khi dùng read replica. Một replica lag mà không ai biết là một quả bom: ứng dụng vẫn đọc từ nó và âm thầm trả dữ liệu cũ cho người dùng. Đặt cảnh báo khi replay_lag vượt ngưỡng bạn chấp nhận được (ví dụ vài giây).

Lag byte và lag thời gian nói hai chuyện khác nhau. Lag byte (66 MB) cho biết khối lượng WAL tồn đọng; lag thời gian (2,33 giây) cho biết dữ liệu cũ bao lâu. Khi primary im (không ghi), lag byte có thể 0 nhưng lag thời gian vẫn nhích lên vì đồng hồ chạy — dùng đúng con số cho đúng câu hỏi.

Đừng để replica lag làm đầy WAL trên primary. Nếu dùng replication slot (như ta đã dựng), primary giữ WAL cho tới khi replica nhận. Replica lag nặng và kéo dài khiến pg_wal trên primary phình to — theo dõi và cân nhắc max_slot_wal_keep_size để primary không sập vì hết đĩa.

Ba ý mang về

  1. Replication lag là khoảng cách giữa WAL primary đã ghi và replica đã phát lại, đo bằng byte (pg_wal_lsn_diff) và giây (replay_lag) — ba mức write/flush/replay, trong đó đọc từ replica quan tâm replay_lag vì đó là lúc dữ liệu hiện ra.
  2. Lag có hệ quả thật lên tính đúng đắn: đo thật, khi làm replica chậm lại, nó kẹt ở 1.000 dòng trong khi primary có 501.000 (66 MB chưa replay, 2,33 giây) — đọc từ replica lúc đó thiếu nửa triệu dòng vừa ghi, một sai lệch chứ không chỉ là chậm.
  3. Trên link nhanh replica bắt kịp gần tức thì (0/200 lần trượt) nhưng đừng dựa vào đó: trên WAN hoặc tải ghi cao lag lên hàng giây — giám sát replay_lag, đọc từ primary cho các truy vấn cần thấy ghi của chính mình, và canh WAL slot để primary không đầy đĩa.

Phần sau ta đi vào đúng cơ chế quyết định độ bền và độ trễ đó: Phần sau đo nhân bản đồng bộ và bất đồng bộ — vì sao sync không mất dữ liệu nhưng làm mỗi commit chậm hơn, và cách chọn giữa hai.