下面详细讲一下为什么 filter 的性能很高,filter 的底层原理究竟是什么?
通过一个搜索的场景来深入剖析一下,当一个 filter 搜索请求打到 Elasticsearch 的时候,ES 会进行下面的操作:
(1)在倒排索引中查找搜索串,获取 document list
以 date 来举例:
word doc1 doc2 doc3
2019-01-01 * *
2019-02-02 * *
2019-03-03 * * *
filter: 2019-02-02
到倒排索引中一找,发现 2019-02-02 对应的 document list 是 doc2,doc3
(2)为每个在倒排索引中搜索到的结果,构建一个 bitset
这一步是非常重要的,使用找到的 doc list,构建一个 bitset,就是一个二进制的数组,数组的每个元素都是 0 或 1,用来标识一个 doc 对一个 filter 条件是否匹配,如果匹配的话值就是 1,不匹配值就是 0。
所以上面的 filter 的 bitset 的结果就是:
[0,1,1]
doc1:不匹配这个 filter 的
doc2 和 doc3:匹配这个 filter 的
注意:这样做的好处就是尽可能用简单的数据结构去实现复杂的功能,可以节省内存空间,提升性能。
(3)遍历每个过滤条件对应的 bitset,优先从最稀疏的开始搜索,查找满足所有条件的 document
由于一次性可以在一个 search 请求中发出多个 filter 条件,那么就会产生多个 bitset,遍历每个 filter 条件对应的 bitset 优先从最稀疏的开始遍历
[0,0,0,0,0,0,0,1] 比较稀疏的 bitset
[1,0,1,1,0,1,0,1]
这里主要是因为先遍历比较稀疏的 bitset,就可以先过滤掉尽可能多的数据
(4)caching bitset
caching bitset 会跟踪 query,在最近 256 个 query 中超过一定次数的过滤条件,缓存其 bitset。对于小 segment(<1000 或 <3%),不缓存 bitset。这样下次如果在有这个条件过来的时候,就不用重新扫描倒排索引,反复生成 bitset,可以大幅度提升性能。
说明:
1、在最近的 256 个 filter 中,有某个 filter 超过了一定次数,这个次数不固定,那么 elasticsearch 就会缓存这个 filter 对应的 bitset
2、filter 针对小的 segment 获取到的结果,是可以不缓存的,segment 记录数小于 1000,或者 segment 大小小于 index 总大小的 3%。因为此时 segment 数据量很小,哪怕是扫描也是很快的;segment 会在后台自动合并,小 segment 很快会跟其它小 segment 合并成大 segment,此时缓存就没有什么意思了,segment 很快会消失。
filter 比 query 好的原因除了不计算相关度分数以外还有这个 caching bitset。所以 filter 性能会很高。
(5)filter 大部分的情况下,是在 query 之前执行的,可以尽可能过滤掉多的数据
query: 会计算每个 doc 的相关度分数,还会根据这个相关度分数去做排序
filter: 只是简单过滤出想要的数据,不计算相关度分数,也不排序
(6)如果 document 有新增和修改,那么 caching bitset 会被自动更新
这个过程是 ES 内部做的,比如之前的 bitset 是 [0,0,0,1]。那么现在插入一条数据或是更新了一条数据 doc5,而且 doc5 也在缓存的 bitset[0,0,0,1] 的 filter 查询条件中,那么 ES 会自动更新这个 bitset,变为[0,0,0,1,1]
(7)以后只要有相同的 filter 条件的查询请求打过来,就会直接使用这个过滤条件对应的 bitset
这样查询性能就会很高,一些热的 filter 查询,就会被 cache 住。