Máy dự phòng là thứ ai cũng dựng và ít ai đo. Phần này dựng một cặp máy chính – máy dự phòng thật, đo độ trễ dưới tải, rồi cố tình làm nó hỏng theo hai cách hay gặp nhất trên máy chủ thật.

Độ trễ dưới tải, chi phí đồng bộ, và hai kiểu hỏng

Dựng

Ba tham số trên máy chính:

wal_level = replica
max_wal_senders = 10
max_replication_slots = 10

Rồi tạo một khe sao chép và lấy ảnh chụp:

# trên máy chính
psql -c "select pg_create_physical_replication_slot('s1')"

# trên máy dự phòng
pg_basebackup -h m1 -U postgres -D /var/lib/postgresql/data -Xs -R -S s1

Cờ -R tự sinh tệp cấu hình và standby.signal, cờ -S s1 gắn vào khe vừa tạo. Khởi động lên là xong:

máy chính   : 1.000.000 dòng   pg_is_in_recovery = f
máy dự phòng: 1.000.000 dòng   pg_is_in_recovery = t

Độ trễ dưới tải

Tám kết nối cập nhật liên tục:

Giây Độ trễ byte Độ trễ giây Trạng thái
5 1.408 byte 0,000 streaming
10 25 kB 0,003 streaming
20 2.224 byte 0,000 streaming
25 2.616 byte 0,001 streaming

Dưới 3 mili giây suốt cả bài đo. Trên mạng nội bộ, sao chép luồng gần như tức thì.

Con số này quan trọng vì nó đặt lại kỳ vọng: nếu bản sao của bạn tụt lại hàng giây hoặc hàng phút, đó không phải giới hạn của cơ chế — đó là dấu hiệu có gì đó khác đang sai. Thường là đĩa của máy dự phòng chậm hơn máy chính, hoặc máy dự phòng đang chạy truy vấn nặng.

Một lưu ý khi đọc pg_last_xact_replay_timestamp(): nó trả về thời điểm của giao dịch cuối cùng đã phát lại. Khi máy chính không ghi gì, con số này lớn dần và trông như độ trễ khổng lồ. Ở lần đo đầu tôi thấy 259 giây, và đó chỉ có nghĩa là 259 giây qua không có giao dịch ghi nào.

Cách đo đúng dưới tải là so pg_current_wal_lsn() trên máy chính với replay_lsn trong pg_stat_replication.

Sao chép đồng bộ lấy đi 37%

synchronous_standby_names tps Độ trễ
'' (bất đồng bộ) 22.337 0,358 ms
'*' (đồng bộ) 13.964 0,573 ms

Máy chính đợi máy dự phòng ghi WAL xuống đĩa trước khi báo commit thành công. Cái giá là 37% thông lượng và 0,215 ms mỗi giao dịch.

Con số 0,215 ms đó gần bằng chi phí fsync đã đo ở phần 48 (0,123 ms) — hợp lý, vì nó là một lần fsync nữa cộng một vòng đi–về mạng.

Đây là đánh đổi giữa "không bao giờ mất giao dịch đã xác nhận" và "37% thông lượng". Phần 48 đã liệt kê năm mức của synchronous_commit, và mức local là lựa chọn ở giữa: giữ độ bền trên máy chính, không đợi máy dự phòng.

Kiểu hỏng 1: truy vấn trên bản sao bị huỷ

Bản sao đang chạy một truy vấn dài. Máy chính DELETE rồi VACUUM những dòng mà truy vấn đó cần đọc:

ERROR:  canceling statement due to conflict with recovery
DETAIL:  User query might have needed to see row versions that must be removed.

Truy vấn bị huỷ, không phải chậm — huỷ hẳn.

Bản sao phải phát lại WAL của máy chính, kể cả phần WAL nói "dọn những dòng này đi". Nhưng truy vấn đang chạy trên bản sao cần đúng những dòng đó. Hai việc không thể cùng đúng, và PostgreSQL chọn ưu tiên việc bám sát máy chính.

max_standby_streaming_delay quyết định nó đợi bao lâu trước khi huỷ. Mặc định 30 giây; tôi hạ xuống 2 giây để dựng lại hiện tượng trong thời gian đo.

Kiểm bằng:

select * from pg_stat_database_conflicts where datname = current_database();

Ở phép đo của tôi: confl_snapshot = 2.

Có hai cách xử lý, và cả hai đều có giá:

Tăng max_standby_streaming_delay. Truy vấn dài chạy được, nhưng bản sao tụt lại đúng bằng khoảng đó — nếu bạn cần nó để chuyển đổi khi sự cố, nó không sẵn sàng.

Bật hot_standby_feedback = on. Bản sao báo ngược cho máy chính rằng nó đang cần những phiên bản dòng nào, và máy chính không dọn chúng. Hết xung đột — nhưng máy chính giờ hành xử như đang có một giao dịch dài mở, và phần 21 đã đo hậu quả: bảng phình lên, VACUUM không dọn được gì.

Không có lựa chọn miễn phí. Cách thực dụng là tách bạch: bản sao dành cho báo cáo thì bật hot_standby_feedback và chấp nhận; bản sao dành cho chuyển đổi khi sự cố thì không cho ai truy vấn.

Kiểu hỏng 2: khe sao chép làm đầy đĩa máy chính

Đây là kiểu hỏng làm sập máy chính, và nó bắt nguồn từ chính cơ chế bảo vệ.

Tôi dừng máy dự phòng rồi chạy ba vòng ghi trên máy chính:

WAL bị giữ lại:   138 MB
thư mục pg_wal:   448 MB

Khe sao chép bảo đảm máy chính không xoá WAL mà bản sao chưa nhận. Đó là điều bạn muốn khi bản sao chỉ mất kết nối vài phút. Nhưng nếu bản sao chết hẳn và không ai để ý, máy chính giữ WAL cho tới khi hết đĩa — và một cơ sở dữ liệu hết đĩa thì dừng nhận ghi.

Tham số chặn chuyện đó:

show max_slot_wal_keep_size;
--  -1

-1 nghĩa là không giới hạn — mặc định. Đặt một con số thật:

alter system set max_slot_wal_keep_size = '10GB';

Khi khe vượt ngưỡng, PostgreSQL vô hiệu hoá nó và xoá WAL. Bản sao đó hỏng và phải dựng lại từ đầu — nhưng máy chính sống. Đó là đánh đổi đúng: mất bản sao còn hơn mất máy chính.

Theo dõi:

select slot_name, active, wal_status,
       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) as wal_giu_lai
from pg_replication_slots;

Cột wal_status là cột cảnh báo sớm: reserved là bình thường, extended là đã vượt max_wal_size, unreserved là sắp bị vô hiệu hoá, lost là đã mất.

khe không dùng nữa thì phải xoá:

select pg_drop_replication_slot('ten_khe');

Một khe bỏ quên từ lần thử nghiệm ba tháng trước vẫn đang giữ WAL, và không có gì nhắc bạn.

Theo dõi cái gì

Trên máy chính:

select application_name, state, sync_state,
       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), sent_lsn))   as chua_gui,
       pg_size_pretty(pg_wal_lsn_diff(sent_lsn, flush_lsn))              as chua_ghi,
       pg_size_pretty(pg_wal_lsn_diff(flush_lsn, replay_lsn))            as chua_ap_dung,
       write_lag, flush_lag, replay_lag
from pg_stat_replication;

Ba cột lag là ba giai đoạn khác nhau, và chúng nói ba chuyện khác nhau:

Cột lớn Nghĩa là
chua_gui Mạng chậm hoặc máy chính quá tải
chua_ghi Đĩa của máy dự phòng chậm
chua_ap_dung Máy dự phòng đang bận phát lại, hoặc đang đợi vì xung đột truy vấn

Cột thứ ba lớn thường đi kèm với max_standby_streaming_delay cao — đó là bản sao đang chờ một truy vấn dài kết thúc.

Thử ba mươi giây

select slot_name, active, wal_status,
       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) as wal_giu_lai
from pg_replication_slots;

show max_slot_wal_keep_size;

Hai câu hỏi:

  1. Có khe nào active = false không? Mỗi cái như vậy đang giữ WAL cho một bản sao không tồn tại.
  2. max_slot_wal_keep_size có phải -1 không? Nếu có, đĩa máy chính của bạn đang không có lưới an toàn nào.

Phần sau đo sao chép logic: nó khác sao chép luồng chỗ nào, chậm hơn bao nhiêu, và làm được gì mà sao chép luồng không làm được.