编码和压缩是两件事,实际是叠加使用的。把 JSON、MessagePack、Protobuf 与 snappy、lz4、gzip、zstd、brotli 各种组合,在三个数据集上量体积和耗时,看看到底哪种组合在什么情况下最合适。
--- products 2 万条 --- 编码 不压缩 snappy lz4 gzip-6 zstd-3 zstd-19 brotli q5 brotli q11 JSON 7214163 2511955 2314042 1484671 1331144 1047909 1216613 1015149 MessagePack 6357308 2458222 2385707 1496373 1413136 1077896 1268320 1061068 Protobuf 4914350 2295420 2298068 1449113 1378051 1081117 1239033 1054849 最小的组合:JSON + brotli q11 = 1015149 字节(JSON 不压缩的 7214163 字节的 14.1%);JSON + zstd-3 = 1331144;Protobuf 不压缩 = 4914350
未压缩时 Protobuf 比 JSON 小 32%(products)、65%(timeseries)、76%(nested)。但压缩之后差距大幅缩小:products 上 zstd-3 的 Protobuf 反而比 JSON 大 3.5%。最小的组合都是 JSON 加高级别压缩(brotli q11 或 zstd-19)。
编码耗时:JSON(orjson) 5.7 ms、MessagePack 13.5 ms、Protobuf(从 dict 构造) 34.9 ms 编码+压缩 snappy lz4 gzip-6 zstd-3 zstd-19 brotli q5 brotli q11 | snappy lz4 gzip-6 zstd-3 zstd-19brotli q5brotli q11 ← 右边是 解压+解码(并完整访问 title 字段) JSON 14.6 16.4 97.3 19.5 1960.6 75.1 8501.7 | 49.6 45.4 48.7 47.0 46.3 49.0 50.8 MessagePack 22.6 24.8 126.3 29.4 1849.6 87.6 7503.2 | 37.2 34.8 37.9 36.9 33.2 37.8 38.3 Protobuf 42.6 44.1 131.9 49.6 1382.6 111.9 5623.5 | 10.7 9.1 11.9 11.4 10.5 15.9 14.4 (左边 = 编码 + 压缩 毫秒,JSON 用 orjson 编码;右边 = 解压 + 解码毫秒,JSON 用标准库 json.loads)
压缩级别的影响远大于编码格式:zstd-3 对 zstd-19 差约 100 倍,brotli q11 要 8 秒。编码本身,orjson 的 JSON 最快(5.7 ms),从 dict 构造 Protobuf 消息最慢(34.9 ms);解码 Protobuf 最快(9–16 ms)。
前沿:JSON + brotli q11 1015149 字节 8319.6 ms 前沿:JSON + zstd-19 1047909 字节 1843.6 ms 前沿:MessagePack + zstd-19 1077896 字节 1474.2 ms 前沿:Protobuf + zstd-19 1081117 字节 1241.7 ms 前沿:JSON + brotli q5 1216613 字节 79.4 ms 前沿:JSON + zstd-3 1331144 字节 21.0 ms 前沿:JSON + lz4 2314042 字节 16.9 ms 前沿:JSON + snappy 2511955 字节 14.8 ms (不在前沿上的组合,同样大或更大的体积至少有另一个组合比它更快;共 21 个组合,前沿上 8 个)
体积和耗时的前沿上,6 个点是 JSON(orjson 编码极快),只有 zstd-19 一档有 MessagePack 和 Protobuf。要的是小体积还是低延迟,先确定压缩档位,再看格式。
--- timeseries 5 万行 --- 编码 不压缩 snappy lz4 gzip-6 zstd-3 zstd-19 brotli q5 brotli q11 JSON 2744543 596771 649008 313607 352531 174010 276226 168309 MessagePack 2000003 569266 580243 321012 298689 246901 268467 218938 Protobuf 939568 554195 570575 304961 343932 235390 245860 230233 最小的组合:JSON + brotli q11 = 168309 字节(JSON 不压缩的 2744543 字节的 6.1%);JSON + zstd-3 = 352531;Protobuf 不压缩 = 939568
timeseries 的 Protobuf 未压缩比 JSON 小 65%,但 zstd-19 压缩后 JSON(174010)比 Protobuf(235390)小。数值有规律时文本的规律被压缩器吃得很干净。
--- nested 3000 个文档 --- 编码 不压缩 snappy lz4 gzip-6 zstd-3 zstd-19 brotli q5 brotli q11 JSON 5582052 1069231 1117578 553565 660737 339808 496377 340290 MessagePack 4062921 977264 1047168 546978 606310 340997 497111 350517 Protobuf 1366787 746981 679347 431831 440781 361178 405303 358818 最小的组合:JSON + zstd-19 = 339808 字节(JSON 不压缩的 5582052 字节的 6.1%);JSON + zstd-3 = 660737;Protobuf 不压缩 = 1366787
nested 上 Protobuf 在 zstd-3 下比 JSON 小 33%,但 zstd-19 下 JSON(339808)反超 Protobuf(361178)。压缩级别越高,差距越小。
编码 不压缩 snappy lz4 gzip-6 zstd-3 zstd-3+字典 (平均每条字节) JSON 359.6 355.1 371.1 274.3 288.6 109.1 MessagePack 317.8 322.6 340.3 289.3 298.9 112.5 Protobuf 242.6 246.3 264.0 231.2 232.7 106.5
每条记录独立处理(不能攒批)时,Protobuf 不压缩就比 JSON 小 33%,但加上 zstd 字典后三种编码只差 6%(106.5、109.1、112.5)。snappy 和 lz4 在单条上几乎没用。