背景:用户反馈查询耗时过长,时常有慢查询发生,业务搜索请求超时。
搜索样例请求如下:
代码语言:json复制GET index_name/_search
{
"from": 0,
"size": 10,
"query": {
"bool": {
"must_not": [
{
"bool": {
"should": [
{
"bool": {
"must": [
{
"exists": {
"field": "deleted_at",
"boost": 1
}
}
],
"adjust_pure_negative": true,
"boost": 1
}
},
{
"term": {
"deleted": {
"value": true,
"boost": 1
}
}
}
],
"adjust_pure_negative": true,
"minimum_should_match": "1",
"boost": 1
}
}
],
"adjust_pure_negative": true,
"boost": 1
}
},
"version": true,
"sort": [
{
"created_at": {
"order": "desc"
}
}
],
"aggregations": {
"groupCurrency": {
"terms": {
"field": "order_currency",
"size": 10,
"min_doc_count": 1,
"shard_min_doc_count": 0,
"show_term_doc_count_error": false,
"order": [
{
"_count": "desc"
},
{
"_key": "asc"
}
]
},
"aggregations": {
"total": {
"value_count": {
"field": "id"
}
},
"paymentAmountTotal": {
"sum": {
"field": "payment_amount"
}
},
"actualPaymentAmountTotal": {
"sum": {
"field": "actual_payment_amount"
}
},
"serviceFeeTotal": {
"sum": {
"field": "service_fee"
}
},
"couponAmountTotal": {
"sum": {
"field": "order_platform_coupon_discount"
}
}
}
}
}
}
DSL分析
用户的索引是一个update场景的索引,会产生一定的doc.deleted,同时也会有较多的segment产生;
在bool查询中,用户进行了判断deleted_at字段必须存在,或deleted字段值是true的数据过滤,并对结果集进行must_not的取反。同时指定了"minimum_should_match": "1"用来约束这个bool查询中的两个子句必须同时被满足。
Aggregations聚合的字段类型主要为scaled_float,并设置scaling_factor(比例因子)为100
scaled_float类型是一种基于long类型数字进行比例缩放的数据类型。
该类型的优点:能够更精确的统计小数并节省磁盘空间;因为整数比浮点数更易于压缩。
必须指定缩放因子scaling_factor。ES索引时,原始值会乘以该缩放因子并四舍五入得到新值,ES内部储存的是这个新值,但返回结果仍是原始值。使用比例因子的好处是整型比浮点型更易压缩,节省磁盘空间。
注意: scaling_factor属性是只针对scaled_float这个数据类型才有,不要在其他类型上使用此属性。
例如:字段a的值为0.1 当将字段a的类型设置为scaled_float,并设置scaling_factor为100,在存储时这个数值就会被存储为0.1*100的一个整数。
使用profile分析查询耗时,基本都是在BooleanQuery和aggregations阶段,aggregations阶段耗时比较大;主要是count和sum消耗了较多时间。
在agg聚合中使用的字段没有keyword类型,所以不存在高基数字段导致聚合过多而出现慢查询或性能下降的问题。
关于高基数字段全局序数的说明
全局序数用于在 keyword字段上 运行 terms aggregations; es不知道哪些fields将用于/不用于 term aggregation,因此全局序数可以在需要时才加载进内存;通过在mapping type上定义 eagerglobalordinals=true,这样在refresh时就会加载全局序数;
优化点
1. mapping中long类型建议采用keyword类型(long改为keyword是要分场景的,如果客户基于这个字段range查询比较多就不应改,如果keyword查询比较多才建议更改)
2. segment太多,而且都不大,建议做下forcemerge
3. 避免每次构建global ordinal,可以关闭这个参数(不使用全局序数会占用较多的内存)
代码语言:javascript复制PUT /_cluster/settings
{
persistent": {
"search.composite_aggregations.use_global_ordinal": false
}
}
注:es默认每次聚合会构建一次该索引1全量的global_ordinals(本质是一个正排索引) 用于缓存加速下一次聚合,有新写入就会导致该缓存失效,下次查询再出发全量构建。map 告诉es直接用query到的value做聚合,避免构建global ordinal的过程;
新写入是新的segment,老的segment并没有改变,每个segment的序数关系也没有改变,所以全局序数记录的和老的segment序数映射关系不用改变,如果新加的segment比较快速,第一次查询时是会有一些影响,但是不使用全局序数建议字段的基数不能太大;
建议
1. 对segment做合并
代码语言:javascript复制POST index_name/_forcemerge
2. 关闭"search.composite_aggregations.use_global_ordinal": false
我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!