MG--索引构造

顾名思义这章就是要谈怎样构造索引的问题,或者说在有限内存和有限时间内,怎么样高效的对大数据集构造索引文件。一旦有了这个索引文件,那么索引的压缩,基于索引的排序,前面的章节都已经讲过。

链接列表

先来看看最一般的方法,在内存中构建这样的数据结构,包含一个term字典,这个字典本身可以用数组,hash表,二分查找树来实现,字典中的每 项,都包含一个指向term的倒排列表的指针,那么对于一个term的倒排列表一般用单项链表来实现,因为这个是动态的,就是说每一项包含文档号,文档内 频率,和下一项指针。

然后遍历每一篇文档,对于文档中的每个term,在字典中如果有就直接把文档号和频率挂在这个term的倒排列表后面,如果没有,先在字典中加上这个term,然后再挂。

当所有文档都处理完了, 我们就在内存中保存了一个完整的倒排索引,最后一步就是把这个内存中的索引存储到磁盘上的一个倒排文件。

那么这个方法,容易理解,又简单看上去很好,但有个问题,就是如果对于大文档集,那内存的消耗是很大的。

那么时间换空间,你可以把链表缓存到数据库,或虚拟内存上面去,当然这个方法不可行,因为效率太低,在index文档时,需要先遍历倒排列表去找到每个term所对应的链表, 这种随机访问会有大量的磁盘读写。

 

基于排序的倒排

对于大文档集的索引,在有限内存情况下,不可能把索引都放在内存里,必然要放到磁盘上,那么对于磁盘的问题是,随机访问的效率很低。所以如果所有索引项都是有序的存放在文件中,那么这样顺序的访问磁盘文件还是比较高效的。

那么什么样的数据结构适合在文件中排序了,答案是三元组 <t, d, fd,t >

这样在index文档时就不用去遍历查找了, 三元组本身就记录了所有的关系,当然三元组会造成数据冗余,在每个三元组中都要保留termid

算法是这样的:

1. 创建空的字典S,和一个磁盘上的临时文件

2. 遍历每一篇文档,对文档中的term,字典中没有的话,加到字典中,有的话,读出termid,组成三元组<t, d, fd,t >并存到临时文件中。

3. 开始排序,因为内存有限不可能把所有三元组都读进来排序,所以要分段排序

就是每次读入内存能够容纳的k条三元组,按termid升序排序,termid相同的,就按docid升序排序

那么这排序后的k条三元组就是一个有序段(sorted run)

然后将有序段写回临时文件

这样不断的读入直到全部处理完

4. 现在的状况就是临时文件里面都是有序段,那么下面要做的就是merge,如果初始有R个有序段,那么通过logR趟(pass)merge,生成最终的有序段,即排序完成,这是个标准的外排序的做法。

5. 最后将这个临时文件顺序读出来生成最终的压缩倒排文件,完成后可删掉临时文件。

这个方法的好处就是一直是顺序读磁盘,没有那种需要查找遍历的随机访问。但有个问题就是会耗费比较多的磁盘,因为你在做两段merge的时候,merge的结果必须存到一个新的临时文件中,就是峰值需要超过原始文件2倍的磁盘空间。

 

索引压缩

前面说了,基于排序的倒排要耗费过多的磁盘资源, 所以下面要谈的是,怎么在创建倒排文件时尽量减少磁盘资源的耗费。

压缩临时文件

临时文件里面都是存放了三元组 <t, d, fd,t >,对于 d, fd,t 的压缩前面在索引压缩时候就谈过,方法很多

那么就来看看t的压缩

在每个有序归并段中,t值是非减的,所以选择差分编码是自然的选择,就是记录这个t和前一个的差值,这个t-gap是零或大于零的整数。

可以直接用一元编码来进行编码,那么可以想象的出,这样存储t所需的空间是很小的

可以看出想要有比较好的压缩效果,必须对有序段做压缩,上面的算法改成这样

对于2,3合并为

遍历每篇文档,取出term组成三元组<t, d, fd,t >这里不直接存到临时文件,先存在内存中,当内存中存放的三元组条数达到k时,对这k条三元组进行排序,然后压缩,最后把这个压缩过的有序段写入临时文件。

4.因为有序段时压缩编码过的,所以在并归的时候要先解码,并归完再压缩编码,写回临时文件

 

原地多路并归

并归阶段是处理器密集型,而不是磁盘密集型, 所以使用多路并归可以进一步降低倒排时间。

推广一下, 假定当前全部的R个并归段进行R路并归,先要从每个并归段读入一个b字节的块,这个b的大小取决于内存大小,这个块是每个并归段的buffer,当一个并归段的块被读完,则从磁盘上再读入一块。

R路并归需要用到最小堆来从候选集合中有效的找到最小值。

那么不断的从堆顶取到最小值写入新的临时文件中, 直到最后完成排序。那么这样我们还是需要2倍原始文件的磁盘耗费,能不能进行原地并归了,下面就介绍一下原地多路并归算法,这个算法比较复杂,也挺有意思

原地并归,就是不占用更多的磁盘空间,并归完后还是写回原临时文件,而不用写入新的临时文件,为达到这个目的有几点是需要认真考虑的

1. 要进行R路并归,会先从每个并归段读入b字节的块到内存,那么容易想到我们只要把排序的结果也组成b字节的块,就可以覆盖到这些已被读过的块空间上,实现原地并归。

问题是每个并归块不一定是b的整数倍,最后读出的一个块可能会小于b,那么这样这个不规则的块会给这个算法带来困扰,那么这个的解决办法很简单,就是padding,对每个并归段后面加上padding,使他一定为b的整数倍。

 

2. 按上面的说法,我们把排好序的块写回被读过的空块中,有个问题是这些空块是零散的,无序的。所以我们需要一个快表block table来记录块的顺序,如block_table[1]表示当前临时文件中的第一块实际的块顺序是多少,比如block_table[1]=3,表示 现在放第一块的应该移到第三块。

那么所以最后我们还要根据block table对临时文件进行重排,以恢复原来的顺序,这个算法是可以在线性时间里完成的

算法如下,算法的目的就是满足block_table[i]=i,

从1到n遍历块表,如果block_table[i] 设为k不等于i, 说明临时文件第i块里面放的不是真正顺序上的第i块,

那么做法是,把当前的第i块放到缓存中,然后去找block_table[j]=i,就是找出顺序上的第i块,放到i位置上

现在第j块空出来了, 看一下k是否等于j,如果等于正好放到j位置上

如果不等于,继续去找block_table[s]=j,放到j位置上,这样一直找下去,直到block_table[k],可以把缓存中的第k块放进去。

当遍历一遍,保证从1到n块都满足block_table[i]=i,则重排完成。

 

3. b值大小的选取,如果b值选的过大会导致内存中放不下R+1个b块,选的过小会耗费过多的磁盘读写次数。

做法是给b一个2的幂形式的初值,这样当b过大时,方便b/2来折半,而且这种方法保证归并段仍然是新的b值的倍数。

那么具体做法就是当b*(R+1) > M时,set b = b/2

 

那么给出原地并归的完整算法,

1. 初始化

    创建空字典S

    创建空临时文件

    设 L为字典占的空间大小

    设 k =(M -L)/w, k为并归段包含三元组的个数,w为一个三元组 <t, d, fd,t >所占用的空间

    设 b = 50Kb

    设 R =0, R为并归段的个数

2. 处理文档和生成临时文件

    遍历每一篇文档D

          parse文档成term

          对于每个term

                如果term在S中存在,取出termid

                在S中不存在

                     把term加到S中

                     更新L,k (因为字典S变大,使可用内存变小,所以k会变小)

                把<t, d, fd,t >加到triple数组中

           任何时候,当triple数组中的triple个数达到k时,生成一个并归段

                 对并归段按term进行排序

                 对各个field进行压缩

                 最后对并归段进行padding,保证是b的倍数,写入临时文件

                 R = R+1

                 当b*(R+1) > M时,set b = b/2

3. 并归

    从每个并归段读入一块,在这些块的number加到freelist里面, 就是说这些块读过了,可以被输出块覆盖

    从每个块取一个triple,共R个triple,建最小堆

    不断取堆顶triple, 放到输出块中, 并从相应的块中取新的triple加到堆中

    当输出块满的时候,从freelist中找一个空的块,如果没有空的,就创建一个新的块append到临时文件后。

    把输出块写入这个空块,更新freelist和block table

    同时当每个并归段读入的块耗完的时候,再读相应并归段的next block,并更新freelist

4. 临时文件重排

5. Truncate 并释放尾部无用的空间

 

压缩的内存内倒排

现在把基于排序的倒排方法放在一边, 回到前面提到的内存内的链结列表方法,这种方法结合压缩技术也可以达到比较好的效果。

现在将要描述的这个方法,基于一个假设就是,每个术语t的文档频率ft 在倒排前已知。 当然想要已知,实现中,就是先遍历统计一遍,第二遍再来建立倒排。

那么费这个劲去预先统计术语t的文档频率ft 有什么好处吗

a. 之前每个term的倒排列表都是用单向链表的结构来实现,因为你不知道到底有多少文档,必须动态的;那么现在你知道文档频率,那么就可以确切知道需要分配多少空间了,可以用数组来代替单项链表,那么至少可以省下这个next指针的耗费。

b. 知道文档的总数N,那么文档编号可以用logN位去编码,而不用整型大小32位

c. 同样知道最大的文档内频率m,也可以用logm位编码fd,t

总之就是说,事先知道一些信息,可以减少数据编码的可能性,也就是说信息熵变小,所以用于编码的位数也就会变小,从而节省了空间。

当然这样做对于内存的占用还是很大,那么又有底下两种方法,来减少内存的消耗

基于字典的切分

基于文本的切分

原理就是把全部倒排列表放内存里面太大,那么就把建倒排的任务分成若干个小任务,每次在内存里面只保留倒排的一部分

基于字典的切分,就是每次只建立和部分term相关的倒排,分多趟覆盖整个字典。

基于文本的切分,就是每次只建立部分文本的倒排,分多趟覆盖整个文本集。

本文章摘自博客园,原文发布日期:2011-07-04

时间: 2024-10-02 03:32:35

MG--索引构造的相关文章

优化器

Oracle 的优化器(Optimizer)实际上是数据库环境的参数设置.可以在INITsid.ORA 文件内的 OPTIMZER_MODE=RULE 或OPTIMZER_MODE=COST 或OPTIMZER_MODE=CHOOSE 来 设置优化目标.用户也可以在会话和查询方式下更改优化器的默认操作模式. 如果OPTIMZER_MODE=RULE,则激活基于规则的优化器(RBO).基于规则的优化器按照一 系列的语法规则来推测可能执行路径和比较可替换的执行路径. 如果OPTIMZER_MODE=

BootStrap 专题

  验证码的输入框和验证码图片在一行,用bootstrap原生的怎么写呢? 看了教程,没有完全一样的可以让右侧的按钮"输入验证码"固定大小.左侧的输入框动态大小吗?   <div class="form-group"> <label for="verify" class="col-sm-4 control-label">验证码</label> <div class="row&

Java实现单向双向链表原理分析

何为链表 链式结构是一种使用对象引用变量来创建对象间的链的数据结构. 对象引用变量可用于创建链式结构 对象引用变量所存储的特定地址一般无关紧要.换句话说,重要的是能够使用引用变量来访问对象,而对象在内存中的特定存储位置并不重要.因此,我们一般将引用变量描述为一个"指向"对象的名称,而不是显示其地址,按照这种理解,引用变量有时称为"指针" 一个指向对象的对象引用变量 下面的这个类就是一个链式结构: 123456 public class Person { privat

PostgreSQL jdbc batch insert

标签 PostgreSQL , jdbc , batch , addbatch , executebatch , insert 背景 如何快速的将数据导入数据库? 比如ETL程序,数据还原程序,数据初始化,数据同步等场景都会有这样的诉求. 从几个方面来分析 1. 统计信息 PostgreSQL会自动统计表的统计信息,包括柱状图等.会有一定的开销,所以在做批量导入时,可以先关闭表的autovacuum. 2. 索引 构造索引,会有一定的CPU和IO开销,影响导入速度,所以可以在数据导入后再建索引.

Sphinx全文检索引擎使用指南:简介

1.1.什么是Sphinx Sphinx 是一个在GPLv2 下发布的一个全文检索引擎,商业授权(例如, 嵌入到其他程序中)需要联系我们(Sphinxsearch.com)以获得商业授权. 一般而言,Sphinx是一个独立的搜索引擎,意图为其他应用提供高速.低空间占用.高结果相关度的全文搜索功能.Sphinx可以非常容易的与SQL数据库和脚本语言集成. 当前系统内置MySQL和PostgreSQL数据库数据源的支持,也支持从标准输入读取特定格式的XML数据.通过修改源代码,用户可以自行增加新的数

透过空间角度 理解GiST索引的构造

标签 PostgreSQL , GIS , PostGIS , Greenplum , 空间检索 , GiST , B-Tree , geohash 背景 可以支持空间检索的GiST索引的数据结果到底是什么样的呢? 本文为以下两篇文档的增补: <Greenplum 空间(GIS)数据检索 B-Tree & GiST 索引实践 - 阿里云HybridDB for PostgreSQL最佳实践> <PostGIS空间索引(GiST.BRIN.R-Tree)选择.优化 - 阿里云RDS

一个例子与InnoDB索引的几个概念

1.一个简单的sql语句问题     假设当前我们有一个表记录用户信息,结构如下:     a)      表结构 CREATE TABLE `u` (   `id` int(11) NOT NULL DEFAULT '0′,   `regdate` int(1) unsigned,   -..   PRIMARY KEY (`id`),   KEY `regdate` (`regdate`) ) ENGINE=InnoDB DEFAULT CHARSET=gbk 说明:1) 由于需要按照注册时

索引-关于B-tree 的bulkloading的构造方法的疑问

问题描述 关于B-tree 的bulkloading的构造方法的疑问 关于B-tree的构造,我见过的很多书包括文章都是通过线性insert操作完成,http://en.wikipedia.org/wiki/B-tree里面就提到:it is frequently useful to build a B-tree to represent a large existing collection of data and then update it incrementally using stan

用Python中的字典来处理索引统计的方法

  这篇文章主要介绍了用Python中的字典来处理索引统计的方法,字典的使用是Python学习当中的基础知识,本文则是相关的一个小实践,需要的朋友可以参考下 最近折腾索引引擎以及数据统计方面的工作比较多, 与 Python 字典频繁打交道, 至此整理一份此方面 API 的用法与坑法备案. 索引引擎的基本工作原理便是倒排索引, 即将一个文档所包含的文字反过来映射至文档; 这方面算法并没有太多花样可言, 为了增加效率, 索引数据尽可往内存里面搬, 此法可效王献之习书法之势, 只要把十八台机器内存全部

在PHP中利用XML技术构造远程服务(2)

xml|远程服务 <?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />  四.基于XML_RPC的Web服务 利用XML_RPC构造和使用服务是很方便的.企业为自己提供的各种服务部署XML_RPC服务器,用户.客户软件和客户企业就可以使用这种服务构造出高端服务或者面向最终用户的应用.这种提供更有效.廉价和优质服务的竞争将极大地提高应用服务的质量. 但这里还存在一些问题有待解决