原创,转载请以连接形式标明作者和出处!
2年前工作中的内容,已成过眼烟云,分享一下,欢迎交流。
sphinx的倒排索引采用紧凑的磁盘存储方式,其docid采用增量压缩方式存储与.spd文件中。
sphinx2.0.1官方版本的.spd文件有三种数据结构:
结构1》[ DocID + (Attr) + HitPosList_Offset + FieldCount + HitCount ] + […] + …
DocID:4或8字节, 增量压缩值(与前一个DocID的差值)
(Attr):属性内容,可配置字段。定长长度,根据具体配置指定,设置属性内联则有该字段。
HitPosList_Offset: 4或8字节,增量压缩值(与前一个HitPosList_Offset的差值)
FieldCount:4字节,压缩值
HitCount: 4字节,为压缩值
结构2》[ DocID + (Attr) + HitCount(1) + HitPos ] + […] + …
DocID:4或8字节,为增量压缩值(与前一个DocID的差值)
(Attr):属性内容,可配置字段。定长长度,根据具体配置指定,设置属性内联则有该字段。
HitCount:4字节,压缩值,此格式该值永远为1
HitPos:4字节,压缩值
结构3》[ DocID + (Attr) + HitCount + FieldCount + HitPosList_Offset ] + […] + …
DocID:4或8字节,增量压缩值(与前一个DocID的差值)
(Attr):属性内容,可配置字段。定长长度,根据具体配置指定,设置属性内联则有该字段。
HitCount:4字节,压缩值
FieldCount:4字节,压缩值
HitPosList_Offset : 4或8字节,压缩值
其中,xx搜索采用了数据结构一。
检索过程中,对查询树进行query_node间的"与"、"或"运算,sphinx采用了多层调用,多路归并的方法来完成。多层调用带来大量的赋值操作,CPU消耗巨大。由于微博本身短小,检索过程只需要使用结构一中的DocID值,不需要HitCount、FieldCount、HitPosList_Offset几个字段,大量赋值操作是没有意义的。因此对spd文件结构进行精简,只保留DocID字段。
xx搜索中有很多条件匹配,多是通过属性字段检索来完成,为了找到符合某属性条件的docid,会进行docid->属性之间的二分查找(微博系统docid本身不连续,无法直接使用docid->属性这种映射,因此只能查询.spa文件的hash索引,该索引在系统启动时创建),当查询节点较多,要求返回结果较大的情况下,二分查找带来的cpu时间消耗是很大的。对于这个问题的解决需要先看一下.spa文件的数据结构:
[DocID0 + mva0_offset + mva1_offset+…attr0+attr1…] + [DocID1 + mva0_offset + mva1_offset+…attr0+attr1…] + [DocID2…] + [DocID3…] + …
DocID:4或8字节。
mvaX_offset:X个8字节,多值属性X在spm中起始偏移。
attrM:M个4字节,固定个数的属性
从数据结构可看出,DocID+属性的分段中DocID是递增的,一个DocID对应着一个属性下标号。因此在.spd文件中直接使用该下标号替代DocID是可行的。.spd中的数据结构长度固定,在检索系统启动时被加载到固定大小的数组中,因此在检索过程中,从.spd文件中取到了一个属性下标号,就可以直接使用这个下标在内存中取到属性内容,不需要再进行查找。
以上对索引数据的修改,涉及到sphinx的索引创建indexer部分。
在创建索引过程中,获取DocID->属性下标的信息,使用AVL树进行存储,在需要写spd文件的DocID增量时查找该树取出下标号并做增量后做为DocID写入,索引创建完成后,释放该树的资源。
在这个过程中有一个隐蔽的问题,.spd文件中标示每个倒排列表结束的0,在检索过程解压倒排列表的时候,当遇到0时既标示倒排列表结束。也就是0在这里有特殊的含义。通过查看spd的文件结构知道,spd在写入倒排列表前,会在文件的最开始写入一个无用的占位字节,目的是为了防止出现偏移为0这类问题,而.spa中的属性下标就是从0开始,所以,使用属性下标替代DocID后可能会出现“docid”差值为0的情况,解决这个问题较简单,只需要在写入属性下标增量时做一个+1操作,在检索过程中,先判断是否是遇到0,当不是0的情况下,将这个获取到的下标号再减1,以此来解决这个隐蔽的问题。