---
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 对比(稳态均值)
### 2. 响应时间:从近 1 秒降到 164 毫秒
平均响应时间对比(ms,越低越好)
用户体感上,这意味着:同样一个搜索请求,Elasticsearch 还在等解压,Easysearch 已经把结果送到了客户端。
### 3. 各轮次详细数据
各轮次 QPS 趋势
各轮次平均响应时间趋势(ms)
### 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(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)
---
## 为什么 ZSTD 能做到"又小又快"?
传统认知中,压缩率和解压速度是一对矛盾。但 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 — 全维度对比
---
## 结论
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`
---