記錄一次業務復雜場景下DSL優化的過程
背景
B端商城業務有一個場景就是客戶可見的產品列表是需要N多閘口及各種其它邏輯組合過濾的,各種閘口數據及產品數據都是存儲在ES的(有的是獨立索引,有的是作為產品屬性存儲在產品文檔上)。
在實際使用的過程中,發現接口的毛刺比較嚴重,而這部分毛刺請求的耗時基本都是花費在從ES中查詢產品索引的時候。
開啟了一下ES慢DSL的日志
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階段。
這里有個地方需要注意
[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會被截斷
分析
已知問題點
- 產品文檔身上有4個屬性會很大
- 屬性A(nested屬性):可以到幾萬個
- 屬性B(nested屬性):可以到幾百個
- 屬性C(string數組):可以到幾萬個
- 屬性D(大Object):可以到幾萬個
- ES fetch階段慢,其實就是從相關分片請求文檔內容慢(這時候id其實已經知道了)
大體就是下圖這么個流程
下面簡化一下請求的DSL,看下移除所有復雜的查詢邏輯后,直接按照_id來terms查詢效果如何?
DSL
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 | ??,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
POST /jiankunking_product_prod/_search
{"size": 10000,"_source": false,"query": {"terms": {"_id": [""],"boost": 1}}
}
這時候查詢耗時只需要203ms
,這種情況下還能不能再優化了呢?答案是可以的
索引中文檔_id就是產品的code
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 |
拓展閱讀
- 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
- https://www.elastic.co/guide/en/elasticsearch/reference/current/general-recommendations.html