Độ bền dữ liệu có giá, và giá đó đo được. Phần này đo bốn mức, và tìm ra rằng tham số nguy hiểm nhất cũng là tham số vô ích nhất.
Bốn mức, tám kết nối
| Cấu hình | tps | Độ trễ | So với mặc định |
|---|---|---|---|
| Mặc định (cả hai bật) | 25.645 | 0,312 ms | 1,00× |
synchronous_commit = off |
87.714 | 0,091 ms | 3,42× |
fsync = off (nguy hiểm) |
78.300 | 0,102 ms | 3,05× |
| Cả hai tắt (rất nguy hiểm) | 83.403 | 0,096 ms | 3,25× |
Kết quả quan trọng nhất nằm ở dòng thứ ba: tắt fsync không nhanh hơn tắt synchronous_commit — nó còn chậm hơn một chút.
Và dòng thứ tư cho thấy tắt cả hai cũng không hơn tắt riêng synchronous_commit.
Nghĩa là: fsync = off không mua được gì mà bán đi rất nhiều. Đó là một tham số không có trường hợp sử dụng hợp lý trên hệ thống thật.
Giá của một lần fsync
Một kết nối, không tranh chấp, để đo trực tiếp độ trễ mỗi lần commit:
| Cấu hình | tps | Độ trễ |
|---|---|---|
fsync=on, synchronous_commit=on |
4.910 | 0,204 ms |
fsync=on, synchronous_commit=off |
12.283 | 0,081 ms |
fsync=off, synchronous_commit=on |
12.043 | 0,083 ms |
0,204 − 0,081 = 0,123 ms
Đó là giá của độ bền, cho mỗi lần commit, trên máy đo này với SSD. Trên đĩa quay nó là vài mili giây; trên đĩa mạng có thể còn hơn.
Con số 0,123 ms giải thích cả bảng đầu tiên: với 8 kết nối, mỗi kết nối tiết kiệm 0,123 ms mỗi giao dịch thì tổng cộng ra 3,42 lần thông lượng.
Nó cũng giải thích vì sao chèn từng dòng một giao dịch chậm đến thế ở phần 37 — 7.278 dòng mỗi giây chính là giới hạn của việc fsync một triệu lần.
Hai tham số, hai loại rủi ro hoàn toàn khác nhau
Đây là điểm mà tôi nghĩ nhiều người gộp làm một, và chúng không giống nhau chút nào.
synchronous_commit = off: mất giao dịch gần nhất, cơ sở dữ liệu vẫn nhất quán.
PostgreSQL vẫn ghi WAL, vẫn fsync — nó chỉ không đợi việc đó xong trước khi báo commit thành công cho ứng dụng. Nếu máy mất điện, các giao dịch chưa kịp xuống đĩa biến mất.
Cửa sổ mất dữ liệu bị chặn bởi wal_writer_delay, mặc định 200 ms. Tiến trình ghi WAL đẩy dữ liệu xuống đĩa mỗi 200 ms, nên bạn mất tối đa khoảng ba lần con số đó.
Điều quan trọng: cơ sở dữ liệu vẫn nhất quán. Nó khởi động lại bình thường, mọi dữ liệu còn lại đọc được, không có gì hỏng. Bạn mất vài trăm mili giây cuối, không mất tính toàn vẹn.
fsync = off: cơ sở dữ liệu có thể hỏng.
Không có gì bảo đảm thứ tự ghi giữa WAL và tệp dữ liệu. Sau mất điện, chỉ mục có thể trỏ vào dòng không tồn tại, trang có thể rách nửa chừng, và cơ chế khôi phục không sửa được vì chính WAL cũng có thể chưa xuống đĩa.
Không có cách khôi phục ngoài bản sao lưu.
Tôi có thử dựng phép đo cho hiện tượng này — giết tiến trình bằng SIGKILL rồi đếm số giao dịch đã được xác nhận nhưng biến mất. Tôi không dựng được phép đo đáng tin: vòng lặp ghi của tôi vẫn tiếp tục sau tín hiệu giết, nên số liệu trước và sau không so được. Trong cả hai lần thử, cơ sở dữ liệu khởi động lại bình thường và không hỏng — nhưng đó không phải bằng chứng cho tính an toàn, chỉ là một lần thử không đủ khắc nghiệt.
Tôi ghi lại chỗ này thay vì bịa một con số, vì đây đúng là loại kết luận mà một phép đo tồi sẽ làm hại.
Cách dùng đúng: đặt riêng cho từng giao dịch
Đừng đổi giá trị toàn cục. Đặt cho đúng những giao dịch không cần độ bền:
begin;
set local synchronous_commit = off;
insert into nhat_ky_truy_cap values (...);
commit;
Đo được: trong giao dịch nó là off, sau commit nó tự trở lại on. Không có cách nào quên trả lại.
Đây là công cụ chính xác cho một tình huống rất thực: bảng nhật ký, bảng đo lường, bảng phiên đăng nhập — những nơi mất 200 ms cuối cùng chẳng ai quan tâm — trong khi bảng đơn hàng và bảng giao dịch tài chính vẫn giữ độ bền đầy đủ.
Đặt theo vai cũng được:
alter role ghi_log set synchronous_commit = off;
Năm mức, không phải hai
synchronous_commit không phải bật/tắt. Nó có năm giá trị:
| Mức | Đợi tới đâu |
|---|---|
off |
Không đợi gì — báo thành công ngay |
local |
Đợi WAL xuống đĩa máy này, không đợi máy dự phòng |
remote_write |
Đợi máy dự phòng nhận được |
on |
Đợi máy dự phòng ghi xuống đĩa (mặc định) |
remote_apply |
Đợi máy dự phòng đã áp dụng xong |
Ba mức cuối chỉ có ý nghĩa khi bạn có sao chép đồng bộ. Không có synchronous_standby_names thì on và local giống hệt nhau.
Mức local là lựa chọn ít được biết mà rất hữu ích: giữ nguyên độ bền trên máy chính, chỉ bỏ phần đợi máy dự phòng. Với replica ở trung tâm dữ liệu khác, phần đợi đó có thể là hàng chục mili giây.
remote_apply là mức duy nhất bảo đảm rằng nếu bạn đọc từ replica ngay sau khi ghi thành công, bạn thấy dữ liệu vừa ghi. Nó cũng là mức đắt nhất.
wal_sync_method
Cách PostgreSQL yêu cầu hệ điều hành đẩy dữ liệu xuống đĩa. Trên máy đo của tôi mặc định là fdatasync, và có bốn lựa chọn: fsync, fdatasync, open_sync, open_datasync.
Đây là tham số hiếm khi đáng chỉnh. fdatasync là mặc định đúng trên Linux. Công cụ pg_test_fsync đi kèm PostgreSQL đo được từng phương thức trên chính đĩa của bạn nếu bạn muốn kiểm.
Bảng quyết định
| Dữ liệu | Nên đặt |
|---|---|
| Đơn hàng, thanh toán, tài khoản | Mặc định — đừng đụng vào |
| Nhật ký truy cập, đo lường, phiên | set local synchronous_commit = off |
| Bảng trung gian trong ETL | Bảng UNLOGGED (phần 37) — rẻ hơn nhiều |
| Môi trường phát triển trên máy cá nhân | fsync = off chấp nhận được |
| Bất cứ thứ gì trên máy chủ thật | Không bao giờ fsync = off |
Dòng thứ ba đáng nhấn mạnh. Phần 37 đã đo bảng UNLOGGED sinh 40 byte WAL thay vì 72 MB — tốt hơn nhiều so với việc hạ độ bền toàn cục, và rủi ro thì rõ ràng và có giới hạn: bảng đó bị cắt trắng sau sự cố, các bảng khác không sao.
Thử ba mươi giây
select name, setting, source from pg_settings
where name in ('fsync','synchronous_commit','wal_sync_method','wal_writer_delay');
Nếu fsync không phải on, hãy tìm xem ai đặt nó và vì sao — cột source cho biết nó đến từ tệp cấu hình hay từ dòng lệnh. Theo số đo ở bài này, đổi nó về on và thay bằng synchronous_commit = off cho ra thông lượng cao hơn với rủi ro thấp hơn hẳn.
Và tìm những bảng đáng hạ độ bền:
select relname, n_tup_ins
from pg_stat_user_tables
order by n_tup_ins desc limit 10;
Bảng nào ở đầu danh sách mà nội dung là nhật ký hoặc số liệu đo lường thì đó là ứng viên cho set local synchronous_commit = off, hoặc cho bảng UNLOGGED.
Phần sau đo sao chép luồng: độ trễ thật giữa máy chính và máy dự phòng, và chuyện gì xảy ra khi replica tụt lại quá xa.