Phần trước đo WAL sinh ra bao nhiêu. Phần này đo chuyện xảy ra khi PostgreSQL phải đẩy tất cả xuống đĩa — và tìm ra rằng con số trung bình mà mọi hệ thống theo dõi hiển thị giấu hoàn toàn vấn đề.
Chỗ sụt
Tải ghi liên tục, tám kết nối, đo tps theo từng năm giây:
| Giây | tps | Độ trễ TB | Độ lệch chuẩn |
|---|---|---|---|
| 5 | 25.229 | 0,317 ms | 0,397 |
| 20 | 27.667 | 0,289 ms | 0,357 |
| 40 | 22.680 | 0,352 ms | 1,440 |
| 45 | 5.335 | 1,503 ms | 4,683 |
| 50 | 3.220 | 2,483 ms | 10,022 |
| 55 | 19.260 | 0,415 ms | 1,601 |
Từ 27.667 xuống 3.220 — chậm hơn tám lần trong mười giây.
Nhưng con số tổng kết của cả phiên là:
tps = 20.032 latency average = 0,399 ms
Hai con số đó trông hoàn toàn bình thường. Chúng giấu mất mười giây mà mọi người dùng thấy hệ thống đứng.
Đây là lý do theo dõi bằng giá trị trung bình không phát hiện được vấn đề này. Cột duy nhất báo động là độ lệch chuẩn: 0,357 ở lúc bình thường, 10,022 ở lúc sụt.
Log nói chính xác chuyện gì đã xảy ra
Với log_checkpoints = on:
LOG: checkpoint complete: wrote 14073 buffers (21.5%); 8 recycled;
write=0.965 s, sync=0.016 s, total=1.030 s;
distance=131054 kB, estimate=131077 kB
21,5% toàn bộ shared_buffers bị ghi xuống đĩa trong chưa tới một giây. Và trong log có ba dòng như vậy liên tiếp, cách nhau khoảng một giây.
Ba trường quan trọng nhất:
wrote N buffers (X%) — bao nhiêu trang bẩn phải ghi. Càng lớn thì cú sốc càng mạnh.
write= và sync= — thời gian ghi và thời gian đồng bộ xuống đĩa. Ở đây sync chỉ 0,016 s vì máy đo dùng SSD và bộ đệm hệ điều hành hấp thụ phần lớn; trên máy chủ có đĩa chậm, sync là chỗ số giây tích lại.
distance= — bao nhiêu WAL đã sinh ra kể từ checkpoint trước. Nếu nó xấp xỉ max_wal_size thì checkpoint đang bị ép chứ không phải theo lịch.
Ba cấu hình, cùng một tải
Đây là phần cho ra kết quả tôi không đoán trước.
| Cấu hình | tps TB | tps thấp nhất | tps cao nhất | Độ lệch | Số checkpoint |
|---|---|---|---|---|---|
max_wal_size 256MB, target 0,9 |
24.529 | 23.005 | 26.297 | 0,786 | 36 |
max_wal_size 256MB, target 0,1 |
20.729 | 4.004 | 28.737 | 2,665 | 3 |
max_wal_size 4GB, target 0,9 |
25.415 | 15.848 | 31.681 | 2,063 | 0 |
Cấu hình có nhiều checkpoint nhất — 36 lần — lại là cấu hình mượt nhất: tps thấp nhất 23.005, độ lệch chuẩn 0,786.
Cấu hình chỉ có ba checkpoint tụt xuống 4.004 tps, kém hơn năm lần.
Trực giác nói "ít checkpoint hơn thì ít gián đoạn hơn". Số đo nói ngược lại, và lý do nằm ở checkpoint_completion_target.
checkpoint_completion_target là nút quan trọng nhất
Tham số này nói: hãy trải việc ghi của một checkpoint ra bao nhiêu phần của khoảng cách tới checkpoint tiếp theo.
Với 0,9, PostgreSQL ghi từ tốn suốt 90% chu kỳ — mỗi giây chỉ một ít. Với 0,1, nó dồn toàn bộ vào 10% đầu rồi ngồi im.
Cùng lượng dữ liệu phải ghi, chỉ khác là trải ra hay dồn lại. Và cột "tps thấp nhất" cho thấy khác biệt: 23.005 so với 4.004.
Từ PostgreSQL 14, mặc định là 0,9. Trước đó nó là 0,5. Nếu bạn nâng cấp từ bản cũ và giữ nguyên postgresql.conf cũ, bạn vẫn đang chạy 0,5 — và đó là một dòng đáng sửa.
Vì sao max_wal_size lớn không tự động tốt hơn
Cấu hình 4 GB có 0 checkpoint trong suốt 45 giây đo, mà tps thấp nhất vẫn chỉ 15.848 — kém hơn cấu hình 256 MB với target 0,9.
Phần biến động còn lại đến từ tiến trình ghi nền và từ chính lần checkpoint tôi ép chạy trước khi đo. Điều đó nói lên rằng max_wal_size không phải nút chỉnh độ mượt — nó là nút chỉnh tần suất.
Và phần 46 đã đo mặt trái của việc đặt nó quá nhỏ: 128 MB gây 18 checkpoint và 29% nhiều WAL hơn so với 1 GB, vì mỗi checkpoint mở lại một đợt ghi trang đầy đủ.
Nên hai tham số này giải quyết hai vấn đề khác nhau:
| Tham số | Điều khiển | Đặt sai thì |
|---|---|---|
checkpoint_completion_target |
Việc ghi được trải ra hay dồn lại | Thông lượng sụt theo chu kỳ |
max_wal_size |
Bao lâu một lần checkpoint | Quá nhỏ: nhiều WAL hơn. Quá lớn: khôi phục lâu hơn |
Thứ tự nên thử
1. Kiểm checkpoint_completion_target. Phải là 0,9. Đây là thay đổi rẻ nhất, áp dụng ngay bằng pg_reload_conf(), và theo số đo ở trên nó là thứ quyết định độ mượt.
2. Nâng max_wal_size nếu checkpoints_req chiếm phần lớn. Với máy chủ ghi nhiều, 2–8 GB là dải hợp lý. Cái giá là thời gian khôi phục sau sự cố dài hơn.
3. Bật log_checkpoints. Mặc định là on từ PostgreSQL 15; ở bản cũ hơn thì phải bật tay. Không có nó, bạn không có cách nào biết checkpoint đang làm gì.
Có một nút thứ tư ít dùng: bgwriter_lru_maxpages và bgwriter_delay điều khiển tiến trình ghi nền, thứ dọn dần trang bẩn giữa các checkpoint. Chỉnh nó chỉ đáng khi ba bước trên đã làm xong và vẫn còn sụt.
Theo dõi cho đúng
Trung bình không đủ. Cần phân vị.
select
checkpoints_timed as theo_lich,
checkpoints_req as bi_ep,
round(100.0 * checkpoints_req / nullif(checkpoints_timed + checkpoints_req, 0), 1) as phan_tram_bi_ep,
round(checkpoint_write_time / 1000.0) as tong_thoi_gian_ghi_giay,
round(checkpoint_sync_time / 1000.0, 2) as tong_thoi_gian_sync_giay,
buffers_checkpoint, buffers_clean, buffers_backend
from pg_stat_bgwriter;
Ba dấu hiệu:
phan_tram_bi_ep trên 33% — max_wal_size quá nhỏ.
tong_thoi_gian_sync_giay lớn — đĩa không theo kịp. Đây là con số duy nhất trong bảng phản ánh đĩa vật lý thật, vì write thường được bộ đệm hệ điều hành hấp thụ (phần 44).
buffers_backend lớn so với buffers_checkpoint — các tiến trình phục vụ truy vấn đang phải tự ghi trang bẩn vì tiến trình nền không theo kịp. Đó là dạng tệ nhất, vì nó làm chậm trực tiếp truy vấn của người dùng.
Ở phía ứng dụng, hãy theo dõi phân vị 99 của độ trễ chứ không phải trung bình. Chỗ sụt ở đầu bài không xuất hiện trong con số trung bình 0,399 ms, nhưng nó chiếm trọn phân vị 99.
Thử ba mươi giây
show checkpoint_completion_target;
show log_checkpoints;
Nếu giá trị đầu không phải 0.9, đổi ngay:
alter system set checkpoint_completion_target = 0.9;
select pg_reload_conf();
Không cần khởi động lại, không có rủi ro, và theo số đo ở bài này nó là khác biệt giữa tps thấp nhất 23.005 và 4.004.
Phần sau đo fsync và synchronous_commit: mỗi mức độ bền tốn bao nhiêu, và bạn mất gì nếu hạ xuống.