1 前言

在《ElasticSearch 降本增效常见的方法》一文中曾提到过 zstd 压缩算法 [1],一步一个脚印我们终于在京东 ES 上线支持了 zstd;我觉得促使目标完成主要以下几点原因:

  1. Elastic 官方原因:zstd 压缩算法没有在 Elastic 官方的开发计划中;Elastic 的 licenes 变更,很多功能使用受限

  2. ES 产品竞争力:提升京东 ES 产品在业界的竞争力,两大云友商和其他大厂都在陆续支持,在对外比拼的时候,我们需要提升我们这方面的能力

  3. 信创大背景:我们需要对开源组件有更好的自主管控和建设能力

  4. 京东零售 ES 与云 ES 产品融合:有更好的机会去打磨我们的 ES 内核

  5. 降本增效:ztsd 压缩算法,能够在降低存储成本的前提下,保证性能几乎不受损,写入性能还有所提升

2 测试结果

测试集群配置:4c8g; 3 个数据节点;

测试索引设置:3 主分片 1 副本

测试数据 mapping: keyword 字段 14 个,geo_point 字段 3 个,integer 字段 2 个,text 字段 1 个,date 字段:2 个,ip 类型字段 1 个,boolean 字段 1 个

在考虑到读写性能和压缩比均衡的情况下,我们推荐使用 jd_zstd(压缩等级 3):

下表为 es6.8.23 版本,在 cpu 压测到 100% 时,不通压缩算法下ES 的 bulk、termquery、rangequery、matchquery 等 TPS 以及压缩比测试结果:

压缩算法 bulk termquery rangequery matchquery 数据存储大小(580W 条文档)segment forcemerge 为 1 个 压缩率,基准为 lz(ES 默认为 lz 压缩算法)
lz4 34K 7.7K 790 450 13gb -
best_compression 26K 4.7K 780 430 10gb 76.9%
jd_zstd(压缩等级 3) 36K 5.4K 790 450 10gb 76.9%
jd_zstd(压缩等级 6) 32K 5.6K 790 460 9.8gb 75.38%
jd_zstd(压缩等级 9) 25K 5.5K 790 450 9.8gb 75.38%

注意⚠️:测试数据仅供参考,实际情况与用户数据有关

3 适用场景

写多读少的场景,比如日志和监控场景。


4 使用方法

云上ES等待上线后,可以进行申请

目前我们暂时只在内部泰山零售 ES 上线,支持 7.X 和 6.8.23 版本;后续会在云舰 ES 和公有云 ES 上线,由于 licenes 的限制,我们将只推出 6.8.23 版本。

Q1: 如何申请?

**A1:** 内部用户:之前在泰山平台申请的杰斯 ES,如果使用的是 7.X 和 6.8.23,可以选择版本升级到最新版本。新建集群,直接提工单申请

Q2 ztsd 如何使用?

A2:我们在 ES 中支持两种 zstd 压缩等级,用户可以根据自己的业务和数据特性选择合适的压缩等级; ES 创建索引时指定 index.codec:jd_zstd(压缩等级为 3)或者jd_zstd_6(压缩等级为 6) 即可,其余没有其他任何特殊之处。

注意⚠️:index.codec 的压缩算法不支持动态修改,必须创建索引时设定好。

# 创建索引zstdtest 压缩等级为 3
PUT zstdtest
{
    "settings": {
      "index": {
        "codec": "jd_zstd"
      }
    }
}

# 创建索引zstdtest_6 压缩等级为 3
PUT zstdtest_6
{
    "settings": {
      "index": {
        "codec": "jd_zstd_6"
      }
    }
}


5 技术实现

首先我们介绍下 ES 与 Lucene 的关系;如下图所示,在集群层面:一个 ES 集群由多个节点组成。数据层面:1 个索引是由多个分片组成的,一个分片可以看是一个 Lucene 实例;一个分片包含多个 segement,一个 segement 即一组数据的最小单元,包含很多的数据文件。

es集群文件 (1).png

5.1 Lucene 文件

lucene[2] 的数据文件主要由以下文件组成:

NAME Extension Brief Description
Segments File segments_N 存储已经落盘数据的位移提交点
Lock File write.lock 锁文件,防止多个 IndexWriters 写同一个文件
Segment Info .si 存储单个 segment 的 metadata
Compound File .cfs, .cfe 复合文件主要是为了减少文件描述符;在 IndexWriterConfig 可以配置是否生成复合索引文件;复合文件实质是索引文件的组合,意思是无论是否设置了使用复合文件,总是先生成非复合索引文件,随后在 flush 阶段,才将这些文件生成.cfs、.cfe 文件,其中.liv、.si 所以文件不会被组合到.cfs、.cfe 中。
Fields .fnm 存储有关字段的信息
Field Index .fdx 指向字段数据的指针;存储了原文数据在原文存储文件中的位置信息,建立起了 doc id 和原文之间的联系,以支持快速访问和定位
Field Data .fdt 文档的存储字段
Term Dictionary .tim term 词典,存储 term 信息
Term Index .tip Term 词典的索引
Frequencies .doc 文档列表,其中包含每个 term 以及频率
Positions .pos 存储 term 在索引中出现位置的位置信息
Payloads .pay 存储附加的每个位置元数据信息,如字符偏移和用户 payloads
Norms .nvd, .nvm 编码文档和字段的长度以及权重提升因子
Per-Document Values .dvd, .dvm 编码额外的评分因子或其他每个文档的信息
Term Vector Index .tvx 矢量数据的索引文件;将偏移量存储到文档数据文件中
Term Vector Data .tvd term 矢量数据
Live Documents .liv 有关哪些文档处于存活的信息;当发生标记删除时会产生该文件
Point values .dii, .dim 保留索引点,如果存在

上述的文件大致可以分为以下几类:

"_source": {
  "enabled": false
}


注意⚠️:关闭_source 后, update, update_by_query, reindex 等功能无法正常使用,因此有 update 等需求的索引不能关闭_source.

zstd 主要压缩为行存储相关文件.fdm、.fdt 和.fdx;如下代码块为压缩文件对比,可以看出在不同的压缩算法中,这几个文件的大小是不同的。

# 为了节省篇幅部分文件省略      
## lz4压缩算法索引testlz4    0 号分片
total 2.4G
-rw-r--r-- 1 admin admin 1.2K Nov 16 16:19 _32.fdm
-rw-r--r-- 1 admin admin 1.3G Nov 16 16:19 _32.fdt
-rw-r--r-- 1 admin admin  76K Nov 16 16:19 _32.fdx
-rw-r--r-- 1 admin admin  85M Nov 16 16:21 _32.kdd
-rw-r--r-- 1 admin admin 149M Nov 16 16:21 _32_Lucene80_0.dvd
.........................................
-rw-r--r-- 1 admin admin  401 Nov 16 16:21 segments_b
-rw-r--r-- 1 admin admin    0 Oct 16 16:05 write.lock

## best_compression压缩算法索引 testbestcompression   0 号分片
total 1.9G
-rw-r--r-- 1 admin admin  287 Nov 16 17:01 _2b.fdm
-rw-r--r-- 1 admin admin 781M Nov 16 17:01 _2b.fdt
-rw-r--r-- 1 admin admin  17K Nov 16 17:01 _2b.fdx
-rw-r--r-- 1 admin admin  85M Nov 16 17:03 _2b.kdd
-rw-r--r-- 1 admin admin 148M Nov 16 17:03 _2b_Lucene80_0.dvd
.........................................
-rw-r--r-- 1 admin admin  401 Nov 16 17:03 segments_a
-rw-r--r-- 1 admin admin    0 Oct 16 16:27 write.lock

## zstd压缩等级为3 索引testzstd3   0 号分片
total 1.9G
-rw-r--r-- 1 admin admin  286 Nov 16 17:26 _8e.fdm
-rw-r--r-- 1 admin admin 758M Nov 16 17:26 _8e.fdt
-rw-r--r-- 1 admin admin  15K Nov 16 17:26 _8e.fdx
-rw-r--r-- 1 admin admin  84M Nov 16 17:29 _8e.kdd
-rw-r--r-- 1 admin admin 148M Nov 16 17:29 _8e_Lucene80_0.dvd
-rw-r--r-- 1 admin admin 3.5K Nov 16 17:29 
.........................................
-rw-r--r-- 1 admin admin  402 Nov 16 17:29 segments_9
-rw-r--r-- 1 admin admin    0 Nov 15 16:50 write.lock

## zstd压缩等级为6 索引testzstd6   0 号分片
total 1.9G
-rw-r--r-- 1 admin admin  286 Nov 16 16:56 _29.fdm
-rw-r--r-- 1 admin admin 742M Nov 16 16:56 _29.fdt
-rw-r--r-- 1 admin admin 9.8K Nov 16 16:56 _29.fdx
-rw-r--r-- 1 admin admin  86M Nov 16 16:58 _29.kdd
-rw-r--r-- 1 admin admin 148M Nov 16 16:58 _29_Lucene80_0.dvd
.........................................
-rw-r--r-- 1 admin admin  412 Nov 16 16:58 segments_a
-rw-r--r-- 1 admin admin    0 Oct 16 16:04 write.lock

## zstd压缩等级为9 索引testzstd9      0 号分片
total 1.9G
-rw-r--r-- 1 admin admin  286 Nov 16 17:21 _gp.fdm
-rw-r--r-- 1 admin admin 738M Nov 16 17:21 _gp.fdt
-rw-r--r-- 1 admin admin  13K Nov 16 17:21 _gp.fdx
-rw-r--r-- 1 admin admin  85M Nov 16 17:23 _gp.kdd
-rw-r--r-- 1 admin admin 149M Nov 16 17:23 _gp_Lucene80_0.dvd
.........................................
-rw-r--r-- 1 admin admin  402 Nov 16 17:23 segments_8
-rw-r--r-- 1 admin admin    0 Nov 15 16:50 write.lock


5.2 ES 侧实现

理论上来说 index.codec 支持的压缩算法最好下沉到 lucene 代码中,目前我们并没有维护 lucene 代码,因此我们直接 ES 侧面代码实现。

zstd[1] 算法是基于 C++ 实现,而 ES 是基于 java 编写,因此借助开源的力量,引入 zstd-jni 来实现 zstd 压缩能力.

# zstd_jni版本 1.5.5-1
api "com.github.luben:zstd-jni:${versions.zstd_jni}"


在 ES 代码中编写自定义的 index.codec;扩展 CompressionMode 压缩模式,自定义实现 ZstdCompressor 压缩和 ZstdDecompressor 解压缩方法,可以在这设定 zstd 的压缩等级以及控制读写数据块大小;最后通过 java 的 spl 机制实现加载我们自定义的压缩算法实现类

在 server/src/main/resources/META-INF/services/org.apache.lucene.codecs.Codec 文件中定义如下.

org.elasticsearch.index.codec.custom.ZstdCodec


注意⚠️:由于 ES 节点启动的时候,有 security 检查机制,因此我们需要在 server/src/main/resources/org/elasticsearch/bootstrap/security.policy 文件中添加代码权限授权策略

grant codeBase "${codebase.zstd-jni}" {
  permission java.lang.RuntimePermission "loadLibrary.*";
  permission java.lang.RuntimePermission "libzstd.*";
};


6 参考文档

[1] https://github.com/facebook/zstd

[2] https://lucene.apache.org/core/8_11_2/core/org/apache/lucene/codecs/lucene87/package-summary.html#package.description

[3] Y. Malkov, D. Yashunin,Efficient and robust approximate nearest neighbor search using Hierarchical Navigable Small World graphs(2016), IEEE Transactions on Pattern Analysis and Machine Intelligence

[4] https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#approximate-knn

[5] https://mp.weixin.qq.com/s/awxgy9pSgv0lVPTfvzfxBw

[6] https://mp.weixin.qq.com/s/dmJwEpl6CWtv-MLdvR7g

作者:京东科技 杨松柏

来源:京东云开发者社区 转载请注明来源京东 ES 支持 ZSTD 压缩算法上线了:高性能,低成本 | 京东云技术团队


↙↙↙阅读原文可查看相关链接,并与作者交流