Bài trước đo thấy phần lớn WAL "phình to" đến từ full-page image (FPI) — lần chạm trang đầu tiên sau mỗi checkpoint chép nguyên cả trang 8KB vào WAL. Câu hỏi tự nhiên tiếp theo: giảm chi phí đó thế nào? PostgreSQL cho hai công tắc liên quan trực tiếp — full_page_writes (nguồn gốc của FPI) và wal_compression (nén FPI). Bài này đo thật cả hai, và làm rõ vì sao một cái nên bật còn cái kia gần như không bao giờ nên tắt.

Hai công tắc, hai hướng ngược nhau

full_page_writes (mặc định on) chính là cơ chế tạo ra FPI. Khi bật, lần đầu sửa một trang sau checkpoint, PostgreSQL chép nguyên trang 8KB vào WAL để chống torn page — trường hợp máy sập lúc HĐH mới ghi được một nửa trang 8KB. Tắt nó đi thì wal_fpi = 0 và WAL nhỏ hẳn, nhưng mất luôn lá chắn chống torn page.

wal_compression (mặc định off) đi hướng ngược lại và an toàn hơn nhiều: nó giữ FPI (vẫn an toàn) nhưng nén chúng lại trước khi ghi vào WAL. Bạn giảm được kích thước WAL mà không hy sinh độ bền.

SHOW full_page_writes;   -- on
SHOW wal_compression;    -- off  (giá trị: pglz | lz4 | zstd | on | off)
ALTER SYSTEM SET wal_compression = 'zstd';   -- hoặc lz4 (nhanh hơn)
SELECT pg_reload_conf();

Ảnh chụp đoạn mã SQL nền tối minh hoạ wal_compression và full_page_writes hai công tắc của FPI, full_page_writes mặc định ON nguồn gốc của full-page image bài trước đã đo lần chạm trang đầu sau checkpoint chép nguyên 8KB vào WAL FPI để chống torn page trang bị ghi dở khi máy sập SHOW full_page_writes on tắt nó wal_fpi bằng 0 WAL nhỏ hẳn nhưng mất chống torn page ALTER SYSTEM SET full_page_writes off nguy hiểm gần như không bao giờ, wal_compression mặc định OFF giảm FPI một cách an toàn thay vì bỏ full-page image mất an toàn hãy nén chúng SHOW wal_compression off giá trị pglz lz4 zstd on off ALTER SYSTEM SET wal_compression zstd hoặc lz4 nhanh hơn SELECT pg_reload_conf FPI vẫn còn vẫn an toàn chỉ được nén lại trước khi ghi WAL, chọn thuật toán nén lz4 nhanh nhất CPU thấp nén vừa phải mặc định tốt nhất cho OLTP zstd nén mạnh nhất CPU cao hơn chút tốt khi I/O là nút cổ chai pglz bản cũ có sẵn mọi build yếu hơn lz4 mức nén phụ thuộc dữ liệu text lặp lại nén rất tốt dữ liệu ngẫu nhiên md5 ảnh đã nén gần như không nén được, vì sao gần như không bao giờ tắt full_page_writes nếu máy sập giữa lúc HĐH đang ghi một trang 8KB trang có thể rách nửa cũ nửa mới không có FPI trong WAL để sửa hỏng dữ liệu chỉ an toàn nếu tầng lưu trữ đảm bảo ghi 8KB nguyên tử rất hiếm vài SAN filesystem đặc thù nghi ngờ để ON

Hình 1: full_page_writes (on) tạo ra FPI để chống torn page; tắt nó bỏ FPI nhưng mất an toàn. wal_compression (off) nén FPI lại — giảm WAL mà vẫn giữ an toàn. Chọn lz4 (nhanh) hoặc zstd (nén mạnh).

Đo thật: nén giảm 17-30% tuỳ độ nén của dữ liệu

Tôi dựng lại một bảng mới hoàn toàn cho mỗi phép đo (để số FPI cố định, chênh lệch chỉ đến từ nén), rồi UPDATE toàn bảng 200k dòng ngay sau CHECKPOINT, đọc pg_stat_wal:

Ảnh chụp bảng kết quả đo thật nền tối nén WAL giảm 17-30 phần trăm tắt FPI giảm 35 phần trăm nhưng nguy hiểm pg_stat_wal UPDATE toàn bảng 200k dòng sau CHECKPOINT bảng dựng lại mới mỗi lần wal_fpi cố định PostgreSQL 16, wal_compression trên dữ liệu dễ nén text lặp lại thực tế wal_fpi 4170 cố định cả 4 lần chênh lệch chỉ do nén off 91 MB pglz 65 MB lz4 65 MB zstd 63 MB giảm khoảng 30 phần trăm FPI vẫn còn vẫn an toàn, wal_compression trên dữ liệu khó nén md5 gần ngẫu nhiên wal_fpi 3020 cố định cùng phép đo dữ liệu khác off 75 MB pglz 73 MB lz4 70 MB zstd 62 MB chỉ khoảng 17 phần trăm nén phụ thuộc độ nén của dữ liệu, bảng wal_compression off dữ liệu dễ nén 91 MB khó nén 75 MB lz4 65 MB 70 MB zstd 63 MB giảm 30 phần trăm 62 MB giảm 17 phần trăm, full_page_writes OFF bỏ hẳn FPI giảm nhiều nhất nhưng nguy hiểm cùng UPDATE 200k dòng dữ liệu dễ nén full_page_writes on wal_fpi 4171 91 MB full_page_writes off wal_fpi 0 59 MB giảm khoảng 35 phần trăm nhưng mất chống torn page máy sập có thể hỏng dữ liệu âm thầm đã khôi phục full_page_writes on ngay sau khi đo, kết luận giữ full_page_writes ON bật wal_compression full_page_writes tạo ra chi phí FPI cần cho an toàn wal_compression là cách lấy lại phần lớn chi phí đó mà không mất an toàn

Hình 2: Trên dữ liệu dễ nén (text lặp lại), wal_compression giảm WAL từ 91 MB xuống 63 MB (zstd, ~30%). Trên dữ liệu khó nén (md5), chỉ giảm còn 62 MB (~17%). full_page_writes=off bỏ hẳn FPI: 91→59 MB (~35%) nhưng mất an toàn.

Hai bộ số cho một bài học quan trọng: mức nén phụ thuộc hoàn toàn vào độ nén của dữ liệu.

  • Dữ liệu dễ nén (text lặp lại như tên, trạng thái, ghi chú mặc định): off 91 MB → lz4 65 MB → zstd 63 MB — giảm ~30%.
  • Dữ liệu khó nén (cột chứa md5, gần như ngẫu nhiên): off 75 MB → lz4 70 MB → zstd 62 MB — chỉ ~17%.

FPI chứa nội dung nguyên trang, nên nếu bảng của bạn toàn text/số lặp lại thì nén rất hiệu quả; nếu toàn dữ liệu ngẫu nhiên (hash, UUID, ảnh, dữ liệu đã nén) thì lợi ích khiêm tốn hơn. Dù vậy, zstd gần như luôn thắng cả hai trường hợp.

full_page_writes=off: giảm nhiều nhất, nhưng đừng

Để so sánh, tôi tắt full_page_writes và đo cùng UPDATE: wal_fpi tụt về 0, WAL từ 91 MB xuống 59 MB — giảm ~35%, nhiều hơn cả nén. Hấp dẫn? Đừng.

Lý do FPI tồn tại: nếu máy sập giữa lúc HĐH đang ghi một trang 8KB, trang có thể "rách" — nửa nội dung cũ, nửa mới. Không có FPI trong WAL để dựng lại nguyên trang, PostgreSQL không thể sửa trang rách đó → hỏng dữ liệu âm thầm, đúng loại lỗi tệ nhất. full_page_writes=off chỉ an toàn khi tầng lưu trữ đảm bảo ghi 8KB nguyên tử (một số SAN/filesystem đặc thù rất hiếm). Với 99% hệ thống, để on. Tôi đã khôi phục full_page_writes=on ngay sau khi đo.

Kết luận đẹp: full_page_writes tạo ra chi phí FPI (cần cho an toàn), còn wal_compression là cách lấy lại phần lớn chi phí đó mà không mất an toàn. Đó là lý do đường đi đúng là giữ full_page_writes=on và bật wal_compression.

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

wal_compression đổi I/O lấy CPU. Nén tốn CPU khi ghi và giải nén khi phục hồi/replay. Trên hệ thống I/O là nút cổ chai (ghi nặng, đĩa chậm, băng thông replication hẹp), đánh đổi này rất đáng. Trên hệ thống CPU đã căng mà I/O dư dả, cân nhắc kỹ hơn — nhưng lz4 có chi phí CPU thấp đến mức thường vẫn nên bật.

lz4 hay zstd? lz4 nhanh, CPU thấp, nén vừa — mặc định tốt cho OLTP nhạy độ trễ. zstd nén mạnh hơn (như đo thấy), CPU cao hơn chút — tốt khi mục tiêu là giảm tối đa WAL (băng thông replication, chi phí lưu trữ WAL archive). pglz là bản cũ, có sẵn mọi build nhưng yếu hơn lz4 gần như mọi mặt.

Lợi ích thật vượt xa kích thước WAL trên đĩa. WAL nhỏ hơn nghĩa là: ít băng thông replication, backup/archive nhỏ hơn, checkpoint recycle WAL dễ hơn, và phục hồi replay ít dữ liệu hơn. Với hệ thống có replica hoặc PITR, giảm 20-30% WAL là lợi ích lan tỏa đáng kể.

Ba ý mang về

  1. full_page_writes tạo ra FPI để chống torn page, wal_compression nén FPI để giảm WAL an toàn — hai công tắc đi ngược hướng: một cái là nguồn chi phí (cần cho an toàn), một cái là cách thu hồi chi phí mà vẫn giữ an toàn.
  2. Bật wal_compression giảm WAL 17-30% tuỳ độ nén dữ liệu: đo thật, dữ liệu text lặp lại giảm 30% (91→63 MB zstd), dữ liệu md5 gần ngẫu nhiên chỉ 17% (75→62 MB) — FPI vẫn còn nên vẫn an toàn; chọn lz4 (nhanh) hoặc zstd (nén mạnh).
  3. Gần như không bao giờ tắt full_page_writes: đo thật nó giảm 35% (91→59 MB, wal_fpi=0) nhưng bỏ hẳn chống torn page → máy sập có thể hỏng dữ liệu âm thầm, chỉ an toàn khi lưu trữ ghi 8KB nguyên tử (rất hiếm) — giữ on, bật wal_compression thay thế.

Phần sau ta xét một tối ưu ở tầng bộ nhớ hệ điều hành: Phần sau mổ xẻ huge pages — vì sao trang nhớ 2MB thay vì 4KB giảm áp lực lên TLB cho shared_buffers lớn, và cách bật.