Đây là nút chỉnh mà mọi lập trình viên đều gặp và ít ai đo: mức nén (compression level). Bạn gõ gzip -9 vì nghĩ "số to = tốt nhất", hoặc để zstd -19 cho "nén tối đa". Nhưng con số đó không miễn phí — và thường bạn đang trả một cái giá khổng lồ về thời gian để đổi lấy một chút xíu dung lượng. Sự thật là: quan hệ giữa mức nén và tỉ lệ nén không tuyến tính — nó tuân theo luật lợi ích giảm dần (diminishing returns), và điểm cân bằng hầu như không bao giờ là mức cao nhất. Bài này (phần 6 loạt Nén) đo thật gzip và zstd trên cùng một khối dữ liệu 11MB để bạn thấy đường cong đó bằng số, và biết chọn mức nào.
Level đổi công sức, không đổi thuật toán
Điều quan trọng đầu tiên: gzip -1 tới -9 (và zstd -1 tới -19) vẫn chạy cùng một thuật toán (DEFLATE / zstd). Cái thay đổi là bao nhiêu công sức bộ tìm khớp bỏ ra:
- Level thấp: tìm khớp nhanh — chỉ cần khớp "đủ tốt", không cố tìm khớp dài nhất, cửa sổ tìm hẹp hơn. Nhanh, nhưng bỏ lỡ nhiều cơ hội nén.
- Level cao: tìm khớp kỹ lưỡng — cố tìm khớp dài nhất, thử nhiều vị trí hơn, đôi khi "nhìn xa" hơn. Nén tốt hơn, nhưng chậm hơn nhiều.
Và một điểm ít người biết: giải nén thì giống nhau ở mọi level. Level chỉ ảnh hưởng lúc nén; file nén ở -1 hay -9 đều giải nén nhanh như nhau. Điều này rất quan trọng cho quyết định "nén một lần, đọc nhiều lần" ở cuối bài.

Hình 1: Level không đổi thuật toán mà đổi công sức tìm khớp (thấp = nhanh/nén ít, cao = chậm/nén nhiều); giải nén giống nhau mọi level; đo cả size lẫn time từng level để thấy lợi ích giảm dần, rồi chọn theo cách dùng thay vì mặc định -9.
Đo thật: đường cong lợi ích giảm dần
Mình tạo 11MB log truy cập mô phỏng (có cấu trúc, biến thiên vừa phải như log thật), nén ở mọi level và đo cả kích thước lẫn thời gian:

Hình 2: Chạy thật trên 11MB — gzip level 1-9: L1 2065KB/27ms, L6 1503KB/83ms (7.3x), L9 1395KB/289ms (7.9x); zstd -1 1758KB/20ms, -9 1343KB/92ms (8.2x), -19 1116KB/3303ms (9.8x); mức cao đổi thời gian gấp hàng chục lần lấy vài % dung lượng.
- gzip: từ -6 lên -9 gần như vô nghĩa: level 1 cho 2065KB trong 27ms. Lên level 6 (mặc định), xuống 1503KB trong 83ms — nén tốt hơn nhiều (5.3x → 7.3x), thời gian tăng vừa phải (3x). Nhưng từ level 6 lên level 9: kích thước chỉ giảm từ 1503KB xuống 1395KB — nhỏ hơn ~7% — trong khi thời gian nhảy từ 83ms lên 289ms (chậm ~3.5 lần). Bạn trả gấp 3.5 lần thời gian cho 7% dung lượng. Với hầu hết trường hợp, đó là món hời tồi.
- zstd: còn kịch tính hơn ở cuối thang: zstd -1 đã cho 6.2x trong 20ms (nhanh hơn cả gzip -9!). -9 cho 8.2x trong 92ms — điểm hợp lý. Nhưng lên -19: tỉ lệ tăng từ 8.2x lên 9.8x (nhỏ hơn ~17%) trong khi thời gian nhảy từ 92ms lên 3303ms — chậm gấp 36 lần. Cùng một luật lợi ích giảm dần, chỉ là đuôi dốc hơn hẳn.
- Điểm cân bằng gần đầu thang, không phải cuối: cả hai đường cong đều cho thấy phần lớn giá trị nằm ở những mức đầu (1→6 với gzip, 1→9 với zstd), còn những mức cuối chỉ vắt kiệt vài phần trăm cuối bằng cái giá thời gian tăng vọt. Mặc định gzip là -6 và zstd là -3 chính vì lý do này — chúng được chọn làm điểm cân bằng, không phải điểm nén tối đa.
Đánh đổi cần cân nhắc
"Nén một lần, đọc nhiều lần" là khi mức cao mới đáng. Vì giải nén nhanh như nhau ở mọi level, nếu bạn nén một asset tĩnh (file JS/CSS phát hành cho hàng triệu lượt tải), một bản phát hành, hay một backup lưu trữ lâu dài — chi phí nén cao trả một lần, còn dung lượng nhỏ hơn tiết kiệm băng thông/đĩa mỗi lần đọc. Ở đây -9/-19 (thậm chí các công cụ chuyên như zopfli, brotli -11) đáng đồng tiền. Đây là ngoại lệ hợp lý của quy tắc "đừng dùng mức cao nhất".
Nén theo luồng, realtime thì tốc độ quan trọng hơn dung lượng. Ngược lại, khi nén log đang chảy, response HTTP động, dữ liệu qua đường ống thời gian thực — mỗi mili-giây nén làm tăng độ trễ. Ở đây mức thấp (gzip -1, zstd -1) hoặc thuật toán nhanh (LZ4, phần 7) là lựa chọn đúng: nén "đủ" để giảm băng thông mà không thành nút cổ chai. Nhiều CDN và proxy nén HTTP động ở mức thấp chính vì điều này.
Đo trên dữ liệu của bạn — đường cong khác nhau theo dữ liệu. Các con số trong bài là cho log mô phỏng; dữ liệu của bạn (JSON, ảnh, nhị phân, text tiếng Việt...) có đường cong khác. Đừng chép con số của người khác — chạy thử vài level trên mẫu thật, vẽ size vs time, rồi chọn. Việc này mất vài phút và cứu bạn khỏi vừa chậm vừa chẳng nén hơn bao nhiêu (như bài profiling ở loạt Debug: đo trước khi tối ưu).
Ba ý mang về
- Level tuân theo luật lợi ích giảm dần: đo thật gzip từ -6 lên -9 chỉ nhỏ hơn ~7% nhưng chậm ~3.5 lần; zstd từ -9 lên -19 nhỏ hơn ~17% nhưng chậm ~36 lần — phần lớn giá trị nằm ở các mức đầu, không phải mức cuối.
- Mặc định (-6 gzip, -3 zstd) là điểm cân bằng có chủ đích: level đổi công sức tìm khớp chứ không đổi thuật toán, và giải nén nhanh như nhau ở mọi level; đừng mặc định gõ -9/-19 nghĩ "tốt nhất".
- Chọn mức theo cách dùng, đo trên dữ liệu của bạn: nén một lần đọc nhiều lần (asset tĩnh, backup) thì mức cao đáng; nén realtime/luồng thì mức thấp; và luôn đo size vs time trên mẫu thật vì đường cong phụ thuộc dữ liệu.
Nguồn
- man7.org — gzip(1) (
-1..-9): https://man7.org/linux/man-pages/man1/gzip.1.html - Facebook — zstd (levels, benchmarks): https://github.com/facebook/zstd
- Python docs — zlib.compress (level): https://docs.python.org/3/library/zlib.html
Phần sau ta không chỉ chỉnh level mà đổi hẳn thuật toán: gzip vs bzip2 vs xz vs zstd — đo thật tỉ lệ nén và tốc độ của cả bốn trên cùng dữ liệu để biết khi nào chọn cái nào.