bigywater.tech
数据编码与压缩 · 23

选型总结:一张决策表和每条结论的前提

前面二十二篇各量了一个方面。这篇把它们放回六个具体场景里,每个场景重新测一遍,给出赢家和适用的前提,以及什么时候什么都不该做。

106.5 对 109.1 字节
单条商品记录加 zstd 字典后,Protobuf 与 JSON 的平均字节(不压缩时 242.6 对 359.6)
988040 字节
2 万条商品批量归档,最小的组合:JSON Lines + xz-9(zstd-19 是 1047098,brotli q11 是 1015193)
117.0%
53 字节的短行用 zstd-3 压缩后相对原文的大小

单条小消息

实测 · 本机执行输出
  编码                  不压缩   zstd-3      +字典       编码+解码 µs    +zstd 字典 µs
  JSON(orjson)      359.6    288.6    109.1            1.2            3.6
  MessagePack       317.8    298.9    112.5            2.4            4.4
  Protobuf          242.6    232.7    106.5            4.1            6.5
  体积最小:Protobuf + 字典(106.5 字节);不压缩时体积最小:Protobuf(242.6);编码+解码最快(不压缩):JSON(orjson)(1.2 微秒)

不压缩时 Protobuf 小 33%;加了字典后三种编码只差 6%。有字典且追求最小体积用 Protobuf,追求编码解码速度和可读性用 JSON(orjson 编码加解码只要 1.2 微秒)。没有字典的小消息,压缩收益很低(289 对 360 字节)。

批量归档

实测 · 本机执行输出
  组合                                  字节 占 JSON Lines      压缩耗时
  JSON Lines + gzip-9            1460895        20.3%     154ms
  JSON Lines + zstd-19           1047098        14.5%    1980ms
  JSON Lines + brotli q11        1015193        14.1%    8094ms
  Protobuf 整批 + zstd-19          1081117        15.0%    1083ms
  JSON Lines + xz-9               988040        13.7%    2004ms
  体积最小:JSON Lines + xz-9(988040 字节)

写一次、很少读时,选高压缩比的压缩器:xz-9 988040、brotli q11 1015193、zstd-19 1047098,差距在 6% 以内。Protobuf 整批 + zstd-19 反而更大(1081117)。

批量热数据

实测 · 本机执行输出
  组合                                    字节      解压+解析
  JSON Lines + lz4                 2314121     59.4ms
  JSON Lines + zstd-3              1331194     59.9ms
  Protobuf 整批 + lz4                2298068      8.2ms
  Protobuf 整批 + zstd-3             1378051     10.8ms
  JSON Lines + zstd-3(orjson 解析)   1331194     23.0ms
  读得最快:Protobuf 整批 + lz4(8.2 ms);体积最小:JSON Lines + zstd-3(1331194 字节)

读得多时,看的是解压加解析:Protobuf + lz4 8.2 ms,Protobuf + zstd-3 10.8 ms,JSON(orjson)+ zstd-3 23.0 ms,JSON(标准库)+ zstd-3 59.9 ms。Python 里 JSON 的解析成本远大于解压。

HTTP

实测 · 本机执行输出
  编码                 字节    压缩 µs    解压 µs
  gzip-6           3878       97       11
  zstd-3           3898       23       10
  brotli q5        3764      153       20
  brotli q11       3048    10811       32
  现压缩里"体积 × 耗时"(越小越好)的第一名:zstd-3;体积最小:brotli q11

现场压缩的动态响应:zstd-3 的体积乘耗时最小,brotli q11 最小但每页 10.8 ms。静态文件预先压成 q11,动态响应用 gzip-6、zstd-3 或 brotli q4–q5,看客户端的支持。

缓存与队列

实测 · 本机执行输出
  方式                 平均字节    解压 µs/页
  原样                13907        0.0
  lz4                5861        3.9
  zstd-3             4013       10.5
  zstd-3 + 字典        2902       12.2
  内存最省:zstd-3 + 字典;解压最快(不含原样):lz4

内存是主要成本:zstd-3 加字典每页 2902 字节,原样 13907,省 79%;lz4 解压最快(3.9 微秒/页)。字典要版本化(第 17 篇)。

不要做的事

实测 · 本机执行输出
  随机 1 MiB                     原  1048576 → zstd-3  1048609( 100.0%)  ← 比原文还大
  已经 gzip 过的 1 MiB 文本          原   217641 → zstd-3   217656( 100.0%)  ← 比原文还大
  timeseries 的一行(54 字节)        原       53 → zstd-3       62( 117.0%)  ← 比原文还大
  50 字节随机串                     原       50 → zstd-3       59( 118.0%)  ← 比原文还大
  products 一条(360 字节)          原      358 → zstd-3      286(  79.9%)

随机、已压缩的数据压不动;几十字节的消息没有字典会变大(117%)。数据模型先于格式(第 20 篇),列式先于格式(第 8 篇),字典先于大量攒批(第 17 篇)。

分步演示

选型
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.8(系统 Python,自带 compression.zstd)/ zstandard 0.25.0 / lz4 4.4.5 / zstd、lz4、brotli、protoc 命令行(Homebrew);msgpack、protobuf、snappy 等第三方库临时装在临时目录里,用完删除;所有数据都是本机脚本生成的合成数据(固定随机种子),没有使用任何真实网站或用户的数据;计时用 time.perf_counter,取多次中位数,数字每次运行会有小幅抖动。orjson 3.13.0、msgpack 1.2.3、protobuf 7.36.2、brotli 1.2.0、lz4 4.4.5、zstandard 0.25.0、xz(lzma);每个场景重新测一遍(单条用 2000 条测试,批量用 2 万条,耗时是 3–5 次中位数,压缩 brotli q11 和 zstd-19 是单次);这是在这台机器、这套合成数据上的结果,不是通用规则;每个场景下的“赢家”依赖所列的前提
    • 没有验证:真实站点的数据(合成数据重复度可能偏高或偏低);其它语言和实现(Go、Java 的 Protobuf、JSON 解析速度);Avro、FlatBuffers、Cap'n Proto、Parquet 等本系列没覆盖的格式;多线程和分布式场景下的网络与调度开销;安全性问题(解压炸弹、Protobuf 的恶意输入),生产环境要另外评估
    京ICP备2026067502号-1京公网安备11010502063487号