Độ 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 độ bền, chi phí fsync mỗi commit, hai loại rủi ro, và cách đặt riêng

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ì onlocal 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.