Elasticsearch Painless查询index_out_of_bounds_exception异常排查
问题原因
- 分片部分成功部分失败是Elasticsearch分布式查询的正常表现:查询请求会分发到所有关联分片独立执行,协调节点会汇总所有返回的分片结果,你使用的客户端配置了「任意分片失败即抛出整体错误」的策略,因此哪怕4个分片返回了匹配结果,只要1个分片执行报错,客户端就会直接抛出异常。
index_out_of_bounds_exception的直接触发原因是脚本逻辑存在边界缺失,且问题分片上存在格式不一致的脏文档:- 你的脚本逻辑默认遍历
doc['collection']时,自增生成的下标index0/index1,一定能在creativeCollectionProperties字段下找到对应的{n}_index层级节点。 - 失败分片上存储的部分文档,
collection数组长度大于creativeCollectionProperties下实际存在的下标节点数量:比如某条文档collection有3个元素,但creativeCollectionProperties下仅存在0_index、1_index两个子属性,当循环自增到下标2、尝试访问creativeCollectionProperties.2_index.xxx路径时就会触发越界。 - 你脚本中写的
doc.containsKey()判断在ES部分版本中无法拦截这类路径越界问题:当路径对应的字段节点不存在时,直接调用.size()、.value方法会直接抛出数组越界异常。 - 其余同结构索引没有触发问题,仅因为这些索引内的文档刚好满足
collection长度与属性长度完全一致,不存在这类数据不一致的情况。
- 你的脚本逻辑默认遍历
修复方案
- 补全脚本的边界判断逻辑:循环内每次访问拼接的字段路径前,先获取
creativeCollectionProperties相关字段的实际可访问长度,若当前自增下标超过实际长度,直接终止循环,不要访问不存在的路径。 - 简化脚本冗余逻辑,规避不稳定写法:
- 删除
Long.parseLong(131784.toString())这类无意义的类型转换,直接使用131784L表示长整型数值,减少执行开销 - 多值字段判断长度大于0后,直接用
doc[fieldPath][0]获取首个值,不要直接调用.value,避免隐式类型转换触发的不可预期错误
- 删除
- 若业务不需要分片部分返回结果,可以在查询顶层增加参数
"allow_partial_search_results": false,从ES服务层面禁止部分分片成功时返回结果,统一错误返回逻辑。
修复后的脚本参考:
{ "query": { "bool": { "must": [ {"term": {"type": {"value": 2}}}, {"term": {"channels": {"value": 94}}}, {"term": {"active": {"value": true}}} ], "filter": { "script": { "script": """ int successCount = 2; int successIndex = 0; // 提前获取关联字段最大可访问长度,做边界校验 int maxIndex = doc['collection'].size(); // 第一组匹配逻辑 int oneCombinationSuccessCount0 = 1; int index0 = 0; boolean productFound0 = false; for (def c : doc['collection']) { if (productFound0 || index0 >= maxIndex) { break; } int oneCombinationSuccessIndex0 = 0; def key = 'creativeCollectionProperties.' + index0 + '_index'; if (doc.containsKey(key+'.product_category') && doc[key+'.product_category'].size() > 0) { def catVal = doc[key+'.product_category'][0]; if (catVal == 131784L || doc[key+'.product_category'].contains(131784L)) { oneCombinationSuccessIndex0 += 1; } } index0++; if (oneCombinationSuccessIndex0 >= oneCombinationSuccessCount0) { successIndex +=1; productFound0 = true; } } // 第二组匹配逻辑 int oneCombinationSuccessCount1 = 2; int index1 = 0; boolean productFound1 = false; for (def c : doc['collection']) { if (productFound1 || index1 >= maxIndex) { break; } int oneCombinationSuccessIndex1 = 0; def key = 'creativeCollectionProperties.' + index1 + '_index'; if (doc.containsKey(key+'.product_category') && doc[key+'.product_category'].size() > 0) { def catVal = doc[key+'.product_category'][0]; if (catVal == 5818L || doc[key+'.product_category'].contains(5818L)) { oneCombinationSuccessIndex1 +=1; } } if (doc.containsKey(key+'.generated_color') && doc[key+'.generated_color'].size() > 0) { def colorVal = doc[key+'.generated_color'][0]; if (colorVal == 123324L || doc[key+'.generated_color'].contains(123324L)) { oneCombinationSuccessIndex1 +=1; } } index1++; if (oneCombinationSuccessIndex1 >= oneCombinationSuccessCount1) { successIndex +=1; productFound1 = true; } } return successIndex >= successCount; """ } } } }, "allow_partial_search_results": false }
额外优化建议:这类多条件关联过滤逻辑尽量不要用Painless脚本实现,性能比原生查询低1~2个数量级,数据量较大时会严重影响查询延迟,可以重构索引映射使用nested类型存储关联属性,用原生查询替代脚本过滤。
内容的提问来源于stack exchange,提问作者Hovo Baghdasaryan
相关产品推荐
相关产品推荐

