公司目前所使用的Elasticsearch(以下简称ES)版本是在2015年的时候选择的,当时选择了ES的最新版本1.5.2。随着ES官方的快速发展,ES也已经推出了很多新的版本,这些新版本往往伴随着一些新特性的发布以及性能的提升,为了使用到这些新特性,我们决定将ES的版本升级到7.5.2。

ES版本升级会遇到API不兼容的问题,我们的解决办法是在业务和ES之间增加一层搜索平台来做接口兼容。此外我们在版本升级之前还需要了解一下ES-7.5.2相对于ES-1.5.2有多少性能提升,所以我们需要针对这两个版本做一下性能测试并对结果进行对比。我们使用如下的四台负载来做性能测试

序号节点IPCPU型号CPU核数内存硬盘操作系统
1172.19.66.58Intel(R) Xeon(R) CPU E5645 @2.40GHz23079520kB14GGNU/Linux 3.10.0-957.21.3.el7.x86_64
2172.19.66.70Intel(R) Xeon(R) CPU E5645 @2.40GHz23079524kB14GGNU/Linux 3.10.0-957.21.3.el7.x86_64
3172.19.66.77Intel(R) Xeon(R) CPU E5645 @2.40GHz23079524kB14GGNU/Linux 3.10.0-957.21.3.el7.x86_64
4172.19.66.133Intel(R) Xeon(R) CPU E5645 @2.40GHz23079524kB14GGNU/Linux 3.10.0-957.21.3.el7.x86_64

使用time和dd命令测试负载的磁盘性能

# 测试磁盘的写入性能
time dd if=/dev/zero of=test.data bs=8k count=10000 oflag=direct
# 测试磁盘的读取性能
time dd if=test.data of=/dev/null bs=8k count=10000 iflag=direct

通过测试可知以上四台负载的磁盘读写性能基本一致。

压测前的环境准备

创建索引和数据

1号和2号节点安装ES-1.5.2集群,3号和4号节点安装ES-7.5.2集群。两个集群各自的两个节点均设置为master-eligible和data节点,并且JVM的堆内存都设置为1GB。分别向两个集群写入相同的测试数据112万条,对于text类型两个集群都使用ES默认的分词器。两个集群的两个索引的mapping基本上一致,我把1.5.27.5.2这两个索引的mapping都放在了gist上面以供参考。

创建压测环境

我们使用 ApacheBench (ab) 来实现压测功能,为了方便操作我们利用ab实现了一个测试脚本 test.sh,在测试脚本中我们使用了query.json文件中的ES查询DSL来实现ES不同类型查询的性能测试,后面我们会修改query.json文件中的DSL来实现不同的查询测试。

进行压测

压测使用的客户机为MacBookPro,客户机和ES集群位于同一个局域网,因此可以保证网络带宽不会成为瓶颈。客户机的具体配置如下

macOs Catalina 10.15.4 (19E287)
MacBook Pro (Retina, 13-inch, Early 2015)
2.7 GHz Dual-Core Intel Core i5
8 GB 1867 MHz DDR3

具体的测试结果如下,我们修改query.json的DSL来实现不同类型操作的测试。

term-level

term
1
2
3
4
5
6
7
8
{
"query": {
"term": {
"acctId": "A00000000000"
}
},
"size": 0
}

修改query.json的内容如上并且执行命令

./test.sh

随后我们可以得到脚本的执行结果,因为执行结果内容比较多所以我们只挑一些关键性的部分进行了解。完整的执行结果可以在 gist上面 看到。

Elasticsearch-1.5.2 >>> 172.19.66.70
Requests per second:    165.87 [#/sec] (mean)
Percentage of the requests served within a certain time (ms)
50%     343
66%     399
75%     421
80%     454
90%     547
95%     620
98%     687
99%     784
100%    905 (longest request)

Elasticsearch-7.5.2 >>> 172.19.66.77
Requests per second:    1049.51 [#/sec] (mean)
Percentage of the requests served within a certain time (ms)
50%     53
66%     61
75%     68
80%     73
90%     87
95%     100
98%     118
99%     129
100%    202 (longest request)

可以看到在并发为60的情况下,执行压测10秒钟,1.5.2的QPS为165.87,7.5.2的QPS为1049.51,此外7.5.2的99%的请求耗时都是在130ms以下的,由此可以证明7.5.2相较于1.5.2在term查询上确实有很大的性能提升。

range

query.json

1
2
3
4
5
6
7
8
9
10
{
"query": {
"range": {
"createdTimeStr": {
"gte": "2020-01-01T00:00:00.000+0800"
}
}
},
"size": 0
}

性能测试结果

Elasticsearch-1.5.2 >>> 172.19.66.70
Requests per second:    1237.86 [#/sec] (mean)
Percentage of the requests served within a certain time (ms)
50%     45
66%     52
75%     58
80%     62
90%     75
95%     85
98%     101
99%     113
100%    189 (longest request)

Elasticsearch-7.5.2 >>> 172.19.66.77
Requests per second:    1099.03 [#/sec] (mean)
Percentage of the requests served within a certain time (ms)
50%     47
66%     54
75%     59
80%     63
90%     75
95%     86
98%     102
99%     120
100%    166 (longest request)

可以看出7.5.2在range查询上有一定的性能提升,但是提升并不明显。

match

query.json

1
2
3
4
5
6
7
8
{
"query": {
"match": {
"userName.analyzed": "江苏千米"
}
},
"size": 0
}

性能测试结果

Elasticsearch-1.5.2 >>> 172.19.66.70
Requests per second:    789.62 [#/sec] (mean)
Percentage of the requests served within a certain time (ms)
50%     74
66%     92
75%     107
80%     115
90%     132
95%     148
98%     169
99%     187
100%    336 (longest request)

Elasticsearch-7.5.2 >>> 172.19.66.77
Requests per second:    1229.56 [#/sec] (mean)
Percentage of the requests served within a certain time (ms)
50%     44
66%     52
75%     57
80%     62
90%     72
95%     85
98%     107
99%     135
100%    275 (longest request)

由上面的结果可见7.5.2对match查询也是有一定的性能提升的。

aggregation操作

metrics

query.json

1
2
3
4
5
6
7
8
9
10
{
"aggs": {
"balance_stats": {
"extended_stats": {
"field": "balance"
}
}
},
"size": 0
}

性能测试结果

Elasticsearch-1.5.2 >>> 172.19.66.70
Requests per second:    32.67 [#/sec] (mean)
Percentage of the requests served within a certain time (ms)
50%    1838
66%    1938
75%    1970
80%    1972
90%    2427
95%    2555
98%    2597
99%    3486
100%   3806 (longest request)

Elasticsearch-7.5.2 >>> 172.19.66.77
Requests per second:    1393.98 [#/sec] (mean)
Percentage of the requests served within a certain time (ms)
50%     41
66%     48
75%     53
80%     56
90%     65
95%     74
98%     84
99%     93
100%    141 (longest request)

7.5.2在指标聚合上的速度提升也十分的可观。

bucket

query.json

1
2
3
4
5
6
7
8
9
10
11
{
"aggs": {
"typeName": {
"terms": {
"field": "balanceTypeName",
"size": 50
}
}
},
"size": 0
}

性能测试结果

Elasticsearch-1.5.2 >>> 172.19.66.70
Requests per second:    79.98 [#/sec] (mean)
Percentage of the requests served within a certain time (ms)
50%    721
66%    1015
75%    1171
80%    1258
90%    1488
95%    1571
98%    1600
99%    1618
100%   1643 (longest request)

Elasticsearch-7.5.2 >>> 172.19.66.77
Requests per second:    1629.80 [#/sec] (mean)
Percentage of the requests served within a certain time (ms)
50%     34
66%     40
75%     45
80%     48
90%     57
95%     65
98%     77
99%     86
100%    153 (longest request)

7.5.2在桶聚合上也有着很好的性能优化效果。

小结

从上面的term、range、match、metric、bucket的测试结果中我们可以看到,除了range之外其余的几个DSL在7.5.2都有了较大的性能提升。但是我们也要清醒地认识到,我们在上面的单次性能测试中使用的都是完全一样的DSL,这就可能会使得ES中的缓存生效,因此该性能测试可能无法完全展现出真实环境中的ES性能,因为在真实环境中ES的缓存可能并不能像这里测试的这样完美的生效。

此外,由于这里的机器资源有限,我们只能做一个简单的性能对比实验,证明7.5.2的性能确实是优于1.5.2的。真正的ES-7.5.2集群的性能极限还要等到搜索平台的兼容性测试完成后,这时候我们可以搭建一个真正的生产环境的7.5.2集群,然后对此集群进行极限压测,等后面做了我会再做一份笔记。

数据写入的性能测试

上面我们测试了ES的数据检索和聚合的性能,下面我们也对ES的数据写入性能做一个测试。创建data.json并保存如下的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
"id": 86523,
"name": "普通高等教育“十一五”国家级规划教材配套参考书:电工电子学学习辅导与习题解答(第3版)",
"publish": "高等教育出版社",
"author": "张伯尧,叶挺秀",
"info": "《电工电子学学习辅导与习题解答(第3版)》",
"price": 86.9,
"createdTimeStr": "2020-01-01T12:00:00.000+0800",
"createdTime": 1547873713709,
"type": "college",
"sellOut": false,
"students": [
{
"name": "王博文",
"class": 1
},
{
"name": "徐晓静",
"class": 2
},
{
"name": "路小楠",
"class": 1
}
]
}

使用ab测试1.5.2的集群写入性能

ab -n 50000 -c 200 -p data.json -T 'application/json' http://172.19.66.70:9200/books/Book

性能报告如下

Time taken for tests:   26.680 seconds
Requests per second:    1874.06 [#/sec] (mean)
Percentage of the requests served within a certain time (ms)
50%     72
66%     85
75%     95
80%     101
90%     123
95%     147
98%     1236
99%     1276
100%    3652 (longest request)

使用ab测试7.5.2的集群写入性能

ab -n 50000 -c 200 -p data.json -T 'application/json' http://172.19.66.77:9200/books/_doc

性能报告如下

Time taken for tests:   103.203 seconds
Requests per second:    484.48 [#/sec] (mean)
Percentage of the requests served within a certain time (ms)
50%     344
66%     387
75%     438
80%     478
90%     615
95%     739
98%     979
99%     1097
100%    2436 (longest request)

很奇怪的是ES-7.5.2的数据写入性能明显低于ES-1.5.2,为了规避掉机器之间的细微的差异,我把ES-7.5.2安装在节点1和节点2之上,之后停止ES-1.5.2集群同时启动ES-7.5.2集群,然后把数据写到新的7.5.2的集群中去,测试结果和上面还是基本一致。具体原因暂时还不清楚,后面再做进一步的深入了解。

数据写入慢的原因

我们删除上面的测试中所创建的books索引,之后给每个索引只创建一条文档

ab -n 1 -c 1 -p data.json -T 'application/json' http://172.19.66.70:9200/books/Book
ab -n 1 -c 1 -p data.json -T 'application/json' http://172.19.66.77:9200/books/_doc

只写入一条数据我们发现1.5.2也是快于7.5.2的集群的,之后我们使用如下接口观察我们写入的数据的段文件情况,首先是1.5.2集群

http://172.19.66.70:9200/books/_segments

得到1.5.2的段文件信息如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
{
"_shards": {
"total": 4,
"successful": 4,
"failed": 0
},
"indices": {
"books": {
"shards": {
"0": [
{
"routing": {
"state": "STARTED",
"primary": true,
"node": "Fn-qbetJSyS7Qn6qb8rcaQ"
},
"num_committed_segments": 1,
"num_search_segments": 1,
"segments": {
"_0": {
"generation": 0,
"num_docs": 1,
"deleted_docs": 0,
"size_in_bytes": 9870,
"memory_in_bytes": 12898,
"committed": true,
"search": true,
"version": "4.10.4",
"compound": true
}
}
},
{
"routing": {
"state": "STARTED",
"primary": false,
"node": "PvI9366jTQKbFXWF2HRg6w"
},
"num_committed_segments": 1,
"num_search_segments": 1,
"segments": {
"_0": {
"generation": 0,
"num_docs": 1,
"deleted_docs": 0,
"size_in_bytes": 9870,
"memory_in_bytes": 12898,
"committed": true,
"search": true,
"version": "4.10.4",
"compound": true
}
}
}
],
"1": [
{
"routing": {
"state": "STARTED",
"primary": false,
"node": "Fn-qbetJSyS7Qn6qb8rcaQ"
},
"num_committed_segments": 0,
"num_search_segments": 0,
"segments": {}
},
{
"routing": {
"state": "STARTED",
"primary": true,
"node": "PvI9366jTQKbFXWF2HRg6w"
},
"num_committed_segments": 0,
"num_search_segments": 0,
"segments": {}
}
]
}
}
}
}

从上面的结果中我们可以看到这条文档被保存到了books索引的0号分片中,这个分片包含了primary和replica两份数据,1号分片中的数据则为空。我们可以看到0号分片的主备两个分片的 segments.size_in_bytes 的值均为9870,这代表了segment在磁盘上占用的空间大小。

了解了以上信息之后,我们继续获得7.5.2的段文件信息

http://172.19.66.77:9200/books/_segments

得到的7.5.2的段文件信息如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
{
"_shards": {
"total": 4,
"successful": 4,
"failed": 0
},
"indices": {
"books": {
"shards": {
"0": [
{
"routing": {
"state": "STARTED",
"primary": true,
"node": "sLtK-N1eSFiGNV7PeA1ntg"
},
"num_committed_segments": 0,
"num_search_segments": 0,
"segments": {}
},
{
"routing": {
"state": "STARTED",
"primary": false,
"node": "HNJ8oebqSRGO6C9YJbjCFA"
},
"num_committed_segments": 0,
"num_search_segments": 0,
"segments": {}
}
],
"1": [
{
"routing": {
"state": "STARTED",
"primary": false,
"node": "sLtK-N1eSFiGNV7PeA1ntg"
},
"num_committed_segments": 0,
"num_search_segments": 1,
"segments": {
"_0": {
"generation": 0,
"num_docs": 1,
"deleted_docs": 0,
"size_in_bytes": 11863,
"memory_in_bytes": 5007,
"committed": false,
"search": true,
"version": "8.3.0",
"compound": true,
"attributes": {
"Lucene50StoredFieldsFormat.mode": "BEST_SPEED"
}
}
}
},
{
"routing": {
"state": "STARTED",
"primary": true,
"node": "HNJ8oebqSRGO6C9YJbjCFA"
},
"num_committed_segments": 0,
"num_search_segments": 1,
"segments": {
"_0": {
"generation": 0,
"num_docs": 1,
"deleted_docs": 0,
"size_in_bytes": 11863,
"memory_in_bytes": 5007,
"committed": false,
"search": true,
"version": "8.3.0",
"compound": true,
"attributes": {
"Lucene50StoredFieldsFormat.mode": "BEST_SPEED"
}
}
}
}
]
}
}
}
}

7.5.2集群的books索引的文档分配在了分片1上,同样分片1也包含了主副两个分片。我们可以看到7.5.2的段文件占用的磁盘空间为11863,比1.5.2的9870要大一些,这是为什么呢?按道理来说Lucene从版本4.10.4升级到版本8.3.0,占用空间应该有一定程度上的优化才对,至少也不应该是占用空间变大了啊。

通过查找资料,我们可以知道在新版本的Elasticsearch中,为了优化ES的排序聚合的速度而引入了Doc values属性,该属性是在文档索引时生成的,目的是为了替代以前在文档查询时才生成的Fielddata属性。

我们可以对字段的mapping属性添加

"doc_values": false

这样可以禁用字段的doc_values属性,在禁用了7.5.2的字段的doc_values之后,段文件占用空间明显减小,索引速度也有明显的提升。

BUT!!!

通过以上的配置之后,7.5.2的数据索引速度仍然只是1.5.2的一半😶,具体原因还需进一步的分析😢。

5月19日补充:

关闭两个集群的refresh_interval(关闭自动refresh之后,写入到索引的数据不会被查询到,除非手动的执行了refresh操作之后才可以)

PUT /books/_settings

{
    "refresh_interval": -1
}

同时关闭7.5.2的doc_values,并且只保存了long类型的数据以避免分词器的影响,之后测试结果发现仍然是7.5.2的时间更长。不过此时我们使用如下的接口查询索引的详细信息

GET /books/_stats

可以发现两个索引的 indexing.index_time_in_millis 的值其实是一样呢,这是为啥呢。。。

参考

Apache Bench 安装与使用
性能测试应该怎么做?

滴滴 ElasticSearch 平台跨版本升级以及平台重构之路
https://www.elastic.co/guide/en/elasticsearch/reference/7.5/query-dsl.html
https://www.elastic.co/guide/en/elasticsearch/reference/7.5/search-aggregations.html