记录一次业务复杂场景下DSL优化的过程
背景
B端商城业务有一个场景就是客户可见的产品列表是需要N多闸口及各种其它逻辑组合过滤的,各种闸口数据及产品数据都是存储在ES的(有的是独立索引,有的是作为产品属性存储在产品文档上)。
在实际使用的过程中,发现接口的毛刺比较严重,而这部分毛刺请求的耗时基本都是花费在从ES中查询产品索引的时候。
开启了一下ES慢DSL的日志
1 | PUT /jiankunking_product_prod/_settings |
经过分析慢DSL日志发现耗时长的部分都是在fetch阶段。
这里有个地方需要注意
1 | [root@jiankunking-search-01: /data/es/logs]# ls -lrth |grep -v .gz |
分析
已知问题点
- 产品文档身上有4个属性会很大
- 属性A(nested属性):可以到几万个
- 属性B(nested属性):可以到几百个
- 属性C(string数组):可以到几万个
- 属性D(大Object):可以到几万个
- ES fetch阶段慢,其实就是从相关分片请求文档内容慢(这时候id其实已经知道了)
大体就是下图这么个流程
下面简化一下请求的DSL,看下移除所有复杂的查询逻辑后,直接按照_id来terms查询效果如何?
DSL
1 | GET /jiankunking_product_prod/_search |
不同文档大小查询时延
当前分析的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 | POST /jiankunking_product_prod/_search |
这时候查询耗时只需要203ms
,这种情况下还能不能再优化了呢?答案是可以的
索引中文档_id就是产品的code
1 | POST /jiankunking_product_prod/_search |
这时候查询只需要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 |
拓展阅读
- https://jiankunking.com/elasticsearch-source-doc-values-and-store-performance.html
- https://jiankunking.com/elasticsearch-scroll-and-search-after.html
- https://luis-sena.medium.com/stop-using-the-id-field-in-elasticsearch-6fb650d1fbae
- https://jiankunking.com/elasticsearch-avoid-the-fetch-phase-when-retrieving-only-id.html
- https://jiankunking.com/elasticsearch-query-secret.html