0%

如何优化Elasticsearch大文档查询?

记录一次业务复杂场景下DSL优化的过程

背景

B端商城业务有一个场景就是客户可见的产品列表是需要N多闸口及各种其它逻辑组合过滤的,各种闸口数据及产品数据都是存储在ES的(有的是独立索引,有的是作为产品属性存储在产品文档上)。

在实际使用的过程中,发现接口的毛刺比较严重,而这部分毛刺请求的耗时基本都是花费在从ES中查询产品索引的时候。

开启了一下ES慢DSL的日志

1
2
3
4
5
6
7
PUT /jiankunking_product_prod/_settings
{
"index.search.slowlog.threshold.query.warn": "10s",
"index.search.slowlog.threshold.query.info": "5s",
"index.search.slowlog.threshold.fetch.warn": "2s",
"index.indexing.slowlog.source": true
}

经过分析慢DSL日志发现耗时长的部分都是在fetch阶段。

这里有个地方需要注意

1
2
3
4
5
6
7
8
9
10
11
[root@jiankunking-search-01: /data/es/logs]# ls -lrth |grep -v .gz
total 2.2G
-rw-r--r-- 1 es es 0 Sep 30 2019 jiankunking_audit.json
-rw-r--r-- 1 es es 0 Sep 30 2019 jiankunking_index_indexing_slowlog.log
-rw-r--r-- 1 es es 0 Sep 30 2019 jiankunking_index_indexing_slowlog.json
-rw-r--r-- 1 es es 53M Dec 31 2023 jiankunking_deprecation.log
-rw-r--r-- 1 es es 108M Dec 31 2023 jiankunking_deprecation.json
-rw-r--r-- 1 es es 55K Jul 30 10:43 jiankunking_server.json
-rw-r--r-- 1 es es 52K Jul 30 10:43 jiankunking.log
-rw-r--r-- 1 es es 63M Jul 30 11:32 jiankunking_index_search_slowlog.log //这里是完整的DSL
-rw-r--r-- 1 es es 8.9M Jul 30 11:32 jiankunking_index_search_slowlog.json //这里的DSL会被截断

分析

已知问题点

大体就是下图这么个流程

下面简化一下请求的DSL,看下移除所有复杂的查询逻辑后,直接按照_id来terms查询效果如何?

DSL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
GET /jiankunking_product_prod/_search
{
"size": 10000,
"_source": {
"includes": [
"code",
"group",
"groupBrand"
],
"excludes": []
},
"query": {
"terms": {
"_id": [
"具体文档_id"
]
}
}
}

不同文档大小查询时延

当前分析的DSL原本命中的文档数就是8306
下表中的文档数是直接在terms中查询的id数

文档数 文档大小(Bytes) 文档大小(KB) 响应时延(ms) 备注
8306 无限制 5908
5908 <50,0000 <488 2327 剔除大的
6929 <20,0000 <195 1507 剔除大的
5731 <10,0000 <97 599 剔除大的
4925 <5,0000 <49 356 剔除大的
4236 <3,0000 <29 214 剔除大的(注意这里,当文档大小比较小的时候,4000+的文档查询其实是比较快的)
---- ---- ---- ---- ----
4070 >3,0000 >29 6261 剔除小的
3381 >5,0000 >49 6050 剔除小的
2572 >10,0000 >97 5388 剔除小的
1377 >20,0000 >195 4973 剔除小的
669 >50,0000 >488 3984 剔除小的
381 >100,0000 >976 3169 剔除小的
217 >200,0000 >1952 2391 剔除小的
88 >300,0000 >2928 1244 剔除小的

分析

  • 文档数与文档大小查询分析
    • 剔除大文档之后,查询数据效率提升明显
    • 剔除小文档之后,查询数据效率提升缓慢

到这里我们可以发现当文档size比较小的时候几千个文档的查询RT是很短的,但当随着请求命中的大文档越来越多,RT极速增加。

回看下我们的产品索引数据,可以发现大字段其实都是用来过滤的,并不是返回给页面需要的;那我们是不是可以:将索引拆分为两个或者ES只用来作为二级索引返回ids,然后去MySQL中查询具体的产品信息?

那我们将慢DSL中中查询的字段修改为只返回_id

1
2
3
4
5
6
7
8
9
10
11
POST /jiankunking_product_prod/_search
{
"size": 10000,
"_source": false,
"query": {
"terms": {
"_id": [""],
"boost": 1
}
}
}

这时候查询耗时只需要203ms,这种情况下还能不能再优化了呢?答案是可以的

索引中文档_id就是产品的code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
POST /jiankunking_product_prod/_search
{
"size": 10000,
"_source": false,
"stored_fields": "_none_",
"docvalue_fields": [
"code"
],
"query": {
"terms": {
"code": [
""
],
"boost": 1
}
}
}

这时候查询只需要76ms

结论

到这里这次优化基本结束了,最终的方案就是

  • 通过从jiankunking_product_prod索引中通过列存获取ids
  • 到MySQL或者新的产品主数据索引中查询具体的产品数据

思考

为啥不直接从jiankunking_product_prod索引中通过列存获取前端需要的数据呢?

因为真实业务场景中需要返回的产品属性虽然每个不大,但总数有20多个,列存在返回字段数多且命中文档大小都不大的场景下,相比原逻辑直接从_source中取会略有下降。

更多原理性解释,可以看下这里:https://jiankunking.com/elasticsearch-source-doc-values-and-store-performance.html

ES适合的场景都有哪些?

目前我这边遇到的场景主要有:

  • 检索加速
    • 数据查询的主存储
      • 当文档大小不是太大的时候,索引检索完直接返回需要的数据
    • 二级索引
      • 针对的就是本文这种场景
  • 日志
    • 应用/容器日志
      • 这里追求的更多是高吞吐的写入
    • 业务日志

具体索引中数据大小是什么情况呢?

分位数 大小 (KB)
0.05 1.16
0.10 1.39
0.15 1.61
0.20 1.69
0.25 1.77
0.30 2.14
0.35 2.97
0.40 3.50
0.45 3.90
0.50 4.24
0.55 4.92
0.60 5.73
0.65 7.15
0.70 8.82
0.75 13.13
0.80 32.32
0.85 57.52
0.90 114.39
0.95 262.47
0.99 989.75

拓展阅读

欢迎关注我的其它发布渠道