子句说明
分层查询一种查询语法的扩充,主要功能是增强查询语法对于召回过程的控制能力,让应用可以根据自己的使用场景来加速文档召回过程,提高系统整体性能,扩充的功能主要有以下几点:
允许用户通过某些方式选择召回的区间
对于不同区间可以选择召回的先后顺序
对于不同区间可以指定不同的query来影响召回集
子句语法
名词介绍
name | description |
seek | 查询过程中,查询到一个doc的操作 |
docid | HA3内部对文档的编号,查询时会按docid从小到大查询。 |
[查询]区间/range | 查询时一个docid的范围,这里的range是指会被查询的docid的范围。 |
[查询]层/layer | 层是由一个或者多个range组成的,层主要作用是决定查询区间的优先级(把不同range放到不同层)。[查询]层/layer[查询]区间/rangedocidseekname |
查询语法
{
"layer" : [
]
}
选择召回区间
对于召回区间的控制主要通过layer语法来实现:
一个layer子句可以由多个single_layer组成,一个single_layer主要有两个元素:
quota:用于设置当前层建议召回的doc个数,这里有几个点需要注意:
quota与rank_size的关系:各层quota总和不会大于rank_size,例如rank_size=10,quota:5;quota:7,这样最终第二层的quota会被截断到5。
当前层quota有剩余的情况下,会自动把quota累加到下一层。
由于层内的每个区间之间的doc不一定满足前面的doc比后面doc要好,所以quota有两种作用机制,一种是每查找到一个doc,就检查一次quota是否有剩余,另一种是在当前层内无视quota限制(依旧被rank_size限制),查找完当前层,最后看是否有剩余,若有剩余则累加到下一层
当用户显示指定layer子句的时候,每层quota默认值是0,quota的最大值为uint32_t的最大值。
range :用于确定当前层的doc范围,如果不写,范围区间为所有doc,及[0,docCount)。range语法基本工作原理是通过用户给定的attribute,逐级计算最终需要seek的doc范围。需要注意的点:
语法中用到的必须是attribute,不能是需要计算的表达式
语法中用到的attribute必须与离线排序方式相符,否则会自动转换为查询全部区间。
语法中用到的attribute必须要是连续出现的,不可以中间插入扩展关键字(%sorted,%docid等)
除了attribute外我们还支持几种扩展语句:%sorted(这一层查询排过序的全量和增量),%unsorted(这一层查询未排序的数据,实时数据等),%other(除去前面层以外的范围),%docid(直接指定docid区间查询),%segmentid(直接指定segmentid区间查询),%percent(指定这个范围百分比范围的区间查询)
如果用户在分层子句中没有指定%sorted,%unsorted关键字,引擎会自动为用户添加,默认模式是每层都是sorted,最后如果结果数不够会为用户多查一层实时数据
选择不同区间的不同query
对于不同查询层,可以使用不同的查询词,或者选择不同的倒排链。不同query通过分号隔开,当layer的数目大于query数目时,会自动用最后一条query填充剩余层。
使用示例:
本节将介绍分层查询与一些引擎提供的其他功能结合使用的场景下,如何搭配使用分层查询。
构建索引时文档排序,查询指定召回区间
在使用离线的排序功能时,文档是全局有序的,例如,按照站点排序后,同一个站点的文档在索引中是连续的一段,当在某个站点内部查询时,可以利用这个信息来加快seek的速度,假定站点对应的attribute是site_id,查询的站点ID为1和7,查询词为iphone:
{
"layer" : [
{
"range" : {
"fields" : [
{
"field" : "site_id",
"values" : [1,7]
}
]
},
"quota" : 5000
}
]
}
或者如果站点1和7中查询结果不够的情况下,用户还想查询站点5和10的结果作为补充,可以这样:
{
"layer" : [
{
"range" : {
"fields" : [
{
"field" : "site_id",
"values" : [1,7]
}
]
},
"quota" : 5000
},
{
"range" : {
"fields" : [
{
"field" : "site_id",
"values" : [5,10]
}
]
},
"quota" : 0
}
]
}
由于是希望第一层结果不够的情况下才查询第二层,所以第二层的quota设置为0(或者不写)。当然,用户还可以进行多样性召回:
{
"layer" : [
{
"range" : {
"fields" : [
{
"field" : "site_id",
"values" : [1,7]
}
]
},
"quota" : 4000
},
{
"range" : {
"fields" : [
{
"field" : "site_id",
"values" : [5,10]
}
]
},
"quota" : 1000
}
]
}
对于离线排序是多维的情况,也可以支持多维区间的定位,还是以站内查询为例,离线排序是先按站点排序,站点相同的,按照网页的静态分排序,这种时候,查询希望召回静态分大于100的网页,查询语法如下:
{
"layer" : [
{
"range" : {
"fields" : [
{
"field" : "site_id",
"values" : [1,7]
},
{
"field" : "static_score",
"values" : "[100,]"
}
]
},
"quota" : 4000
}
]
}
注:区间范围表示加双引号
多query查询
在某些情况下,用户的查询即担心召回数目是否足够,又希望召回数不能太多,导致影响查询性能,比较常见的一类场景是,有多个查询词,以A,B为例:
query方式 | 召回数 | 性能 |
A AND B | 最少 | 最好 |
A OR B | 最多 | 最差 |
A RANK B/B RANK A | 适中 | 中等 |
上述的几种query方式,对于A和B的召回数和性能都不一样,大部分查询希望的是有较好的召回,而且召回数不需要太多,这种情况下,固定用某种查询方式,很难保证结果数和性能都适中。使用多query的方式就可以通过一次查询来解决这个问题:
{
"query": "A OR B;A RANK B;A AND B",
"layer" : [
{
"quota" : 1000
},
{
"quota" : 1000
},
{
"quota" : 1000
}
]
}
这种查询方式可以兼顾召回文档数目和性能,对于查询词的关联程度也有比较大的提升(对于大召回可以命中一些A AND B的结果,对于小召回可以通过A OR B来尽可能多召回)。
需要考虑时效性的查询
有时候用户会希望查询结果的时效性比较好,在这种情况下用户会希望先查询实时的无序索引,之后再查询全量和增量的有序索引,用户也可以在有序索引中指定先查询后面的一部分数据,再查前面的一部分数据,可以用percent实现
例如用户想要先查询时效性最好的实时索引,结果不够的话查询有序索引中后50%的doc,最后查询前50%的doc,查询词为iphone:
{
"layer" : [
{
"range" : {
"index_type" : "%unsorted"
},
"quota" : 5000
},
{
"range" : {
"index_type" : "%sorted",
"fields" : [
{
"field" : "service_id",
"values" : [1,3]
}
],
"percent" : "[50,100)"
},
"quota" : 0
},
{
"range" : {
"index_type" : "%sorted",
"fields" : [
{
"field" : "service_id",
"values" : [1,3]
}
],
"percent" : "[0,50)"
},
"quota" : 0
}
]
}
注:percent关键字中可以指定不同多个区间,区间是左闭右开的
注意事项
layer子句是可选子句