按long类型字段降序排序时Elasticsearch返回异常数据
现有一个包含1.13亿条文档(约133GB)的Elasticsearch索引,其映射与核心设置如下:
"mappings": { "dynamic": "strict", "properties": { ...some fields..., "id": { "type": "long" } } }, "settings": { "index": { ... "number_of_shards": "1", "provided_name": "users_index", "number_of_replicas": "1", ... } }
执行以下无过滤的排序查询,意图获取最大id:
{ "_source": false, "track_total_hits": true, "size": 1, "sort": { "id": "desc" } }
返回结果中sort值为108384632,但该值并非索引中的最大id——直接查询id=114098981可找到对应文档。
添加id > 108384632的范围过滤后,执行相同排序查询:
{ "_source": false, "track_total_hits": true, "size": 1, "sort": { "id": "desc" }, "query": { "range": { "id": {"gt": 108384632} } } }
得到了正确的最大id结果114186327。
尝试用聚合查询获取最大id时结果异常:
{ "_source": false, "track_total_hits": true, "size": 0, "aggs": { "test": { "terms": { "size": 1, "field": "id", "order": {"_key": "desc"} } } } }
返回的聚合key为44050081,与实际最大id不符。
疑问:该问题是否因索引数据量过大导致Elasticsearch优化时未遍历所有文档?如何正确按id字段排序获取最大id?
异常原因说明
无过滤排序返回错误值:
该索引仅1个分片,排除分片间统计不一致的可能。大概率是索引存在段(Segment)数据未完全合并/刷新的情况——ES会将数据写入多个段,未合并的段可能导致排序时无法正确遍历所有数据;也可能是索引存在局部数据不一致(如写入时的异常导致部分数据未被纳入全局排序统计)。数据量过大只是间接诱因,并非ES主动跳过遍历,而是未处理完所有段数据。Terms聚合结果错误:
这是用法错误,terms聚合的设计是统计字段值的出现频率,返回词频最高的项,即使按_key降序排序,也只会在高频项中取最大值,而非全局范围内的最大id,和数据量无关。
正确获取最大id的方法
方法一:使用Max聚合(最可靠)
Max聚合是专门用于获取字段极值的聚合类型,能准确遍历所有文档返回最大id:{ "_source": false, "size": 0, "aggs": { "max_id": { "max": { "field": "id" } } } }方法二:带范围过滤的排序查询
该方法已验证有效,若不确定范围,可先用Max聚合获取大致范围,再缩小过滤条件执行排序查询。方法三:修复索引段问题
若怀疑是段未合并/刷新导致的排序异常,可先执行强制刷新和段合并操作,再尝试无过滤排序:POST /users_index/_refresh POST /users_index/_forcemerge?max_num_segments=1该操作会将索引的多个段合并为1个,确保所有数据被统一统计,之后无过滤排序大概率能返回正确结果。
内容的提问来源于stack exchange,提问作者Vasiliy Babenko

