单 AZ · Kafka 4.1.2 · zstd on vs off · 三架构横评
Kafka zstd 压缩基准测试报告
回答四个问题:zstd 的延迟成本、吞吐代价、两端 CPU 开销落在哪一侧、以及跨 CPU 架构是否有差异。 全部数据来自 ap-northeast-1 真机(r8g Graviton4 / r8a AMD EPYC / r8i Intel,均 .8xlarge、单 AZ、KRaft、RF=3、acks=all), 每 cell 重复 3 次取中位数,执行顺序随机化,含 1 轮预热丢弃。
0 ms
固定速率下 zstd 的 p99 延迟增量(三机型一致)
−40~53%
broker CPU 下降(H1 证实且为负增量)
7.2×
真实业务 JSON 压缩比(合成随机仅 1.26×)
5%
zstd 吞吐跨三架构极差(none 组为 44%)
三条可带走的结论
① 不饱和运行时,zstd 的延迟成本为零。三机型、两压缩,固定速率下 p50/p95/p99 完全一致。 「压缩会增加延迟」只在接近饱和时成立 —— 那时增加的是排队延迟,不是压缩计算延迟。
② 压缩 CPU 全部落在 producer,broker CPU 反而下降(−40%~−53%)。 因为 Kafka 是 batch 级压缩:producer 压一次 → broker 原样落盘与复制(不重压)→ consumer 才解压。 字节量砍到 1/7 后,broker 的内核态开销(网络栈/CRC/page cache 拷贝)大幅减少,
③ zstd 吞吐存在架构无关瓶颈,加 CPU 无效。三种架构、物理核差一倍,zstd 吞吐全部收敛到同一区间。 要提吞吐应加 producer 实例数或调大 batch/linger,而不是换更强的 CPU。
① 不饱和运行时,zstd 的延迟成本为零。三机型、两压缩,固定速率下 p50/p95/p99 完全一致。 「压缩会增加延迟」只在接近饱和时成立 —— 那时增加的是排队延迟,不是压缩计算延迟。
② 压缩 CPU 全部落在 producer,broker CPU 反而下降(−40%~−53%)。 因为 Kafka 是 batch 级压缩:producer 压一次 → broker 原样落盘与复制(不重压)→ consumer 才解压。 字节量砍到 1/7 后,broker 的内核态开销(网络栈/CRC/page cache 拷贝)大幅减少,
sys% 降幅是直接证据。③ zstd 吞吐存在架构无关瓶颈,加 CPU 无效。三种架构、物理核差一倍,zstd 吞吐全部收敛到同一区间。 要提吞吐应加 producer 实例数或调大 batch/linger,而不是换更强的 CPU。
实验 A · 固定速率延迟(zstd 的延迟成本)
为什么必须固定速率:饱和态下不同压缩跑出不同吞吐,测到的是排队延迟而非服务延迟,横向比较无效。
本实验各组跑同一速率(= min(各组饱和吞吐) × 50%,见 manifest),实测偏差 < 0.02%,此时延迟差才归因于压缩本身。
| 机型 | 架构 | 物理核 | 固定速率 (msg/s) | compression | p50 ms | p95 ms | p99 ms | 样本 |
|---|---|---|---|---|---|---|---|---|
| r8g | Graviton4 (arm64) | 32 | 233,863 | none | 2 | 3 | 4 | n=3 |
| r8g | Graviton4 (arm64) | 32 | 233,863 | zstd | 2 | 3 | 4 | n=3 |
| r8g p99 判定 | none 全距 4~4 / zstd 4~4 | 无显著差异 | ||||||
| r8a | AMD EPYC (x86_64) | 32 | 247,843 | none | 2 | 3 | 4 | n=3 |
| r8a | AMD EPYC (x86_64) | 32 | 247,843 | zstd | 2 | 3 | 4 | n=3 |
| r8a p99 判定 | none 全距 4~4 / zstd 4~4 | 无显著差异 | ||||||
| r8i | Intel (x86_64) | 16 | 231,203 | none | 3 | 5 | 6 | n=3 |
| r8i | Intel (x86_64) | 16 | 231,203 | zstd | 3 | 5 | 6 | n=3 |
| r8i p99 判定 | none 全距 6~6 / zstd 6~6 | 无显著差异 |
实验 B · 饱和吞吐(zstd 的吞吐代价)
本表延迟列已被刻意省略:饱和态各组吞吐不同,其延迟为排队延迟,
脚本已在数据层标记
latency-NOT-comparable-saturated,禁止进入延迟结论。| 机型 | 架构 | 物理核 | none (msg/s) 中位 / 全距 | zstd (msg/s) 中位 / 全距 | zstd/none | zstd tps/物理核 | 样本 |
|---|---|---|---|---|---|---|---|
| r8g | Graviton4 (arm64) | 32 | 910,022 909,091~912,201 | 475,992 473,233~476,815 | 52% | 14,875 | n=3 |
| r8a | AMD EPYC (x86_64) | 32 | 1,205,364 1,152,572~1,257,862 | 495,203 490,587~496,802 | 41% | 15,475 | n=3 |
| r8i | Intel (x86_64) | 16 | 1,310,187 1,285,967~1,339,360 | 470,976 470,284~477,897 | 36% | 29,436 | n=3 |
★ 本次最重要的发现:zstd 存在架构无关瓶颈
三种不同架构、物理核数差一倍(16 vs 32),zstd 吞吐全部收敛到同一区间。 若瓶颈是 CPU 算力,32 核应接近 16 核的两倍 —— 实测是持平。 → 瓶颈在压缩路径的串行段(zstd-jni 的 JNI 边界 / per-batch 压缩不可并行),加核无效。
工程含义:想提升 zstd 吞吐,应增加 producer 实例数(更多并行压缩流)或调大 batch/linger 让单次压缩摊薄更多消息,而不是换更强 CPU。
none 组跨机型极差 44%(910,022 ~ 1,310,187),
而 zstd 组极差仅 5%(470,976 ~ 495,203)。三种不同架构、物理核数差一倍(16 vs 32),zstd 吞吐全部收敛到同一区间。 若瓶颈是 CPU 算力,32 核应接近 16 核的两倍 —— 实测是持平。 → 瓶颈在压缩路径的串行段(zstd-jni 的 JNI 边界 / per-batch 压缩不可并行),加核无效。
工程含义:想提升 zstd 吞吐,应增加 producer 实例数(更多并行压缩流)或调大 batch/linger 让单次压缩摊薄更多消息,而不是换更强 CPU。
实验 D · 两端 CPU 开销(压缩 CPU 落在哪一侧)
Kafka 是 batch 级压缩:producer 压一次 → broker 原样落盘与复制(
compression.type=producer 保证不重压)→ consumer 才解压。
所以 CPU 必须在 producer / consumer / broker 三处同时采,缺一不可证伪 H1「broker 压缩 CPU ≈ 0」。
采集口径为 /proc/<pid>/stat 累积计数器差值(不受采样丢峰影响),归一化成「每 GB 的 CPU 秒」使跨速率可比。真实业务 JSON
字段名/枚举高度重复,熵较低 → 可压。固定速率 230,000 msg/s,两组处理相同条数 → CPU 严格可比。
| 机型 | 角色 | none usr/sys % | none 合计 % | zstd usr/sys % | zstd 合计 % | Δ 合计 | CPU 秒/GB (none→zstd) |
|---|---|---|---|---|---|---|---|
| r8g | producer | 13.29 / 1.39 | 14.68 | 29.15 / 0.63 | 29.78 | +103% | 2.40 → 4.87 |
| r8g | consumer | 5.46 / 1.68 | 7.13 | 9.76 / 0.52 | 10.28 | +44% | 1.17 → 1.68 |
| r8g | broker | 4.44 / 6.29 | 10.73 | 3.47 / 1.61 | 5.09 | -53% ← H1 证实 | 1.75 → 0.83 |
| r8a | producer | 9.33 / 1.01 | 10.35 | 27.40 / 0.21 | 27.61 | +167% | 1.69 → 4.51 |
| r8a | consumer | 2.66 / 1.19 | 3.85 | 5.33 / 0.33 | 5.66 | +47% | 0.63 → 0.93 |
| r8a | broker | 4.41 / 4.82 | 9.23 | 4.13 / 1.41 | 5.53 | -40% ← H1 证实 | 1.51 → 0.91 |
| r8i | producer | 11.73 / 2.43 | 14.16 | 30.81 / 0.99 | 31.80 | +125% | 2.31 → 5.20 |
| r8i | consumer | 0.56 / 0.06 | 0.62 | 0.39 / 0.07 | 0.46 | -26% | 0.10 → 0.07 |
| r8i | broker | 7.67 / 6.26 | 13.93 | 5.39 / 2.05 | 7.44 | -47% ← H1 证实 | 2.28 → 1.22 |
合成随机数据(对照)
可打印字节均匀分布,熵接近上限 → 基本不可压。固定速率 230,000 msg/s,两组处理相同条数 → CPU 严格可比。
| 机型 | 角色 | none usr/sys % | none 合计 % | zstd usr/sys % | zstd 合计 % | Δ 合计 | CPU 秒/GB (none→zstd) |
|---|---|---|---|---|---|---|---|
| r8g | producer | 12.98 / 1.26 | 14.24 | 59.09 / 1.24 | 60.33 | +324% | 2.34 → 9.90 |
| r8g | consumer | 5.66 / 1.48 | 7.14 | 14.52 / 1.39 | 15.90 | +123% | 1.17 → 2.61 |
| r8g | broker | 4.37 / 6.20 | 10.57 | 6.68 / 5.36 | 12.03 | +14% | 1.73 → 1.97 |
| r8a | producer | 11.81 / 1.11 | 12.92 | 52.56 / 0.98 | 53.54 | +314% | 2.12 → 8.78 |
| r8a | consumer | 2.72 / 1.21 | 3.92 | 7.40 / 0.78 | 8.18 | +109% | 0.64 → 1.34 |
| r8a | broker | 4.53 / 5.30 | 9.83 | 6.46 / 4.01 | 10.47 | +7% | 1.61 → 1.72 |
| r8i | producer | 12.47 / 1.92 | 14.40 | 57.55 / 1.74 | 59.28 | +312% | 2.36 → 9.73 |
| r8i | consumer | 0.25 / 0.06 | 0.31 | 12.49 / 2.04 | 14.53 | +4587% | 0.05 → 2.38 |
| r8i | broker | 7.40 / 6.44 | 13.85 | 9.35 / 5.10 | 14.45 | +4% | 2.27 → 2.37 |
实验 C · 压缩比与语料熵(测试数据选错会让结论反转)
方法学要点:压缩比必须用真实业务形态数据测。合成随机数据信息熵接近上限、本就不可压,
用它做选型会得出「zstd 又贵又没用」的错误结论 —— 而这正是本表两组数据的差别所在。
| 机型 | 语料 | 熵 (bit/Byte) | none 磁盘 (GiB) | zstd 磁盘 (GiB) | 压缩比 |
|---|---|---|---|---|---|
| r8g | json | 5.115 | 10.93 | 1.51 | 7.24× |
| r8g | synth | 6.566 | 10.90 | 8.65 | 1.26× |
| r8a | json | 5.115 | 14.40 | 1.99 | 7.23× |
| r8a | synth | 6.566 | 14.36 | 11.38 | 1.26× |
| r8i | json | 5.115 | 14.40 | 1.99 | 7.23× |
| r8i | synth | 6.566 | 14.36 | 11.39 | 1.26× |
被排除的数据点
无 —— 所有 cell 状态均为 OK,无失败、无 rejected、无 CPU 快照异常。
方法学 · 相对既有测试修正的三处硬伤
① 延迟与吞吐必须分开测(最关键)
既有测试用
本次做法:实验 A 固定速率测延迟(各组同速率,实测偏差 <0.02%);实验 B 拉满只测吞吐, 且其延迟列在数据层被打上
既有测试用
--throughput -1(拉满)同时得出吞吐与延迟。但饱和态下不同压缩跑出不同吞吐,
此时测到的 avg/p99 主要是队列积压而非服务延迟 —— 吞吐低的那组反而显得延迟更高,
这是队列效应造成的假象,横向比较无效。本次做法:实验 A 固定速率测延迟(各组同速率,实测偏差 <0.02%);实验 B 拉满只测吞吐, 且其延迟列在数据层被打上
latency-NOT-comparable-saturated 标记,禁止进入延迟结论。② 补上 CPU 数据,验证「broker 零压缩 CPU」
该论断此前只有机制推理,从未被 CPU 数据证实。本次在 producer / consumer / broker 三处同时采
该论断此前只有机制推理,从未被 CPU 数据证实。本次在 producer / consumer / broker 三处同时采
/proc/<pid>/stat 累积计数器差值(不受采样丢峰影响),并归一化成「每 GB 的 CPU 秒」使跨速率可比。
producer 与 consumer 分机部署,否则两端 CPU 无法拆分归因。③ 压缩比必须用真实业务形态数据
合成随机数据信息熵接近上限、本就不可压。用它测出的压缩比会严重低估真实收益, 据此选型会得出「zstd 又贵又没用」的错误结论。本次两种语料并列对照并实测各自香农熵。
合成随机数据信息熵接近上限、本就不可压。用它测出的压缩比会严重低估真实收益, 据此选型会得出「zstd 又贵又没用」的错误结论。本次两种语料并列对照并实测各自香农熵。
环境与版本(全部 pin 死)
| 项 | 值 | 说明 |
|---|---|---|
| Region / AZ | ap-northeast-1c | 单 AZ,消除跨 AZ 网络变量;三机型均在该 AZ 可用(已核实) |
| 拓扑 | broker×3 + producer×1 + consumer×1 | 两端分机部署 = CPU 归因前提;cluster placement group |
| 机型 | r8g / r8a / r8i .8xlarge | 均 32 vCPU / 256 GiB / 15 Gigabit 持续带宽 |
| ⚠️ 物理核 | r8g 32 · r8a 32 · r8i 仅 16 + SMT2 | 已 DefaultCores 核实。CPU 跨机型比较同时给每 vCPU 与每物理核两口径 |
| 为何用 8xlarge | 4xlarge 是 "Up to 15G" 突发档 | 靠网络积分;zstd=off 组字节多会先撞流量整形→ 系统性污染对照组 |
| Kafka | 4.1.2(KRaft) | SHA512 与官方逐字符一致,UserData 内强校验 |
| zstd-jni | 1.5.6-10,level 3 | level 由 jshell 直接问库实测,非文档推断 |
| JDK | Corretto 21(devel) | 三机型 JVM flags 逐字符一致(E0-J1 校验通过) |
| EBS(broker) | gp3 1000 GiB / 16000 IOPS / 1000 MB/s | 确保磁盘不成为瓶颈;若停在 gp3 默认 125 MB/s,off 组会先撞磁盘墙 |
| 关键 broker 配置 | compression.type=producer | ★ 保证 broker 不重压缩;设成具体算法会解压重压,直接摧毁 H1 的验证前提 |
| topic | 24 分区 / RF=3 / minISR=2 / acks=all | cleanup.policy=delete,避免 compaction 触发意外重压 |
可复现性声明
全部资产在仓库
zstd-bench/ 下,可一键重跑:
cdk/— CDK v2(TypeScript):单 AZ 5 节点、参数化机型、AMI 走 SSM 动态查、标签隔离Project=kafka-zstd-benchscripts/05-e0-gate.sh— E0 环境闸门:16 项校验,任一 blocking 失败即拒绝采数(三机型均 PASS,blocking=0)scripts/10-run-latency-throughput.sh— 实验 A/B(含饱和探测定共同速率、随机化执行顺序、达标率 >2% 偏差即判 rejected)scripts/20-run-cpu-and-ratio.sh— 实验 C/D(含语料生成与熵计算、三端 CPU 采集、NA/负值双重拦截)scripts/60-build-report.py— 本报告生成器(只读 CSV,数字不手改)results/FINAL/— 全部原始 CSV + manifest(含共同速率推导过程)+ 语料指纹scripts/99-teardown-all.sh— 标签白名单销毁(DRY-RUN 默认,硬排除非本项目资源)
有效性威胁与已采取的对策
| 威胁 | 对策 |
|---|---|
| 饱和态延迟不可比 | 实验 A 固定速率;实验 B 延迟列数据层标记禁用 |
| JIT / page cache 预热 | 每组丢弃 1 轮预热;CPU 采集跳过 45s 预热窗口,只取稳态 120s |
| 时间漂移 / 邻居噪声 | 执行顺序随机化(seed 落 manifest);%steal > 1% 判该 run 无效 |
| 网络 / EBS 积分耗尽 | 用持续带宽档 8xlarge;EBS 显式 1000 MB/s;E0 记录上限指纹 |
| broker 意外解压重压 | compression.type=producer + cleanup.policy=delete + JMX ProduceMessageConversionsPerSec 守卫 |
| 消息格式 down-conversion | client 与 broker 同为 4.1.2;E0-X1 校验三节点 jar 校验和一致 |
| GC 线程数伪装成架构差异 | 显式固定 ParallelGCThreads;E0-J1 校验三机型 JVM flags 逐字符一致 |
| CPU 采样得出物理不可能值 | /proc/stat 取不到时返回 NA(不填 0);负差值判 FAILED_CPU_REGRESSED;producer 多灌 25% 条数确保覆盖采集窗口 |
| 把噪声当结论 | 每 cell n=3 报中位数 + 全距;全距重叠即判「无显著差异」 |
结论适用边界(外部有效性)
本次为 单 AZ、250 B 量级消息、24 分区、单 producer 进程的场景。以下情形结论可能不同: 跨 AZ / 跨云(网络字节量成为瓶颈时,zstd 的吞吐劣势会转为优势)、大消息(≥1 MB)、 多 producer 并发(可能突破本次观测到的 zstd 串行瓶颈)、以及 zstd level ≠ 3 的配置。
本次为 单 AZ、250 B 量级消息、24 分区、单 producer 进程的场景。以下情形结论可能不同: 跨 AZ / 跨云(网络字节量成为瓶颈时,zstd 的吞吐劣势会转为优势)、大消息(≥1 MB)、 多 producer 并发(可能突破本次观测到的 zstd 串行瓶颈)、以及 zstd level ≠ 3 的配置。
数据来源:ap-northeast-1 真机实测,2026-08-04/05。三机型 E0 环境闸门均 PASS(blocking=0)。
本报告由 scripts/60-build-report.py 从 results/FINAL/*.csv 自动生成,表格数字未经手工修改。