Serialization và định dạng dữ liệu: đo thật

Loạt bài chuyên sâu về serialization cho lập trình viên: JSON, Protobuf, gob, MessagePack, CBOR, varint, nén, schema evolution, zero-copy. Mỗi bài đo THẬT kích thước byte và tốc độ mã hóa/giải mã bằng Go trong container, so sánh trung thực.

12/12 phần đã đăng Lập trình
1 JSON trong Go: tiện đến mức ta quên nó tốn gì — đo thật chi phí reflection encoding/json của Go tiện kinh khủng: một cặp Marshal/Unmarshal cho mọi struct. Nhưng nó dùng reflection lúc chạy nên có chi phí ẩn. Bài này chạy thật một đơn hàng 5 mặt hàng: đo kích thước JSON (643 byte gọn, 928 byte thụt lề), và benchmark cho thấy Unmarshal chậm ~4 lần Marshal với 27 lần cấp phát bộ nhớ so với 3 — vì giải mã phải dựng lại slice, string, struct con. 22/09/2026 · 6 phút đọc 2 Tối ưu JSON trong Go: cắt một nửa số lần cấp phát mà không đổi định dạng Phần 1 đo được encoding/json tốn nhiều cấp phát, nhất là khi giải mã. Bài này giữ nguyên JSON nhưng tối ưu cách dùng: json.Encoder/Decoder ghi đọc thẳng io tránh []byte trung gian, và json.RawMessage hoãn giải mã phần chưa cần. Đo thật: Encoder cắt 50% số lần cấp phát khi ghi 1000 object, RawMessage giảm 61% cấp phát khi chỉ cần field ngoài, Decoder streaming giảm 32% bộ nhớ đổi lấy ~10% thời gian. 22/09/2026 · 6 phút đọc 3 Protobuf wire format: mổ từng byte để hiểu vì sao nó gọn hơn JSON Protobuf gọn hơn JSON nhiều — nhưng vì sao? Bài này mổ xẻ wire format tới từng byte: mỗi field là một tag ghép field number với wire type, số nén bằng varint 7 bit. Dùng gói protowire của Go (không cần protoc) để tự mã hóa một sản phẩm, in ra 20 byte hex và giải thích từng byte là gì, rồi giải mã lại chứng minh. Cùng dữ liệu, JSON tốn 58 byte còn Protobuf chỉ 20. 22/09/2026 · 6 phút đọc 4 Protobuf vs JSON: cùng dữ liệu lên bàn cân — nhỏ hơn 2 lần, giải mã nhanh gần 10 lần Phần 3 đã thấy Protobuf gọn hơn qua từng byte. Bài này cân đầy đủ trên cùng một đơn hàng thật (nested + slice): đo kích thước lẫn tốc độ marshal/unmarshal. Kết quả đo được: Protobuf nhỏ hơn ~2 lần, và giải mã nhanh gấp ~9,7 lần JSON (283 vs 2743 ns) — lợi thế lớn nhất nằm ở giải mã, nơi JSON phải parse text và dùng reflection. 22/09/2026 · 6 phút đọc 5 encoding/gob: định dạng nhị phân của riêng Go, và cái bẫy khi chỉ gửi một giá trị gob là serialization gọn của Go — tự mô tả kiểu nên không cần schema ngoài như Protobuf. Nhưng nó có một đặc tính dễ gây bất ngờ: gửi một giá trị lẻ thì gob còn LỚN hơn JSON vì gói kèm mô tả kiểu. Bài này đo thật: 1 giá trị gob 443 byte vs JSON 398, nhưng stream 1000 giá trị thì gob chỉ 215 byte/giá trị (~nửa JSON); và Encoder tái dùng mã hóa chỉ 1 lần cấp phát, nhanh hơn JSON. 22/09/2026 · 6 phút đọc 6 MessagePack: JSON nhị phân — nhưng đo thật cho thấy nó không nhỏ như bạn tưởng MessagePack được quảng cáo là JSON nhị phân gọn hơn. Đúng, nhưng đo thật cho con số khiêm tốn hơn kỳ vọng: cùng một đơn hàng, MessagePack mặc định chỉ nhỏ hơn JSON 1,08 lần, bật compact ints được 1,25 lần — còn xa Protobuf (~2 lần). Lý do lộ ra khi nhìn byte: MessagePack vẫn lưu tên field trong map. Điểm mạnh thật của nó là giải mã nhanh ~2,4 lần JSON và đổi đúng một dòng code. 22/09/2026 · 6 phút đọc 7 CBOR: người anh em chuẩn hóa của MessagePack, và vì sao IETF cần thêm một định dạng CBOR (RFC 8949) gần như MessagePack: nhị phân, tự mô tả, cùng ý tưởng byte format. Vậy sao IETF lại chuẩn hóa thêm một định dạng? Bài này đo thật: cùng đơn hàng, CBOR 339 byte nhỏ hơn cả MessagePack 392 và JSON 440, mã hóa nhanh nhất trong ba. Nhưng khác biệt lớn nhất là CBOR có tag ngữ nghĩa và là chuẩn IETF — nền tảng của WebAuthn, COSE, CWT, IoT. 22/09/2026 · 6 phút đọc 8 Varint và ZigZag: vì sao số -1 tốn 10 byte, và mẹo đưa nó về 1 byte Varint là kỹ thuật nền tảng khiến Protobuf và CBOR gọn: số nhỏ tốn ít byte. Nhưng nó có một cái bẫy đo được: mọi số ÂM trong varint thuần đều tốn 10 byte, kể cả -1. ZigZag sửa điều đó bằng cách ánh xạ đan xen âm-dương. Bài này đo thật số byte bằng Go: -1 từ 10 byte xuống 1, -1000 từ 10 xuống 2 — và giải thích công thức. 22/09/2026 · 6 phút đọc 9 Nén + serialize: sự thật bất ngờ khi bạn gzip một JSON và một Protobuf Protobuf nhỏ hơn JSON ~2 lần — ai cũng biết. Nhưng nếu bạn vốn đã gzip dữ liệu trước khi gửi thì sao? Bài này đo thật 1000 đơn hàng: JSON nén gzip xuống 11,6 lần, Protobuf chỉ 5,1 lần (vì đã gọn, ít phần lặp). Kết quả bất ngờ: JSON+gzip (33KB) không chỉ nhỏ hơn Protobuf thô (188KB) mà còn nhỏ hơn cả Protobuf+gzip — nén xóa gần hết lợi thế kích thước của Protobuf. 22/09/2026 · 5 phút đọc 10 Schema evolution: thêm field mà không làm sập dịch vụ đang chạy phiên bản cũ Trong hệ phân tán, code cũ và mới chạy song song lúc triển khai — dữ liệu phải đọc chéo được cả hai chiều. Bài này chạy thật bằng Protobuf: code mới ghi 4 field, code cũ đọc vẫn lấy được 2 field và bỏ qua field lạ không crash; code cũ ghi 2 field, code mới đọc field mới nhận default. Và một cái bẫy đo được: đổi kiểu field làm mất dữ liệu âm thầm. Kèm luật an toàn vs nguy hiểm. 22/09/2026 · 6 phút đọc 11 Zero-copy serialization: đọc một field trong 0,25 nano giây, không cấp phát JSON và Protobuf phải giải mã cả bản ghi thành struct trước khi đọc một field — lãng phí khi bạn chỉ cần vài field trong message lớn. Zero-copy (FlatBuffers, Cap'n Proto) đọc thẳng field từ buffer bằng offset, không giải mã, không cấp phát. Bài này đo thật bằng Go: đọc một trường mất 0,25 ns và 0 lần cấp phát, so với 1524 ns và 9 lần khi phải unmarshal cả bản ghi — nhanh hơn khoảng 6000 lần. 22/09/2026 · 6 phút đọc 12 Chọn định dạng serialization nào? Tổng kết 12 phần bằng một bảng đo thật Khép lại loạt Serialization: điểm lại JSON, Protobuf, gob, MessagePack, CBOR, zero-copy — mỗi cái mạnh ở đâu — rồi chạy thật cả năm trên cùng một đơn hàng để so kích thước, tốc độ mã hóa/giải mã và cấp phát. Kết quả đo được: Protobuf nhỏ nhất (205 byte) và giải mã nhanh nhất, JSON dễ đọc nhất nhưng giải mã chậm nhất (2793 ns). Kèm cây quyết định và lời khuyên khi nào cứ JSON là đủ. 22/09/2026 · 6 phút đọc