最近我们在公司内尝试用ES替换老旧的Solr, 在性能对比测试的环节, 发现ES竟然比Solr慢了非常多, 响应时间是Solr的两三倍, 然后开始各种排查, 最后发现ES的响应时间竟然随着request.size的增加呈线性增加, 这说明大部分时间都耗在了获取返回字段上面. 而我们目前在召回时并未获取很多字段, 只获取了UID(我们自己定义的一个基于docvalues列存的字段)和score. 按照ES的query-then-fetch召回模式来说, score应该是在query阶段生成, 在fetch阶段应该只需要读取UID, 而UID是基于列存的, 没有理由会随着request.size的增加而线性增长.
因此有一个初步的猜想, 就是执行fetch阶段时可能不符合我们的预期.
让我们来看看官方文档里提供的获取字段的几种方式.
params['_source']['my_field']
.doc['my_field'].value
.测试: 在返回4000条文档的UID,score属性的测试中, 配置不同的返回字段参数的响应时间如下:
"_source":{
"include":["UID"]
},
120ms +
"fields":["UID"],
"_source":false,
110ms +
"docvalue_fields":["UID"],
"_source":false,
110ms +
"docvalue_fields":["UID"],
"stored_fields": "_none_",
"_source":false,
20ms +
很显然, 使用"stored_fields": "_none_"
的响应时间相比简单的使用_source要减少100ms, 性能要提升5倍多.
根据官方文档的说法, "stored_fields": "_none_"
是完全禁掉了包括_source在内的store字段.
目前还有两个疑问:
"_source":false
的时候性能无明显提升呢? 难道即便这样设置, ES依然会从硬盘上读取_source吗? 这听起来不是很合理啊.为什么当设置了
"_source":false
的时候性能无明显提升呢? 难道即便这样设置, ES依然会从硬盘上读取_source吗? 这听起来不是很合理啊.
通过阅读源码知道, 当设置了"_source":false
的时候, ES确实没有读取_source, 但是会默认读取两个字段: _id和_routing, 这两个字段是ES内置的, 正常情况下无法查看其字段类型, 但是我们可以通过Luke工具查看:
通过查看我们得知, 这两个字段是仅索引的, 既没有存docvalues也没有存stored. 那么ES是如何读取的呢? 答案是通过fielddata cache. 第一次试图召回_id字段的时候, ES会根据其倒排索引结构, 在堆内存中构建fielddata cache并缓存. fielddata cache就是把倒排索引结构反转为正排索引, 这样一来就相当于在内存中构建了_id字段的列存. 缺陷是第一次请求因为要构建fielddata cache会慢.
因此仅仅设置"_source":false
是不够的, 如果不需要召回_id和_routing的话, 应该设置"stored_fields": "_none_"
. 而且官方文档其实也指出了这一点:
因为是query_then_fetch的模式, 这样在fetch阶段, 每个shard需要获取字段的文档数应该接近size/shard_size, 假设有20个shard, 那么平均每个shard只需要获取4000/20=200个文档, 并且多个shard是并发执行的, 这个过程会增加100ms那么多时间吗?
这个问题暂时还没搞清楚.
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。