--- title: "Easysearch ZSTD 基准测试:高压缩率下实现近 5 倍查询吞吐" date: 2026-03-15 lastmod: 2026-03-15 description: "在同等环境且有明显背景负载下,Easysearch 的 ZSTD 方案在保持接近 DEFLATE 压缩率的同时,实现了接近 5 倍的查询吞吐和显著更低的延迟。" tags: ["Easysearch", "performance"] summary: "在搜索引擎领域,压缩算法的选择一直是一个经典的权衡难题: 选择高压缩率(如 best_compression / DEFLATE),磁盘省了,但查询解压慢; 选择高速编码(如默认 LZ4),查询快了,但磁盘占用大。 Easysearch 引入了基于 JDK 21 FFM(Foreign Function & Memory API) 直连本地 ZSTD 动态库的加速方案,试图打破这一困局。为了验证效果,我们在完全对等的环境下,对 Easysearch(ZSTD)和 Elasticsearch 7.10.2(best_compression)进行了一次严格的查询吞吐对比测试。 结果令人振奋——即使在系统明显背景负载下,Easysearch 也没有因为高压缩而变慢,反而在查询吞吐上实现了近 5 倍提升。 测试环境 # 为确保对比公平,两套集群的硬件资源、JVM 配置、数据规模、索引结构完全对齐: 配置项 Easysearch Elasticsearch 7.10.2 节点数 3 3 JVM 堆内存 12GB × 3 12GB × 3 node.processors 16 × 3 16 × 3 文档数 10,000,000 10,000,000 主分片 / 副本 3 / 0 3 / 0 数据类型 nginx 访问日志 nginx 访问日志 字段数 17 17 mapping 完全一致(MD5 校验) 完全一致(MD5 校验) Stored fields 压缩模式 ZSTD (JDK21 FFM/native, level=3) best_compression (DEFLATE) 压缩机制对比:best_compression 映射到 Lucene BEST_COMPRESSION;在 stored fields 路径上,压缩实现为 DeflateWithPresetDictCompressionMode,内部使用 java." --- 在搜索引擎领域,**压缩算法的选择**一直是一个经典的权衡难题: - 选择高压缩率(如 `best_compression` / DEFLATE),磁盘省了,但查询解压慢; - 选择高速编码(如默认 LZ4),查询快了,但磁盘占用大。 Easysearch 引入了基于 **JDK 21 FFM(Foreign Function & Memory API)** 直连本地 ZSTD 动态库的加速方案,试图打破这一困局。为了验证效果,我们在**完全对等的环境**下,对 Easysearch(ZSTD)和 Elasticsearch 7.10.2(best_compression)进行了一次严格的查询吞吐对比测试。 结果令人振奋——**即使在系统明显背景负载下,Easysearch 也没有因为高压缩而变慢,反而在查询吞吐上实现了近 5 倍提升**。 --- ## 测试环境 为确保对比公平,两套集群的硬件资源、JVM 配置、数据规模、索引结构完全对齐: | 配置项 | Easysearch | Elasticsearch 7.10.2 | |:---|:---|:---| | 节点数 | 3 | 3 | | JVM 堆内存 | 12GB × 3 | 12GB × 3 | | node.processors | 16 × 3 | 16 × 3 | | 文档数 | 10,000,000 | 10,000,000 | | 主分片 / 副本 | 3 / 0 | 3 / 0 | | 数据类型 | nginx 访问日志 | nginx 访问日志 | | 字段数 | 17 | 17 | | mapping | 完全一致(MD5 校验) | 完全一致(MD5 校验) | | Stored fields 压缩模式 | **ZSTD (JDK21 FFM/native, level=3)** | **best_compression (DEFLATE)** | > 压缩机制对比:`best_compression` 映射到 Lucene `BEST_COMPRESSION`;在 stored fields 路径上,压缩实现为 `DeflateWithPresetDictCompressionMode`,内部使用 `java.util.zip.Deflater/Inflater`(即 DEFLATE)。 > Easysearch ZSTD 当前走 JDK 21 FFM 绑定本地 zstd 库(`java.lang.foreign`);`index.compression.zstd.jni=true` 为当前这套实现的启用方式。 查询模型:JMeter 随机 `match` 查询,随机命中 `service_name`、`method`、`error_code`、`url` 四个字段,每次返回 10 条文档。 压测起始负载(`_cat/nodes` 快照): | 负载项 | Easysearch run | Elasticsearch run | |:---|:---|:---| | load_1m | 29.74 | 25.27 | | load_5m | 27.10 | 28.15 | | load_15m | 26.09 | 36.96 | | ram.percent | 99 | 99 | > 说明:压测并非在空闲机上进行,而是在已有明显背景负载的生产式环境下完成。 --- ## 核心结果 ### 1. 查询吞吐量(QPS):在高背景负载下,Easysearch 仍领先 372% 稳态阶段(3 轮平均),Easysearch 的查询吞吐是 Elasticsearch 的 **4.7 倍**: | 指标 | Elasticsearch (DEFLATE) | Easysearch (ZSTD) | 差异 | |:---|---:|---:|:---| | 稳态 QPS | 532.8 | **2,518.0** | **+372.6%** | | 平均响应时间 | 779.0 ms | **164.3 ms** | **-78.9%** | | 稳态 CPU 占用(系统总占用) | 92.43% | 89.59% | 仅作背景参考 | > 注:压测期间服务器存在明显背景负载(其他进程占用较高),该 CPU 指标是系统总占用,不等价于“仅搜索进程”的纯业务 CPU 对比。 > 在系统总 CPU 均接近 90% 的背景下,Easysearch 仍达到接近 5 倍吞吐。

查询吞吐量 QPS 对比(稳态均值)

ES (DEFLATE) 532.8 Easysearch (ZSTD) 2,518.0 QPS(越高越好)
### 2. 响应时间:从近 1 秒降到 164 毫秒

平均响应时间对比(ms,越低越好)

ES (DEFLATE) 779.0 ms Easysearch (ZSTD) 164.3 ms 响应时间(越低越好)
用户体感上,这意味着:同样一个搜索请求,Elasticsearch 还在等解压,Easysearch 已经把结果送到了客户端。 ### 3. 各轮次详细数据

各轮次 QPS 趋势

0 1500 3000 warmup steady_r1 steady_r2 steady_r3 2087.9 2533.2 2491.7 2529.0 484.5 521.8 539.6 537.1 Easysearch (ZSTD) Elasticsearch (DEFLATE)

各轮次平均响应时间趋势(ms)

0 400 800 warmup steady_r1 steady_r2 steady_r3 171 795 769 773 39 163 166 164 Easysearch (ZSTD) Elasticsearch (DEFLATE)
### 4. CPU 使用效率:每 1% CPU 产出的 QPS 差距惊人 单看 CPU 占用率,两者似乎差不多(89.59% vs 92.43%)。但如果换一个视角——**每消耗 1% CPU 能产出多少 QPS**,差距就一目了然了: | 指标 | Elasticsearch (DEFLATE) | Easysearch (ZSTD) | 倍数 | |:---|---:|---:|:---| | 稳态 QPS | 532.8 | 2,518.0 | — | | 稳态 CPU | 92.43% | 89.59% | — | | **QPS / 1% CPU** | **5.76** | **28.10** | **4.88×** |

CPU 使用效率:每 1% CPU 产出的 QPS

ES (DEFLATE) 5.76 Easysearch (ZSTD) 28.10 QPS / 1% CPU(越高越好)── 效率差距 4.88 倍
这意味着什么? - **ES 使用 DEFLATE(best_compression)时,解压路径更可能成为 CPU 热点**;结合 ES 的高 CPU(92.43%)与较低 QPS,说明单位 CPU 产出偏低; - **Easysearch 使用 ZSTD(JDK21 FFM/native)时,解压开销更小**;在相近 CPU 水位(89.59%)下获得更高 QPS,单位 CPU 产出明显更高。 换句话说,当前这组实测更支持“ZSTD 在该查询模型下单位 CPU 产出更高”。 ### 5. 存储空间:ZSTD 并未膨胀 | 索引 | 压缩算法 | 磁盘占用 | |:---|:---|---:| | nginx_best_10m (ES) | best_compression (DEFLATE) | 1.8 GB | | nginx_zstd3 (Easysearch) | ZSTD (level=3, JDK21 FFM/native) | 1.9 GB | 两者存储空间接近。若按 `_cat/indices` 的 1 位小数展示是 `1.8GB` vs `1.9GB`;若按 `_stats/store` 字节值计算,差异约 `2.5%`。因此可以认为 ZSTD 在 level=3 下与 DEFLATE `best_compression` 压缩率接近。

磁盘占用对比(GB)

ES (DEFLATE) 1.8 GB Easysearch (ZSTD) 1.9 GB 按字节口径约 +2.5%,整体接近
--- ## 为什么 ZSTD 能做到"又小又快"? 传统认知中,压缩率和解压速度是一对矛盾。但 ZSTD 算法天然具备**非对称压缩**的特性:

压缩算法特性对比

算法 压缩率 解压速度 LZ4 快但不紧凑 DEFLATE 紧凑但解压慢 ZSTD 两者兼得
在搜索引擎场景中,查询会触发存储字段(`_source`)读取与解压路径,命中文件系统页缓存时,可能不发生实际磁盘 I/O,但仍需进行 _source 解压。 当查询涉及较多 `_source` 读取时: - **DEFLATE** 的解压开销成为 CPU 瓶颈,拖慢了整体吞吐; - **ZSTD(JDK21 FFM/native)** 的解压速度在该场景下明显更优,单次请求的解压 CPU 成本更低,从而释放出更多 CPU 资源用于并发查询处理。 这就是为什么 Easysearch 在 CPU 占用更低(89.59% vs 92.43%)的情况下,反而能处理近 5 倍的查询量。 --- ## 一张图总结

Easysearch ZSTD vs Elasticsearch DEFLATE — 全维度对比

查询吞吐 +372.6% ↑ 响应时间 -78.9% ↓ CPU 效率 (QPS/CPU%) +387.8% ↑ 磁盘占用 +2.5% ≈ CPU 占用 -2.84pp ↓ 压缩率几乎相同,查询性能提升近 4 倍,CPU 效率提升近 5 倍
--- ## 结论 Easysearch 的 ZSTD 压缩方案证明了一个事实:**即使在高背景负载下,高压缩率和高查询性能依然可以兼得**。 在 1000 万条 nginx 日志、且系统存在明显背景负载的实测中: - 查询吞吐提升 **372%**,从 533 QPS 跃升至 2518 QPS - 平均响应时间下降 **79%**,从 779ms 降至 164ms - CPU 使用效率提升 **388%**,每 1% CPU 产出 28.10 QPS vs 5.76 QPS - CPU 占用绝对值下降 **2.84 个百分点**(相对下降约 3.07%) - 磁盘占用与 DEFLATE best_compression 接近(按字节口径约 +2.5%) 对于日志分析、可观测性、安全审计等需要兼顾存储成本和查询性能的场景,Easysearch ZSTD 是一个不需要妥协的选择。 --- ## ZSTD 使用方法 ### 1) 新建索引时启用 ZSTD ```bash curl -k -u 'admin:' -X PUT 'https://127.0.0.1:9200/' \ -H 'Content-Type: application/json' -d '{ "settings": { "index.codec": "ZSTD", "index.compression.zstd.jni": true } }' ``` 可选参数: - `index.compression.zstd.level`(默认 `3`) 说明: - `index.compression.zstd.dict` 固定为 `true`,无需单独配置 - `index.compression.zstd.dict` 不作为独立开关来调整 ### 2) 老索引切换到 ZSTD(推荐 reindex) `index.codec` 是静态设置(打开状态不可动态改;可在关闭索引后调整)。 `index.compression.zstd.jni` 是 `final` 设置(关闭索引后也不可修改)。 如果老索引要启用 `index.compression.zstd.jni=true`,建议新建目标索引后 `reindex` 迁移: 如果对已有索引执行 `PUT //_settings` 直接修改,会报错:`final setting [index.compression.zstd.jni], not updateable`。 ```bash # 先创建目标索引(启用 ZSTD) curl -k -u 'admin:' -X PUT 'https://127.0.0.1:9200/' \ -H 'Content-Type: application/json' -d '{ "settings": { "index.codec": "ZSTD", "index.compression.zstd.jni": true } }' # 再迁移数据 curl -k -u 'admin:' -X POST 'https://127.0.0.1:9200/_reindex' \ -H 'Content-Type: application/json' -d '{ "source": { "index": "" }, "dest": { "index": "" } }' ``` ### 3) 校验是否生效 ```bash curl -k -u 'admin:' \ 'https://127.0.0.1:9200//_settings?include_defaults=true&pretty' ``` 重点确认: - `index.codec = ZSTD` - `index.compression.zstd.jni = true` ---