能否调整Elasticsearch过滤器执行顺序以优化查询性能?
原始查询
{ "query": { "bool": { "filter": [ { "term": { "userId": { "value": "a_user_id", "boost": 1 } } }, { "range": { "date": { "from": 1648598400000, "to": 1648684799999, "boost": 1 } } }, { "query_string": { "query": "*MyQuery*", "fields": [ "aField^1.0", "anotherField^1.0", "thirdField^1.0" ], "boost": 1 } } ], "boost": 1 } } }
问题背景
移除上述查询中的第三个query_string过滤器后,查询耗时从约2000ms骤降至20ms。前两个userId和date范围过滤器的命中结果始终只有约50条,属于高选择性过滤条件。若能让Elasticsearch先执行这两个过滤器,再对少量结果执行query_string的通配符匹配,即可解决性能瓶颈。
过滤器顺序的规则变化
- 旧版ES(1.x):过滤器顺序直接决定执行优先级,需将高选择性(匹配结果少)的过滤器放在前面,尽早排除无关文档:
"bool子句中过滤器的顺序对性能至关重要。应将更具体的过滤器放在较不具体的过滤器之前,以尽早排除尽可能多的文档。如果子句A匹配1000万文档,子句B仅匹配100条,则应将子句B放在子句A之前。"
- 新版ES:官方说明过滤器/查询的书写顺序不影响执行计划,系统会自动根据计算成本和匹配代价重新排序:
问:查询DSL中查询/过滤器的顺序是否重要?
答:不重要,因为它们会根据各自的成本和匹配成本自动重新排序。
可行的优化方案
虽然新版ES会自动排序,但仍可通过调整查询结构强制优先执行高选择性过滤器,具体方案如下:
1. 嵌套constant_score结构(推荐)
将高选择性的userId和date过滤器放在外层bool的filter中,把query_string包裹在constant_score的filter里嵌套进去。这种结构会引导ES先执行外层过滤,将结果集缩小到约50条后,再对这些文档执行query_string匹配:
{ "query": { "bool": { "filter": [ { "term": { "userId": "a_user_id" } }, { "range": { "date": { "from": 1648598400000, "to": 1648684799999 } } }, { "constant_score": { "filter": { "query_string": { "query": "*MyQuery*", "fields": ["aField^1.0", "anotherField^1.0", "thirdField^1.0"] } } } } ] } } }
2. 调整query_string的执行时机(备选)
将高选择性过滤器保留在filter中,把query_string移到bool的must子句中。这种方式下,ES通常会优先执行filter中的无评分过滤,但效果不如嵌套结构稳定,仅作为备选尝试。
3. 辅助优化:降低query_string的权重
适当降低query_string的boost值(比如设为0.1),让ES认为该查询的优先级更低,间接引导系统优先执行高选择性过滤器。
核心逻辑
通过嵌套结构强制ES先执行高选择性过滤,将待处理文档量从全量缩小到50条左右,再对这少量文档执行高代价的通配符查询,从根本上减少计算量,达到与移除query_string相近的性能表现。
内容的提问来源于stack exchange,提问作者fornwall

