<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Information Retrieval Blog &#187; indexing</title>
	<atom:link href="http://blog.zye.me/tag/indexing/feed" rel="self" type="application/rss+xml" />
	<link>http://blog.zye.me</link>
	<description>REAL TIME DATA PROCESSING, DISTRIBUTED COMPUTING, PATTERN DISCOVERY</description>
	<lastBuildDate>Wed, 08 Feb 2012 17:33:32 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>如何读取Lucene索引数据1&#8211;整理中</title>
		<link>http://blog.zye.me/2011/09/3886.html</link>
		<comments>http://blog.zye.me/2011/09/3886.html#comments</comments>
		<pubDate>Sat, 10 Sep 2011 01:52:04 +0000</pubDate>
		<dc:creator>yezheng</dc:creator>
				<category><![CDATA[information Retrieval]]></category>
		<category><![CDATA[indexing]]></category>
		<category><![CDATA[lucene]]></category>
		<category><![CDATA[信息检索]]></category>
		<category><![CDATA[索引]]></category>

		<guid isPermaLink="false">http://www.5yiso.cn/articles/%e5%a6%82%e4%bd%95%e8%af%bb%e5%8f%96lucene%e7%b4%a2%e5%bc%95%e6%95%b0%e6%8d%ae1-%e6%95%b4%e7%90%86%e4%b8%ad.html</guid>
		<description><![CDATA[Lucene源码分析(1) &#8212; 如何读取Lucene索引数据 终于清楚如何用读Lucene的索引 。本文要介绍一下如何利用IndexReader获取信息。为什么要读索引呢？因为我需要实现这些功能： (1) 统计term在整个collection中的文档频度(document frequency, DF)； (2) 统计term在整个collection中出现的词次(term frequency in whole collection)； (3) 统计term在某个文档中出现的频度(term frequency, TF)； (4) 列出term在某文档中出现的位置(position)； (5) 整个collection中文档的个数； 那 么为什么要用到这些数据呢？这些数据是实现TR(Text Retrieval，文本检索)的必备的&#8221;原料&#8221;，而且是经过加工的。在检索之前，只有原始文本(raw data)；经过索引器(indexer)的处理之后，原始文本变成了一个一个的term(或者token)，然后被indexer纪录下来所在的位置、 出现的次数。有了这些数据，应用一些模型，就可以实现搜索引擎实现的功能――文本检索。 聪明的读 者您可能会说，这看起来似乎很好做，不过就是计数(count)么。不错，就是计数，或者说是统计。但是看似简单的过程，如果加上空间(内存容量)的限 制，就显得不那么简单了。假设如果每篇文档有100个term，每个term需要存储10字节信息，存1,000,000篇文档需要 10x100x10^6=10^9=2^30字节，也就是1GB。虽然现在1G内存不算什么，可是总不能把1GB的数据时时刻刻都放入内存吧。那么放入硬 盘好了，现在需要用数据的时候，再把1GB数据从硬盘搬到内存。OK，可以先去冲杯咖啡，回来在继续下面的操作。这是1,000,000的文档，如果更多 一点呢，现在没有任何辅助数据结构的方式，会导致很差的效率。 Lucene的索引会把数据分成 段，并且在需要的时候才读，不需要的时候就让数据乖乖地呆在硬盘上。Lucene本身是一个优秀的索引引擎，能够提供有效的索引和检索机制。文本的目的 是，介绍如用利用Lucene的API，如何从已经建好的索引的数据中读取需要的信息。至于Lucene如何使用，我会在后续的文章中逐渐介绍。 我们一步一步来看。这里建设已经有实现建好索引，存放在index目录下。好，要读索引，总得先生成一个读索引器(即Lucene中IndexReader的实例)。好，写下面的程序(程序为C#程序，本文使用DotLucene)。 IndexReader reader; 问 题出来了，IndexReader是一个abstract类，不能实例化。那好，换派生类试试看。找到IndexReader的两个孩子 ――SegmentReader和MultiReader。用哪个呢？无论是哪个都需要一大堆参数(我是颇费了周折才搞清楚它们的用途，后面再解释)，似 乎想用Lucene的索引数据不是那么容易啊。通过跟踪代码和查阅文档，我终于找到使用IndexReader的钥匙。原来IndexReader有一个 &#8220;工厂模式&#8221;的static interface――IndexReader.Open。定义如下： #0001 public static IndexReader Open(System.String path) #0002 public static <a href='http://blog.zye.me/2011/09/3886.html'>[...]</a>]]></description>
			<content:encoded><![CDATA[<p>Lucene源码分析(1) &#8212; 如何读取Lucene索引数据</p>
<p id="msgcns!3BB36966ED98D3E5!408" class="bvMsg"><font face="Verdana"></font><font size="2">终于清楚如何用读Lucene的索引 <img src='http://blog.zye.me/wp-includes/images/smilies/icon_smile.gif' alt=':-)' class='wp-smiley' /> 。本文要介绍一下如何利用IndexReader获取信息。为什么要读索引呢？因为我需要实现这些功能：<br />
(1) 统计term在整个collection中的文档频度(document frequency, DF)；<br />
(2) 统计term在整个collection中出现的词次(term frequency in whole collection)；<br />
(3) 统计term在某个文档中出现的频度(term frequency, TF)；<br />
(4) 列出term在某文档中出现的位置(position)；<br />
(5) 整个collection中文档的个数；</font></p>
<p><font face="Verdana"></font><font size="2">那 么为什么要用到这些数据呢？这些数据是实现TR(Text Retrieval，文本检索)的必备的&#8221;原料&#8221;，而且是经过加工的。在检索之前，只有原始文本(raw data)；经过索引器(indexer)的处理之后，原始文本变成了一个一个的term(或者token)，然后被indexer纪录下来所在的位置、 出现的次数。有了这些数据，应用一些模型，就可以实现搜索引擎实现的功能――文本检索。</font></p>
<p><font face="Verdana"></font><font size="2">聪明的读 者您可能会说，这看起来似乎很好做，不过就是计数(count)么。不错，就是计数，或者说是统计。但是看似简单的过程，如果加上空间(内存容量)的限 制，就显得不那么简单了。假设如果每篇文档有100个term，每个term需要存储10字节信息，存1,000,000篇文档需要 10x100x10^6=10^9=2^30字节，也就是1GB。虽然现在1G内存不算什么，可是总不能把1GB的数据时时刻刻都放入内存吧。那么放入硬 盘好了，现在需要用数据的时候，再把1GB数据从硬盘搬到内存。OK，可以先去冲杯咖啡，回来在继续下面的操作。这是1,000,000的文档，如果更多 一点呢，现在没有任何辅助数据结构的方式，会导致很差的效率。</font></p>
<p><font face="Verdana"></font><font size="2">Lucene的索引会把数据分成 段，并且在需要的时候才读，不需要的时候就让数据乖乖地呆在硬盘上。Lucene本身是一个优秀的索引引擎，能够提供有效的索引和检索机制。文本的目的 是，介绍如用利用Lucene的API，如何从已经建好的索引的数据中读取需要的信息。至于Lucene如何使用，我会在后续的文章中逐渐介绍。</font></p>
<p><font face="Verdana"></font><font size="2">我们一步一步来看。这里建设已经有实现建好索引，存放在index目录下。好，要读索引，总得先生成一个读索引器(即Lucene中IndexReader的实例)。好，写下面的程序(程序为C#程序，本文使用DotLucene)。<br />
IndexReader reader;<br />
问 题出来了，IndexReader是一个abstract类，不能实例化。那好，换派生类试试看。找到IndexReader的两个孩子 ――SegmentReader和MultiReader。用哪个呢？无论是哪个都需要一大堆参数(我是颇费了周折才搞清楚它们的用途，后面再解释)，似 乎想用Lucene的索引数据不是那么容易啊。通过跟踪代码和查阅文档，我终于找到使用IndexReader的钥匙。原来IndexReader有一个 &#8220;工厂模式&#8221;的static interface――IndexReader.Open。定义如下：<br />
#0001 public static IndexReader Open(System.String path)<br />
#0002 public static IndexReader Open(System.IO.FileInfo path)<br />
#0003 public static IndexReader Open(Directory directory)<br />
#0004 private static IndexReader Open(Directory directory, bool closeDirectory)<br />
其中有三个是public的接口，可供调用。打开一个索引，就是这么简单：<br />
#0001 IndexReader reader = IndexReader.Open(index);</font></p>
<p><font face="Verdana"></font><font size="2">实际上，这个打开索引经历了这样的一个过程：<br />
#0001 SegmentInfos infos = new SegmentInfos();<br />
#0002 Directory directory = FSDirectory.GetDirectory(index, false);<br />
#0003 infos.Read(directory);<br />
#0004 bool closeDirectory = false;<br />
#0005 if (infos.Count == 1)<br />
#0006 {<br />
#0007 // index is optimized<br />
#0008 return new SegmentReader(infos, infos.Info(0), closeDirectory);<br />
#0009 }<br />
#0010 else<br />
#0011 {<br />
#0012 IndexReader[] readers = new IndexReader[infos.Count];<br />
#0013 for (int i = 0; i &lt; infos.Count; i++)<br />
#0014 readers[i] = new SegmentReader(infos.Info(i));<br />
#0015 return new MultiReader(directory, infos, closeDirectory, readers);<br />
#0016 }</font></p>
<p><font face="Verdana"></font><font size="2">首 先要读入索引的段信息(segment information, #0001~#0003)，然后看一下有几个段：如果只有一个，那么可能是优化过的，直接读取这一个段就可以(#0008)；否则需要一次读入各个段 (#0013~#0014)，然后再拼成一个MultiReader(#0015)。打开索引文件的过程就是这样。</font></p>
<p><font face="Verdana"></font><font size="2">接下来我们要看看如何读取信息了。用下面这段代码来说明。<br />
#0001 public static void PrintIndex(IndexReader reader)<br />
#0002 {<br />
#0003 //显示有多少个document<br />
#0004 System.Console.WriteLine(reader + &#8220;tNumDocs = &#8221; + reader.NumDocs());<br />
#0005 for (int i = 0; i &lt; reader.NumDocs(); i++)<br />
#0006 {<br />
#0007 System.Console.WriteLine(reader.Document(i));<br />
#0008 }<br />
#0009<br />
#0010 //枚举term，获得&lt;document, term freq, position* &gt;信息<br />
#0011 TermEnum termEnum = reader.Terms();<br />
#0012 while (termEnum.Next())<br />
#0013 {<br />
#0014 System.Console.Write(termEnum.Term());<br />
#0015 System.Console.WriteLine(&#8220;tDocFreq=&#8221; + termEnum.DocFreq());<br />
#0016<br />
#0017 TermPositions termPositions = reader.TermPositions(termEnum.Term());<br />
#0018 int i = 0;<br />
#0019 int j = 0;<br />
#0020 while (termPositions.Next())<br />
#0021 {<br />
#0022 System.Console.WriteLine((i++) + &#8220;-&gt;&#8221; + &#8221; DocNo:&#8221; + termPositions.Doc() + &#8220;, Freq:&#8221; + termPositions.Freq());<br />
#0023 for (j = 0; j &lt; termPositions.Freq(); j++)<br />
#0024 System.Console.Write(&#8220;[" + termPositions.NextPosition() + "]&#8220;);<br />
#0025 System.Console.WriteLine();<br />
#0026 }<br />
#0027<br />
#0028 //直接获取 &lt;term freq, document&gt; 的信息<br />
#0029 TermDocs termDocs = reader.TermDocs(termEnum.Term());<br />
#0030 while (termDocs.Next())<br />
#0031 {<br />
#0032 System.Console.WriteLine((i++) + &#8220;-&gt;&#8221; + &#8221; DocNo:&#8221; + termDocs.Doc() + &#8220;, Freq:&#8221; + termDocs.Freq());<br />
#0033 }<br />
#0034 }<br />
#0035<br />
#0036 // FieldInfos fieldInfos = reader.fieldInfos;<br />
#0037 // FieldInfo pathFieldInfo = fieldInfos.FieldInfo(&#8220;path&#8221;);<br />
#0038<br />
#0039 //显示 term frequency vector<br />
#0040 for (int i = 0; i &lt; reader.NumDocs(); i++)<br />
#0041 {<br />
#0042 //对contents的token之后的term存于了TermFreqVector<br />
#0043 TermFreqVector termFreqVector = reader.GetTermFreqVector(i, &#8220;contents&#8221;);<br />
#0044<br />
#0045 if (termFreqVector == null)<br />
#0046 {<br />
#0047 System.Console.WriteLine(&#8220;termFreqVector is null.&#8221;);<br />
#0048 continue;<br />
#0049 }<br />
#0050<br />
#0051 String fieldName = termFreqVector.GetField();<br />
#0052 String[] terms = termFreqVector.GetTerms();<br />
#0053 int[] frequences = termFreqVector.GetTermFrequencies();<br />
#0054<br />
#0055 System.Console.Write(&#8220;FieldName:&#8221; + fieldName);<br />
#0056 for (int j = 0; j &lt; terms.Length; j++)<br />
#0057 {<br />
#0058 System.Console.Write(&#8220;[" + terms[j] + &#8220;:&#8221; + frequences[j] + &#8220;]&#8221;);<br />
#0059 }<br />
#0060 System.Console.WriteLine();<br />
#0061 }<br />
#0062 System.Console.WriteLine();<br />
#0063 }</font></p>
<p><font face="Verdana"></font><font size="2">#0004 计算document的个数<br />
#0012~#0034 枚举collection中所有的term<br />
其中#0017~#0026 枚举每个term在出现的document中的所有位置(第几个词，从1开始计数)；#0029~#0033 计算每个term出现在哪些文档和相应的出现频度(即DF和TF)。<br />
#0036~#0037在reader是SegmentReader类型的情况下有效。<br />
#0040~#0061可以快速的读取某篇文档中出现的term和相应的频度。但是这部分需要在建索引时，设置storeTermVector为true。比如<br />
doc.Add(Field.Text(&#8220;contents&#8221;, reader, true));<br />
其中的第三项即是。默认为false。</font></p>
<p><font face="Verdana"></font><font size="2">有了这些数据，就可以统计我需要的数据了。以后我会介绍如何建立索引，如何应用Lucene。</font></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.zye.me/2011/09/3886.html/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Lucene同时进行查询和索引</title>
		<link>http://blog.zye.me/2011/07/36221.html</link>
		<comments>http://blog.zye.me/2011/07/36221.html#comments</comments>
		<pubDate>Sun, 17 Jul 2011 14:27:50 +0000</pubDate>
		<dc:creator>yezheng</dc:creator>
				<category><![CDATA[information Retrieval]]></category>
		<category><![CDATA[indexing]]></category>
		<category><![CDATA[lucene]]></category>
		<category><![CDATA[searching]]></category>
		<category><![CDATA[查询]]></category>
		<category><![CDATA[索引]]></category>

		<guid isPermaLink="false">http://www.5yiso.cn/2008/07/36221.html</guid>
		<description><![CDATA[Does Lucene allow searching and indexing simultaneously?Yes. However, an IndexReader only searches the index as of the &#8220;point in time&#8221; that it was opened. Lucene在用IndexReader打开索引的同时，允许用IndexWriter对该索引进行更新，但是IndexReader只能查询到open索引时所索引的文件或者说Document，要想查询新索引的Document，IndexReader必须调用reopen方法（该方法开销较小）。 IndexReader.isCurrent() 方法可以用于测试索引是否有更新。]]></description>
			<content:encoded><![CDATA[<p>Does Lucene allow searching and indexing simultaneously?Yes. However, an IndexReader only searches the index as of the &#8220;point in time&#8221; that it was opened.</p>
<p>Lucene在用IndexReader打开索引的同时，允许用IndexWriter对该索引进行更新，但是IndexReader只能查询到open索引时所索引的文件或者说Document，要想查询新索引的Document，IndexReader必须调用reopen方法（该方法开销较小）。</p>
<p>IndexReader.isCurrent() 方法可以用于测试索引是否有更新。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.zye.me/2011/07/36221.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Indri中的动态文档索引技术（转）</title>
		<link>http://blog.zye.me/2011/06/5150.html</link>
		<comments>http://blog.zye.me/2011/06/5150.html#comments</comments>
		<pubDate>Sat, 04 Jun 2011 02:29:04 +0000</pubDate>
		<dc:creator>yezheng</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[indexing]]></category>
		<category><![CDATA[Indri]]></category>
		<category><![CDATA[information Retrieval]]></category>
		<category><![CDATA[lucene]]></category>
		<category><![CDATA[信息检索]]></category>
		<category><![CDATA[索引]]></category>

		<guid isPermaLink="false">http://www.5yiso.cn/2007/02/5150.html</guid>
		<description><![CDATA[Indri中的动态文档索引技术 戴维译 摘要： Indri 动态文档索引的实现技术，支持在更新索引的同时处理用户在线查询请求。 文本搜索引擎曾被设计为针对固定的文档集合进行查询，对不少应用来说，这种机制工作得很好，然而对于诸于新闻，财经和桌面搜索而言，需要的是高效、经常性的更新索引。 以往支持动态文档集合的研究主要围绕增量索引方法，增量系统通过往已有的索引中追加大的文档集合来优化索引性能，但是不允许在增量索引的同时处理用户查询。 与以往的增量系统不同，Indri搜索引擎的最新版本支持动态文档集合，不需要通过加大文档集合大小来获取索引性能，同时Indri支持索引和查询的并发，允许用户在增量索引的同时进行查询。 1．介绍 尽管全文索引技术已经出现了几十年之久，但是直到互联网的出现，它才真正得到普及。现在，几乎每个互联网使用者都是搜索引擎用户，全文搜索技术被广泛地用于各种应用领域，如Web搜索，新闻搜索，以及时下流行的桌面搜索等。 搜索桌面（或硬盘）文件和e-mails对大多数信息检索系统而言是一个新的挑战。用户期望他们的e-mails即到即索引，文件在保存到磁盘的顷刻便被索引好。永远不要期望桌面搜索用户能忍受由于索引更新所带来的存储消耗。不管是祈祷还是咒骂，用户看到了全文搜索的好处，更为普遍的事实是人们越来越发现自己离不开搜索。 然后，更新一个全文索引是一个耗时的过程，现在的搜索引擎通过建立大量文档的倒排索引表来创建索引，一个倒排索引项包含一篇文章的无重复词语列表，以及这些词语的附加信息（如词语在文档中的位置，词性等）。一篇短小的包含100个左右不重复词语的文章，索引的时候需要更新100个倒排索引项。如果这些倒排索引是存储在磁盘上的话，更新操作将需要100次的磁盘寻址，在现有的硬件配置下，这需要一秒或更长的时间。当然，长文章耗时也越长。另外，一个随之而来的问题是，在更新索引的时候，搜索引擎是该让用户等待更新完成还是继续处理用户的查询请求呢？ Indri搜索引擎新的版本突破了上述限制。作为Lemur[1]项目研发的一部分，Indri支持结构化查询语言，采用语言建模方法[2]，同时为了满足问答系统的需要，Indri还支持对结构化文档不同域进行查询。Indri第一个版本没有加入对增量索引的支持，但在最新的版本中允许对单个文档进行真正的实时索引。 一个信息检索系统要处理动态的文档集合，需要解决三个关键问题。首先，要能快速地添加和删除集合中的文档，这里的快速取决于一个桌面搜索用户愿意花多长的时间来等待索引的完成，也就是说对于单个文档而言响应应该是瞬时的。其次，系统要允许查询在任何时候都能得到响应，即使是在新文档添加进文档集合的同时。另外，系统要实用，在索引和检索性能上比不支持动态文档集合的检索系统更具有竞争性。 在Indri最新版本中，我们通过引入如下设计原则来实现上述目标： 内存结构-为了避免读写磁盘，尽可能长地把数据调入内存。 加锁机制-系统在尽可能小的时间段内对数据进行加锁互斥。 只读结构-为了减少对互斥锁的依赖，系统引入只读数据结构。 后台I/O-系统采用后台线程来和低速设备进行交互，以提高索引操作性能。 多版本结构-如果一个耗时长的操作需要获取可能已经更新的数据，系统将维持此数据的多个版本来减少互斥锁的使用。 上述原则的具体运用将在文章后续部分进行详细介绍。 2．相关工作 数据库研究团体已经花了几十年时间来研究数据获取并发技术。Ramakrishnan和Gehrke提出了一个通用的数据库原则[3]，Gray和Reuter则进一步深入探讨了事务处理系统[4]。 尽管访问文档数据和访问数据库数据所遇到的问题类似，但它们之间仍然存在着显著的差别。在数据库系统中，用户特别关心的是数据的原子性，有一个经典得例子，一位银行顾客把d美元钱从一个储蓄户头转到一个支票账号，如果d美元先从储蓄户头减掉，那么这位顾客的总钱数就少了d美元；如果先往支票账号加入d美元，那么他的钱就凭空多了d美元。不管怎么样，在第一个账号改变的同时就会出现金额不一致的情况。于是，数据库中事件并发研究的一个主要任务就是确保数据库的一致性，即使是在系统操作失败的情况下也要如此。 在我们系统中可以确保文档的插入和删除是原子操作，没有用户会看到一个文档是部分被删除或者插入的。但我们不允许多个文档的插入和删除是原子的。既然文档之间很少像数据库中记录那样相互依赖，这就不会成为我们系统的主要限制。 虽然有上述的差异，我们仍然可以从数据库中得到借鉴。异步I/O和互斥技术被现代数据库系统广泛使用，我们也在索引系统中采用类似的多版本并发技术[5]。 信息检索研究团体没有忽略动态文档集合，他们把研究重点放在了增量系统上，这种系统通过一次性添加大批量文档到已有索引上来代替单个文档的高效添加。这种研究并没有考虑当更新索引时系统能否继续处理查询的问题。 Brown，Callan和Croft[6]研究了一种高效增量索引的方法。他们区别对待小于8k和大于8k的索引，当一个小的索引需要增大时，它将被拷贝到一个大的连续的倒排索引文件中。然而对于一个大的索引项则不需要移动，只需添加一个前向指针到新的存储段(segment)里面。这也使得倒排索引可以通过倒排索引文件串连起来。他们发现，当在7个簇中创建一个索引的时候，查询性能降低了6%。使用小的簇时代价偏高，在他们的模型中索引每簇大小为64M的文本所花的时间是索引每簇大小为1M的文本的8倍。 在最近的研究中，Lester，Zobel以及Williams[7]比较了三种索引策略：占位(in-place)，合并（re-merge）以及重构（rebuild）。除了没有对连接链表进行优化外，in-place策略类似Brown所采用的方法。所有的倒排索引连续存放，如果没有足够的空间写入新数据的时候，已有数据必须被拷贝到别的地方。在re-merge策略中，新的文档簇被创建到单独的索引中，然后和已经存在的索引进行合并。rebuild策略则对已经构建的索引弃之不顾，在原始文档的基础上重新构建索引。他们研究发现，re-merge策略是最高效的更新索引的方法。但是，他们没有像Brow，Callan和Croft所作的那样，对预分配(pre-allocation)策略之间和处理大索引策略之间的差异进行比较。 Lester，Zobel和Williams提到，在使用最小的文档簇(10个文档)的情况下，表现最好的索引策略(in-place策略)在大约7秒的时间内更新了1G的索引。相对于别的策略而言，这已经是非常快了，但是，对于单个文档的索引更新来说，这并不是个理想的策略。 本文描述的方法类似Lucene搜索引擎，正常情况下，就像传统的批量索引一样，Lucene以分段(segment)的方式把数据写入磁盘。一旦数据被写入段中，他们就可以被查询到，并且不需要进行段的合并。这和Brown，Callan和Croft的连接链表方法有点类似，只是，把数据写入簇(batch)中需要更多的开销。为了获得更好的性能，许多文档必须被写入磁盘的一个簇(batch)中。 如果需要快速的响应，Lucene提供一个RAMDirectory类在内存中创建索引。添加一个文档到RAMDirectory很快，因为不需要进行磁盘I/O操作。一个文档一加入RAMDirectory便可以通过一个叫做IndexWriter的对象进行查询。这也解决了文档簇对于小文档集合的问题。然而，对于大小大于机器内存的文档集合，内存索引方式将不再可用，数据必须被写入磁盘，并根据用户对索引数据的定位方式来决定，哪些数据需要驻留内存，哪些数据应该写入磁盘。 在我们的工作中，当需要对文档进行快速存取时，Indri使用内存索引而不是批量索引。当需要同时处理查询请求的时候，Indri会立即决定什么时候该合并索引，而什么时候该把数据写入磁盘。 3．策略 3.1 内存结构 Indri采用两种类型的索引：内存索引和磁盘索引。内存索引驻留内存，而磁盘索引则存储在磁盘上。两种索引都能够处理查询，但只有内存索引能添加新的文档。Indri的磁盘索引结构是固定不变的，可以删除，但不能修改。 大多数的信息检索系统在磁盘上为所有文档创建一个单独的索引，而Indri在创建索引的同时还会生成不同用途的索引文件，这里我们使用&#8221;索引库repository&#8220;来指代一个文档集合对应的索引及其相关数据结构。 当在文本集合上创建索引库的时候，Indri把当前文档索引到活动的内存索引中。只要一个索引库处在打开写模式，就存在一个活动的内存索引准备接收文档。对于小的文档集合，索引数据直到索引库需要关闭的时候才写入磁盘。数据写入的同时，一个新的内存索引被创建，作为新的活动索引使用。 用上述方法构建一个索引相当于多个检索系统同时工作。文档通常一次性加入内存索引结构，只有当达到了内存限制时才被写入磁盘。在批量系统中，磁盘和内存结构不能独立工作，索引需要经过后续处理才能用于系统查询。 因为构建许多小的索引比构建一个单独的大索引更加高效，所以在磁盘上维持许多单独的小索引要更加有利。大的索引只有在需要合并小索引的时候才会出现，为了尽可能快地向系统中添加文档，简单地生成小的索引更有优势[7]。 然而，从一个单独的大索引中查询比在众多小索引中寻觅要快的多，主要的原因在于磁盘寻址时间，查询所需要的大量的磁盘寻址和索引数目之间具有线性的关系。因此，重查询负载的情况下，Indri将通过合并索引的方式来减少磁盘索引数目。 3.2 加锁机制 为了满足系统快速响应的需求，Indri必须快速地处理查询和加入文档。Indri以前的版本已经是一个有效的批量系统，可以很容易地对数据结构进行加锁，但是这也容易使查询或文档插入被长时间阻塞。为了保持快速的响应，必须确保系统中互斥锁的使用是在很短的时间内。 我们通过只允许活动内存索引可变来减少互斥锁的加锁时间。除了一些caches外，内存索引是系统加锁时需要处理的唯一结构。而内存索引的大小是受可用内存大小限制的，它的全部内容都驻留在内存中，即算对于复杂的查询，内存索引的响应也相当快。当然，加锁时间也可以通过减少内存限制来降低。 互斥锁也需要向内存索引中添加新的文档，为了减少加锁时间，我们确保每个文档在上锁前是被解析过的，只有当单个文档被索引的时候才加锁，然后开锁以处理查询。大多数的网页和新闻文档可以在小于1/100秒的时间内索引好，查询处理需要等待的时间相应也就很短。 当处理查询的时候有新文档到来，系统又正好处于加锁状态，此时，系统将禁止磁盘I/O操作。这样可以显著减少主线程在持有互斥锁的情形下任务调度混乱。 3.3 只读结构 为了减少系统在处理大卷数据时互斥锁加锁的时间，我们设法让大部分数据保持不变，对于磁盘数据来说这是基本准则。如果磁盘数据不是只读的，那么线程之间就需要在进行磁盘I/O读写时进行加锁，这可能导致加锁时数据的高度不一致。 既然磁盘数据在写入后便不允许更改，对于读取数据来说就不用加锁。加锁策略让Indri可以充分利用多处理器系统来提高性能。大部分的查询代码路径(query code path)仅仅需要只读锁就可以了，也就是说查询处理是高度并行的，尽管它看起来似乎受到磁盘子系统的并行限制。另外，由于文本解析和索引可以同时进行，索引过程也具有可并行性。 3.4 <a href='http://blog.zye.me/2011/06/5150.html'>[...]</a>]]></description>
			<content:encoded><![CDATA[<p style="TEXT-ALIGN: center"><span style="FONT-SIZE: 16pt"><span style="FONT-FAMILY: Times New Roman">Indri</span></span><span style="FONT-SIZE: 16pt; FONT-FAMILY: 宋体">中的动态文档索引技术</span> <span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">戴维</span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">译</span></p>
<p style="TEXT-ALIGN: center"><span style="FONT-SIZE: 10pt; FONT-FAMILY: 宋体">摘要：</span> <span style="FONT-SIZE: 10pt; FONT-FAMILY: Verdana">Indri</span> <span style="FONT-SIZE: 10pt; FONT-FAMILY: 宋体">动态文档索引的实现技术，支持在更新索引的同时处理用户在线查询请求。</span> <span style="FONT-SIZE: 10pt; FONT-FAMILY: 宋体">文本搜索引擎曾被设计为针对固定的文档集合进行查询，对不少应用来说，这种机制工作得很好，然而对于诸于新闻，财经和桌面搜索而言，需要的是高效、经常性的更新索引。</span> <span style="FONT-SIZE: 10pt; FONT-FAMILY: 宋体">以往支持动态文档集合的研究主要围绕增量索引方法，增量系统通过往已有的索引中追加大的文档集合来优化索引性能，但是不允许在增量索引的同时处理用户查询。</span> <span style="FONT-SIZE: 10pt; FONT-FAMILY: 宋体">与以往的增量系统不同，</span><span style="FONT-SIZE: 10pt; FONT-FAMILY: Verdana">Indri</span><span style="FONT-SIZE: 10pt; FONT-FAMILY: 宋体">搜索引擎的最新版本支持动态文档集合，不需要通过加大文档集合大小来获取索引性能，同时</span><span style="FONT-SIZE: 10pt; FONT-FAMILY: Verdana">Indri</span><span style="FONT-SIZE: 10pt; FONT-FAMILY: 宋体">支持索引和查询的并发，允许用户在增量索引的同时进行查询。</span></p>
<p><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">1</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">．介绍</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">尽管全文索引技术已经出现了几十年之久，但是直到互联网的出现，它才真正得到普及。现在，几乎每个互联网使用者都是搜索引擎用户，全文搜索技术被广泛地用于各种应用领域，如</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Web</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">搜索，新闻搜索，以及时下流行的桌面搜索等。</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">搜索桌面（或硬盘）文件和</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">e-mails</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">对大多数信息检索系统而言是一个新的挑战。用户期望他们的</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">e-mails</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">即到即索引，文件在保存到磁盘的顷刻便被索引好。永远不要期望桌面搜索用户能忍受由于索引更新所带来的存储消耗。不管是祈祷还是咒骂，用户看到了全文搜索的好处，更为普遍的事实是人们越来越发现自己离不开搜索。</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">然后，更新一个全文索引是一个耗时的过程，现在的搜索引擎通过建立大量文档的倒排索引表来创建索引，一个倒排索引项包含一篇文章的无重复词语列表，以及这些词语的附加信息（如词语在文档中的位置，词性等）。一篇短小的包含</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">100</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">个左右不重复词语的文章，索引的时候需要更新</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">100</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">个倒排索引项。如果这些倒排索引是存储在磁盘上的话，更新操作将需要</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">100</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">次的磁盘寻址，在现有的硬件配置下，这需要一秒或更长的时间。当然，长文章耗时也越长。另外，一个随之而来的问题是，在更新索引的时候，搜索引擎是该让用户等待更新完成还是继续处理用户的查询请求呢？</span></p>
<p><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman"> Indri</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">搜索引擎新的版本突破了上述限制。作为</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Lemur[1]</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">项目研发的一部分，</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Indri</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">支持结构化查询语言，采用语言建模方法</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">[2]</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">，同时为了满足问答系统的需要，</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Indri</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">还支持对结构化文档不同域进行查询。</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Indri</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">第一个版本没有加入对增量索引的支持，但在最新的版本中允许对单个文档进行真正的实时索引。</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">一个信息检索系统要处理动态的文档集合，需要解决三个关键问题。首先，要能快速地添加和删除集合中的文档，这里的快速取决于一个桌面搜索用户愿意花多长的时间来等待索引的完成，也就是说对于单个文档而言响应应该是瞬时的。其次，系统要允许查询在任何时候都能得到响应，即使是在新文档添加进文档集合的同时。另外，系统要实用，在索引和检索性能上比不支持动态文档集合的检索系统更具有竞争性。</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">在</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Indri</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">最新版本中，我们通过引入如下设计原则来实现上述目标：</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">内存结构-为了避免读写磁盘，尽可能长地把数据调入内存。</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">加锁机制-系统在尽可能小的时间段内对数据进行加锁互斥。</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">只读结构-为了减少对互斥锁的依赖，系统引入只读数据结构。</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">后台</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">I/O</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">-系统采用后台线程来和低速设备进行交互，以提高索引操作性能。</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">多版本结构-如果一个耗时长的操作需要获取可能已经更新的数据，系统将维持此数据的多个版本来减少互斥锁的使用。</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">上述原则的具体运用将在文章后续部分进行详细介绍。</span></p>
<p><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">2</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">．相关工作</span></p>
<p style="TEXT-ALIGN: left"><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">数据库研究团体已经花了几十年时间来研究数据获取并发技术。</span><span style="FONT-SIZE: 12pt; FONT-FAMILY: CMR10"><span style="FONT-FAMILY: Times New Roman">Ramakrishnan</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">和</span><span style="FONT-SIZE: 12pt; FONT-FAMILY: CMR10"><span style="FONT-FAMILY: Times New Roman">Gehrke</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">提出了一个通用的数据库原则</span><span style="FONT-SIZE: 12pt; FONT-FAMILY: CMR10"><span style="FONT-FAMILY: Times New Roman">[3]</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">，</span><span style="FONT-SIZE: 12pt; FONT-FAMILY: CMR10"><span style="FONT-FAMILY: Times New Roman">Gray</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">和</span><span style="FONT-SIZE: 12pt; FONT-FAMILY: CMR10"><span style="FONT-FAMILY: Times New Roman">Reuter</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">则进一步深入探讨了事务处理系统</span><span style="FONT-SIZE: 12pt; FONT-FAMILY: CMR10"><span style="FONT-FAMILY: Times New Roman">[4]</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">。</span></p>
<p style="TEXT-ALIGN: left"><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">尽管访问文档数据和访问数据库数据所遇到的问题类似，但它们之间仍然存在着显著的差别。在数据库系统中，用户特别关心的是数据的原子性，有一个经典得例子，一位银行顾客把</span><span style="FONT-SIZE: 12pt; FONT-FAMILY: CMR10"><span style="FONT-FAMILY: Times New Roman">d</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">美元钱从一个储蓄户头转到一个支票账号，如果</span><span style="FONT-SIZE: 12pt; FONT-FAMILY: CMR10"><span style="FONT-FAMILY: Times New Roman">d</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">美元先从储蓄户头减掉，那么这位顾客的总钱数就少了</span><span style="FONT-SIZE: 12pt; FONT-FAMILY: CMR10"><span style="FONT-FAMILY: Times New Roman">d</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">美元；如果先往支票账号加入</span><span style="FONT-SIZE: 12pt; FONT-FAMILY: CMR10"><span style="FONT-FAMILY: Times New Roman">d</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">美元，那么他的钱就凭空多了</span><span style="FONT-SIZE: 12pt; FONT-FAMILY: CMR10"><span style="FONT-FAMILY: Times New Roman">d</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">美元。不管怎么样，在第一个账号改变的同时就会出现金额不一致的情况。于是，数据库中事件并发研究的一个主要任务就是确保数据库的一致性，即使是在系统操作失败的情况下也要如此。</span></p>
<p style="TEXT-ALIGN: left"><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">在我们系统中可以确保文档的插入和删除是原子操作，没有用户会看到一个文档是部分被删除或者插入的。但我们不允许多个文档的插入和删除是原子的。既然文档之间很少像数据库中记录那样相互依赖，这就不会成为我们系统的主要限制。</span></p>
<p style="TEXT-ALIGN: left"><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">虽然有上述的差异，我们仍然可以从数据库中得到借鉴。异步</span><span style="FONT-SIZE: 12pt; FONT-FAMILY: CMR10"><span style="FONT-FAMILY: Times New Roman">I/O</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">和互斥技术被现代数据库系统广泛使用，我们也在索引系统中采用类似的多版本并发技术</span><span style="FONT-SIZE: 12pt; FONT-FAMILY: CMR10"><span style="FONT-FAMILY: Times New Roman">[5]</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">。</span></p>
<p style="TEXT-ALIGN: left"><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">信息检索研究团体没有忽略动态文档集合，他们把研究重点放在了增量系统上，这种系统通过一次性添加大批量文档到已有索引上来代替单个文档的高效添加。这种研究并没有考虑当更新索引时系统能否继续处理查询的问题。</span></p>
<p><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman"> Brown</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">，</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Callan</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">和</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Croft[6]</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">研究了一种高效增量索引的方法。他们区别对待小于</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">8k</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">和大于</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">8k</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">的索引，当一个小的索引需要增大时，它将被拷贝到一个大的连续的倒排索引文件中。然而对于一个大的索引项则不需要移动，只需添加一个前向指针到新的存储段</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">(segment)</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">里面。这也使得倒排索引可以通过倒排索引文件串连起来。他们发现，当在</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">7</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">个簇中创建一个索引的时候，查询性能降低了</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">6%</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">。使用小的簇时代价偏高，在他们的模型中索引每簇大小为</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">64M</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">的文本所花的时间是索引每簇大小为</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">1M</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">的文本的</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">8</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">倍。</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">在最近的研究中，</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Lester</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">，</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Zobel</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">以及</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Williams[7]</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">比较了三种索引策略：占位</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">(in-place)</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">，合并（</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">re-merge</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">）以及重构（</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">rebuild</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">）。除了没有对连接链表进行优化外，</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">in-place</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">策略类似</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Brown</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">所采用的方法。所有的倒排索引连续存放，如果没有足够的空间写入新数据的时候，已有数据必须被拷贝到别的地方。在</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">re-merge</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">策略中，新的文档簇被创建到单独的索引中，然后和已经存在的索引进行合并。</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">rebuild</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">策略则对已经构建的索引弃之不顾，在原始文档的基础上重新构建索引。他们研究发现，</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">re-merge</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">策略是最高效的更新索引的方法。但是，他们没有像</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Brow</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">，</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Callan</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">和</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Croft</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">所作的那样，对预分配</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">(pre-allocation)</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">策略之间和处理大索引策略之间的差异进行比较。</span></p>
<p><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman"> Lester</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">，</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Zobel</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">和</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Williams</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">提到，在使用最小的文档簇</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">(10</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">个文档</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">)</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">的情况下，表现最好的索引策略</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">(in-place</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">策略</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">)</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">在大约</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">7</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">秒的时间内更新了</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">1G</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">的索引。相对于别的策略而言，这已经是非常快了，但是，对于单个文档的索引更新来说，这并不是个理想的策略。</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">本文描述的方法类似</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Lucene</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">搜索引擎，正常情况下，就像传统的批量索引一样，</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Lucene</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">以分段</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">(segment)</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">的方式把数据写入磁盘。一旦数据被写入段中，他们就可以被查询到，并且不需要进行段的合并。这和</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Brown</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">，</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Callan</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">和</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Croft</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">的连接链表方法有点类似，只是，把数据写入簇</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">(batch)</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">中需要更多的开销</span><span style="FONT-SIZE: 0.9em"><span style="FONT-FAMILY: 宋体">。</span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">为了获得更好的性能，许多文档必须被写入磁盘的一个簇</span></span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">(batch)</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">中。</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">如果需要快速的响应，</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Lucene</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">提供一个</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">RAMDirectory</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">类在内存中创建索引。添加一个文档到</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">RAMDirectory</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">很快，因为不需要进行磁盘</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">I/O</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">操作。一个文档一加入</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">RAMDirectory</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">便可以通过一个叫做</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">IndexWriter</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">的对象进行查询。这也解决了文档簇对于小文档集合的问题。然而，对于大小大于机器内存的文档集合，内存索引方式将不再可用，数据必须被写入磁盘，并根据用户对索引数据的定位方式来决定，哪些数据需要驻留内存，哪些数据应该写入磁盘。</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">在我们的工作中，当需要对文档进行快速存取时，</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Indri</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">使用内存索引而不是批量索引。当需要同时处理查询请求的时候，</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Indri</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">会立即决定什么时候该合并索引，而什么时候该把数据写入磁盘。</span></p>
<p><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">3</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">．策略</span></p>
<p><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">3.1</span></span> <span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">内存结构</span></p>
<p><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman"> Indri</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">采用两种类型的索引：内存索引和磁盘索引。内存索引驻留内存，而磁盘索引则存储在磁盘上。两种索引都能够处理查询，但只有内存索引能添加新的文档。</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Indri</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">的磁盘索引结构是固定不变的，可以删除，但不能修改。</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">大多数的信息检索系统在磁盘上为所有文档创建一个单独的索引，而</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Indri</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">在创建索引的同时还会生成不同用途的索引文件，这里我们使用&#8221;索引库</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">repository</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">&#8220;来指代一个文档集合对应的索引及其相关数据结构。</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">当在文本集合上创建索引库的时候，</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Indri</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">把当前文档索引到活动的内存索引中。只要一个索引库处在打开写模式，就存在一个活动的内存索引准备接收文档。对于小的文档集合，索引数据直到索引库需要关闭的时候才写入磁盘。数据写入的同时，一个新的内存索引被创建，作为新的活动索引使用。</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">用上述方法构建一个索引相当于多个检索系统同时工作。文档通常一次性加入内存索引结构，只有当达到了内存限制时才被写入磁盘。在批量系统中，磁盘和内存结构不能独立工作，索引需要经过后续处理才能用于系统查询。</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">因为构建许多小的索引比构建一个单独的大索引更加高效，所以在磁盘上维持许多单独的小索引要更加有利。大的索引只有在需要合并小索引的时候才会出现，为了尽可能快地向系统中添加文档，简单地生成小的索引更有优势</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">[7]</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">。</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">然而，从一个单独的大索引中查询比在众多小索引中寻觅要快的多，主要的原因在于磁盘寻址时间，查询所需要的大量的磁盘寻址和索引数目之间具有线性的关系。因此，重查询负载的情况下，</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Indri</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">将通过合并索引的方式来减少磁盘索引数目。</span></p>
<p><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">3.2</span></span> <span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">加锁机制</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">为了满足系统快速响应的需求，</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Indri</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">必须快速地处理查询和加入文档。</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Indri</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">以前的版本已经是一个有效的批量系统，可以很容易地对数据结构进行加锁，但是这也容易使查询或文档插入被长时间阻塞。为了保持快速的响应，必须确保系统中互斥锁的使用是在很短的时间内。</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">我们通过只允许活动内存索引可变来减少互斥锁的加锁时间。除了一些</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">caches</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">外，内存索引是系统加锁时需要处理的唯一结构。而内存索引的大小是受可用内存大小限制的，它的全部内容都驻留在内存中，即算对于复杂的查询，内存索引的响应也相当快。当然，加锁时间也可以通过减少内存限制来降低。</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体"><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">互斥锁也需要向内存索引中添加新的文档，为了减少加锁时间，我们确保每个文档在上锁前是被解析过的，只有当单个文档被索引的时候才加锁，然后开锁以处理查询。大多数的网页和新闻文档可以在小于</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">1/100</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">秒的时间内索引好，查询处理需要等待的时间相应也就很短。</span></span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">当处理查询的时候有新文档到来，系统又正好处于加锁状态，此时，系统将禁止磁盘</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">I/O</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">操作。这样可以显著减少主线程在持有互斥锁的情形下任务调度混乱。</span></p>
<p><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">3.3</span></span> <span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">只读结构</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">为了减少系统在处理大卷数据时互斥锁加锁的时间，我们设法让大部分数据保持不变，对于磁盘数据来说这是基本准则。如果磁盘数据不是只读的，那么线程之间就需要在进行磁盘</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">I/O</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">读写时进行加锁，这可能导致加锁时数据的高度不一致。</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">既然磁盘数据在写入后便不允许更改，对于读取数据来说就不用加锁。加锁策略让</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Indri</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">可以充分利用多处理器系统来提高性能。大部分的查询代码路径</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">(query code path)</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">仅仅需要只读锁就可以了，也就是说查询处理是高度并行的，尽管它看起来似乎受到磁盘子系统的并行限制。另外，由于文本解析和索引可以同时进行，索引过程也具有可并行性。</span></p>
<p><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">3.4</span></span> <span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">后台</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">I/O</span></span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">如果</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">I/O</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">操作</span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">在查询和索引时候不能执行，就需要引入异步</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">I /O</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">。</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Indri</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">通过一直运行如下两个线程来实现异步</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">I/O</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">操作：</span></p>
<p><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">RepositoryMaintenanceThread</span></span></p>
<p><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">RepositoryLoadThread</span></span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">这两个线程和特定的</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Repository</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">相关联，如果多于一个</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Repository</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">被打开，每个</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">repository</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">均需要对应的这样一对线程相关联。</span></p>
<p><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman"> RepositoryLoadThread</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">执行两项任务。一是为查询和新加入的文档载入统计数据，这种数据载入和</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Unix</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">进程载入有些类似，线程标明在过去的</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">1</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">，</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">5</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">以及</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">15</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">分钟内处理的查询和添加的文档数目，以帮助系统决定何时该把内存数据写入磁盘。</span></p>
<p><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman"> RepositoryLoadThread</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">的另一项任务是检查系统的内存使用情况。如果系统使用的内存超过用户限制的</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">25</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">％，</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">RepositoryLoadThread</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">将挂起所有文档索引线程直到内存占用降下来为止，这可以防止系统在大批量文档加入的时候崩溃。对于多数可能的实时程序，如新闻播报，系统的运行决不能超出内存限制。</span></p>
<p><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman"> RepositoryMaintenanceThread</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">把索引写入磁盘，它是唯一能把索引数据写入磁盘，并可以从磁盘删除数据的线程，在这个线程中不需要复杂的加锁机制。该线程每分钟激活</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">5</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">次以检查系统当前内存占用量，如果系统使用了过多的内存，它就开始把内存数据写入磁盘。</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">如上所述，创建新的磁盘索引并不是总有好处，因为许多小的磁盘索引结构对于大的磁盘索引结构来说需要更多的查询时间。为此，索引库维护线程</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">RepositoryMaintenaceThread</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">在写磁盘之前检查查询和文档的载入情况，如果查询相对于文档载入量更大的话，维护线程将进行索引合并而不是往磁盘中写入一个新的索引。</span></p>
<p><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">3.5</span></span> <span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">多版本结构</span></p>
<p><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman"> Indri Repository</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">可能包含多个索引，</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Indri</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">维护一个称之为</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">index_state</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">的结构，这个结构持有指向当前索引库中所有索引的指针。索引数据在两种情况下被写入磁盘：</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">内存索引</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">MemoryIndex</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">已经达到了它的内存限制；</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">存在过多的内存索引</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">MemoryIndex</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">，他们需要进行合并。</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">在上述两种情况下，</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Indri</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">把数据写入磁盘，这些数据可能已经以别的形式存在于系统中，因此</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">index_state</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">需要进行修改以反映数据是否被删除。</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">一个解决办法是对</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">index_state</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">结构的读写进行互斥，然而，这种方法也可能导致在重负载情况下系统性能低下。</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">考虑</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Indri</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">是运行在一个并行系统中，并且用户正在进行复杂的查询，这些查询每个都需要</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">10</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">分钟的处理时间。假如用户在两个独立的线程中提交查询，系统中就总是有两份同样的查询在运行。如果这些查询以</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">5</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">分钟为时间片轮流执行，</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">A</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">线程分别在一个小时的</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">0</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">，</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">10</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">，</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">20</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">，</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">30</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">，</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">40</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">和</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">50</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">分钟时开始运行，而</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">B</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">线程则分别在</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">5</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">，</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">15</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">，</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">25</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">，</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">35</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">，</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">45</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">和</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">55</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">分钟时开始执行。在一个小时开始的时候，我们对</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">index_state</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">的写进行加锁，当在</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">B</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">线程处理完一个查询前，</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">A</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">线程不允许处理它的下一个的查询，于是一个处理器将有</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">5</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">分钟的空闲时间，这是我们不希望看到的。</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">为了避免上述情况的出现，</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Indri</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">在同一时间维持多个</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">index_state</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">结构，所有新的任务（如新的查询，文档的加入）使用新的</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">index_state</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">结构，而旧的任务继续使用旧的</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">index_state</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">结构，当没有用户需要使用</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">index_state</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">的时候，它将被删除。</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">在上面的例子中，这意味着线程</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">B</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">在使用它旧的</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">index_state</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">结构完成它的查询处理的同时，线程</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">A</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">使用新的</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">index_state</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">结构开始处理它的下一个查询。当线程</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">B</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">处理完毕当前查询，旧的</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">index_state</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">将不在有用户使用，从而被删除掉。</span></p>
<p><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">4</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">．删除文档</span></p>
<p><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman"> Indri</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">支持删除标记。删除标记是一种弱删除方式，只是简单地隐藏文档对于用户的可见性，而不是真正的删除。文档对应的索引数据并不会真正从倒排链表，有向链表或者压缩集合中删除掉，也就是说文档中词语的计数仍然保留在语料统计数据库中。</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">假设我们有一个文档集合</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">A</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">，以及它的一个子集</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">B</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">，首先创建</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">A</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">的索引</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">I</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">，然后从</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">I</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">中删除</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">B</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">对应的索引。我们只是通过把文档集合</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">A</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">－</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">B</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">添加到</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">I&#8217;</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">来创建一个相似的索引</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">I&#8217;</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">。由于包含了文档集</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">B</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">的数据，索引</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">I</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">比索引</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">I&#8217;</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">需要占用更多的磁盘空间。进一步，因为</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">I</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">和</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">I&#8217;</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">对应的语料统计库稍有差别，当在这两个索引上进行查询时，查询结果也会有所不同。基于如上原因，当使用</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Indri</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">进行搜索的时候要谨慎地使用文档删除特性。</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">尽管实际应用中，删除是个很有用的特性，但单纯的删除用处不大。删除往往被用于进行文档更新（删除旧的版本，插入新的版本）。对于桌面搜索或新闻搜索而言，经常需要更新已有文档的错误（或者过时）版本，这就显得尤为重要。</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">我们采用一个简单的位图来标记文档的删除，当一个文档需要删除，就为其设置对应的比特位，任何不在位图中的比特位均假设没有被设置。因此，如果没有文档被删除，位图文件将是一个空文件。这个文件会一直扩充直到最后一个比特位被设置为非</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">0</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">。</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">查询时，每个文档均要在打分前对照位图进行检查，只有没有被标记为删除的文档才能进行查询计分。</span></p>
<p><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">5</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">．总结</span></p>
<p><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman"> Indri</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">现在可以在小于</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">1</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">秒的短时间片内完成文档索引并立即用于查询，这使得高速、并发访问新索引的文档所付出的代价足够小，以至</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Indri</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">不需要采用特殊的批量和增量模式。</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Indri</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">可以每小时索引约</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">15G</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">的</span><span style="FONT-SIZE: 12pt"><span style="FONT-FAMILY: Times New Roman">Web</span></span><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">数据，包括压缩和存储每个原始文档。</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">在这种性能下，我们已经实现了适合新闻过滤以及桌面搜索应用的检索系统，我们相信这是第一个具有如此高性能的开源系统。</span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体"><em>Copyright@戴维 2005.8 于北京</em></span></p>
<p><span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体">参考文献：</span></p>
<p style="TEXT-ALIGN: left"><span style="FONT-SIZE: 0.9em"><span style="FONT-FAMILY: Times New Roman"><span style="FONT-FAMILY: CMR8">[1] Trevor Strohman, Donald Metzler, Howard Turtle, and W. Bruce Croft,</span> <span style="FONT-FAMILY: CMTI8">Indri: A language model-based serach engine for complex queries</span><span style="FONT-FAMILY: CMR8">, IA 2005: Proceedings of the 2nd International Conference on Intelligence Analysis (to appear), 2005.</span></span></span></p>
<p style="TEXT-ALIGN: left"><span style="FONT-SIZE: 0.9em"><span style="FONT-FAMILY: Times New Roman"><span style="FONT-FAMILY: CMR8">[2] Donald Metzler, Victor Lavrenko, and W. Bruce Croft,</span> <span style="FONT-FAMILY: CMTI8">Formal multiple-bernoulli models for language modeling</span><span style="FONT-FAMILY: CMR8">, Proceedings of ACM SIGIR 2004, 2004, pp. 540-541.</span></span></span></p>
<p style="TEXT-ALIGN: left"><span style="FONT-SIZE: 0.9em"><span style="FONT-FAMILY: Times New Roman"><span style="FONT-FAMILY: CMR8">[3] Raghu Ramakrishnan and Johannes Gehrke,</span> <span style="FONT-FAMILY: CMTI8">Database management systems</span><span style="FONT-FAMILY: CMR8">, McGraw-Hill Higher Education, 2000.</span></span></span></p>
<p style="TEXT-ALIGN: left"><span style="FONT-SIZE: 0.9em"><span style="FONT-FAMILY: Times New Roman"><span style="FONT-FAMILY: CMR8">[4] Jim Gray and Andreas Reuter,</span> <span style="FONT-FAMILY: CMTI8">Transaction processing: Concepts and techniques</span><span style="FONT-FAMILY: CMR8">, Morgan Kaufmann, 1993.</span></span></span></p>
<p style="TEXT-ALIGN: left"><span style="FONT-SIZE: 0.9em"><span style="FONT-FAMILY: Times New Roman"><span style="FONT-FAMILY: CMR8">[5] Philip A. Bernstein and Nathan Goodman,</span> <span style="FONT-FAMILY: CMTI8">Multiversion concurrency control˙theory and algorithms</span><span style="FONT-FAMILY: CMR8">, ACM Trans. Database Syst.</span> <span style="FONT-FAMILY: CMBX8">8</span> <span style="FONT-FAMILY: CMR8">(1983), no. 4, 465-483.</span></span></span></p>
<p style="TEXT-ALIGN: left"><span style="FONT-SIZE: 0.9em"><span style="FONT-FAMILY: Times New Roman"><span style="FONT-FAMILY: CMR8">[6] Eric W. Brown,</span> <span style="FONT-FAMILY: CMTI8">Fast evaluation of structured queries for information retrieval</span><span style="FONT-FAMILY: CMR8">,SIGIR&#8217;95:Proceedings of the 18th annual international ACM SIGIRconference onResearch and development in information retrieval (NewYork, NY, USA),ACM Press, 1995, pp. 30-38.</span></span></span></p>
<p style="TEXT-ALIGN: left"><span style="FONT-SIZE: 0.9em"><span style="FONT-FAMILY: Times New Roman"><span style="FONT-FAMILY: CMR8">[7] Nicholas Lester, Justin Zobel, and Hugh E. Williams,</span> <span style="FONT-FAMILY: CMTI8">In-place versus re-build versus re-merge: index maintenance strategies for text retrieval systems</span><span style="FONT-FAMILY: CMR8">, Proceedings of the 27th conference on Australasian computer science, Australian Computer Society, Inc., 2004, pp. 15-23.</span></span></span></p>
<p>相关链接：<br /><a href="http://newhaven.lti.cs.cmu.edu/indri/" rel="nofollow"><span style="COLOR: #0e61b2">http://newhaven.lti.cs.cmu.edu/indri/</span></a></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.zye.me/2011/06/5150.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Inside Lucene/超人气搜索引擎学习(2.0)-读取索引</title>
		<link>http://blog.zye.me/2011/05/17463.html</link>
		<comments>http://blog.zye.me/2011/05/17463.html#comments</comments>
		<pubDate>Fri, 27 May 2011 14:30:02 +0000</pubDate>
		<dc:creator>yezheng</dc:creator>
				<category><![CDATA[information Retrieval]]></category>
		<category><![CDATA[indexing]]></category>
		<category><![CDATA[lucene]]></category>
		<category><![CDATA[信息检索]]></category>
		<category><![CDATA[索引]]></category>

		<guid isPermaLink="false">http://www.5yiso.cn/2008/03/17463.html</guid>
		<description><![CDATA[以前搜集的两篇文章，感觉对lucene的研究能稍微深入点的，想研究或者修改源代码的话值得一看。 下面是源地址 Inside Lucene/超人气搜索引擎学习(2.0)-读取索引 Index in Practice 索引: 按图索骥 T ermDoc从哪读取数据,自然是硬盘上已经建好的某个index, 具体说, 是从index中的某个文件读取. 要了解TermDoc读了什么东东,怎么读这些东东,必要时得考察Lucene index的细部结构. T ermDoc是个抽象类,这很好,以后可以创建自己的index结构,建立自己的搜索算法.不过这之前先要了解Lucene是怎么干的,而这个抽象类并不包含这个信息,所以,我们首先要找到TermQuery使用哪个TermDoc实现. 回想一下scorer中的TermDoc从哪里来. public class TermQuery extends Query { private class TermWeight implements Weight { public Scorer scorer(IndexReader reader) throws IOException { TermDocs termDocs = reader.termDocs(term); if (termDocs == null) return null; return new TermScorer(this, termDocs, getSimilarity(searcher), reader.norms(term.field())); } ... <a href='http://blog.zye.me/2011/05/17463.html'>[...]</a>]]></description>
			<content:encoded><![CDATA[<div class="postTitle"><em>以前搜集的两篇文章，感觉对lucene的研究能稍微深入点的，想研究或者修改源代码的话值得一看。</em> 下面是源地址</div>
<div class="postTitle"><a href="http://blog.csdn.net/bluemiles/archive/2006/07/24/968433.aspx">Inside Lucene/超人气搜索引擎学习(2.0)-读取索引</a></div>
<div class="postText">
<p><span style="FONT-SIZE: 180%"><strong><span style="FONT-SIZE: 0.75em">Index in Practice 索引: 按图索骥</span></strong></span></p>
<p><span style="FONT-SIZE: 180%"><strong><span style="FONT-SIZE: 1.5em">T</span></strong></span> ermDoc从哪读取数据,自然是硬盘上已经建好的某个index, 具体说, 是从index中的某个文件读取. 要了解TermDoc读了什么东东,怎么读这些东东,必要时得考察Lucene index的细部结构.</p>
<p><span style="FONT-SIZE: 1.5em; FONT-FAMILY: Courier New"><strong>T</strong></span> ermDoc是个抽象类,这很好,以后可以创建自己的index结构,建立自己的搜索算法.不过这之前先要了解Lucene是怎么干的,而这个抽象类并不包含这个信息,所以,我们首先要找到TermQuery使用哪个TermDoc实现.</p>
<p>回想一下scorer中的TermDoc从哪里来. <br /><code><br />public class TermQuery extends Query { <br />private class TermWeight implements Weight { <br />public Scorer scorer(IndexReader reader) throws IOException { <br />TermDocs termDocs = reader.termDocs(term); </p>
<p>if (termDocs == null) <br />return null; </p>
<p>return new TermScorer(this, termDocs, getSimilarity(searcher), <br />reader.norms(term.field())); <br />} <br />... <br />} <br />... <br />} <br /></code> <br /><span style="FONT-SIZE: 1.5em; FONT-FAMILY: Courier New"><strong>从</strong></span> 这段代码能找到真正创建TermDocs的那个类: IndexReader <br />用哪个TermDocs实现并不是TermQuery说了算,而是IndexReader的权利. TermQuery得到怎样一个TermDocs, 全由我们传递给TermQuery.weight.scorer()的那个IndexReader决定. 将这个TermDocs定位到指定的Term也完全由IndexReader负责。很遗憾,IndexReader也是抽象类. 想知道内幕?先找找IndexReader实现类。</p>
<p><span style="FONT-SIZE: 1.5em; FONT-FAMILY: Courier New"><strong>如</strong></span> 果按照用户手册的方法进行搜索, IndexReader的一个静态方法将被调用,它返回我们需要的一个IndexReader实现:SegmentReader, 这是整个查询中用到的reader。</p>
<p><span style="FONT-SIZE: 1.5em; FONT-FAMILY: Courier New"><strong>顺</strong></span> 藤摸瓜,很容易找到SegmentTermDocs这个类,也就是默认查询中SegmentReader使用的TermDocs,大部分查询结果通过这个类的实例来遍历.现在是时候翻它老底了,看看它怎么遍历数据,这些数据又从哪来. <br />&lt;code&gt; <br />class IndexReader{ <br />public TermDocs termDocs(Term term) throws IOException { <br />TermDocs termDocs = termDocs(); <br />termDocs.seek(term); <br />return termDocs; <br />} <br />&#8230; <br />}</p>
<p>class SegmentReader extends IndexReader{ <br />public final TermDocs termDocs() throws IOException { <br />return new SegmentTermDocs(this); <br />} <br />&#8230; <br />} <br />&lt;/code&gt;</p>
<p><span style="FONT-SIZE: 1.5em; FONT-FAMILY: Courier New"><strong>从</strong></span> 已经列出的代码中, 能清晰地看到SegmentTermDocs从创建到传递给scorer前进行的一系列动作:</p>
<p>1. SegmentTermDocs构造: 根据parent设定自己的属性 <br />2. IndexReader调用TermDocs.seek(term); 实现类中这一步具体化为SegmentReader调用SegmentTermDocs.seek(term)</p>
<p><span style="FONT-SIZE: 1.5em; FONT-FAMILY: Courier New"><strong>第</strong></span> 二步中, SegmentTermDocs进行了实际对index文件的读取. 而为了进行这些IO操作, 像前边说的, 必须依靠IndexReader才能完成, 这就是SegmentTermDocs构造是需要参数SegmentReader的原因.</p>
<p>seek (term)方法中SegmentTermDocs利用构造函数的唯一参数IndexReader(也就是创建它的那个reader, 称作parent&#8221;), 在硬盘索引文件中定位指定的term, 读入相关信息:df(包含term的文档数), 以及满足该term的文档集合在index文件中的位置. 这个位置后面, 是创建索引时就已排好的包含这个term的文档信息.</p>
<p>seek 完成后, TermDoc已经准备好读取数据了, 只要一声令下, TermDoc.read方法立刻能把每一篇文档的id和该term在这篇文档中的次数tf. 前面的记载是, scorer对象调用read方法, 尔后遍历其返回的全部文档, 把他们一个个塞到Collector中</p>
<p>精妙繁复的步骤: seek如何完成?</p>
<p><span style="FONT-SIZE: 1.5em; FONT-FAMILY: Courier New"><strong>这</strong></span> 要涉及索引结构, 现在可以掀开索引文件的一个角, 偷窥下.</p>
<p>tis文件: Term InformationS <br />frq文件: FReQuency</p>
<p><span style="FONT-SIZE: 1.5em; FONT-FAMILY: Courier New"><strong>必</strong></span> 须注意到IO动作一定是在IndexReader的几个成员中作的, 所有其他类中的IO要么用这些成员的Clone来完成, 要么直接代理给IndexReader. SegmentTermDocs.seek(term)动作是通过IndexReader进行的, SegmentTermDocs把创建他的IndexReader尊为parent, 在seek这种关键时刻利用IndexReader来读取索引数据. 没办法, 索引文件的读取(输入流的建立和定位)全由IndexReader负责. <br /><span style="FONT-SIZE: 1.5em; FONT-FAMILY: Courier New"><strong>s</strong></span> eek 方法中为了实现定位而利用了IndexReader一个负责Term定位的成员tis, 从他的类名TermInfoReader看就知道有什么用途. 这个tis从.tis文件中找到我们指定的term, 读出一切我们需要的信息: 这个term在多少个文档中出现过(df)/这些文档记录在frq文件的什么位置(起始位置) 等等.</p>
<p><span style="FONT-SIZE: 1.5em; FONT-FAMILY: Courier New"><strong>得</strong></span> 到这些信息后, TermDoc再自己seek, 这一步很简单, 除了几个加法和赋值, 唯一有特色的是对.frq文件的输入流(FileInputStream)进行seek(), seek()的数量正好是tis返回的&#8221;文档记录在frq文件中的位置&#8221;. 这个流是IndexReader初始化时创建的, 专门从frq文件读数据. IndexReader创建TermDoc时, TermDoc把这个输入流Clone()了一下, 赋给自己的成员. 这一seek()把.frq文件的输入指针定位好, 以后真正需要这个流的地方只有从frq文件读文档数据那一阵. 读数据的过程就发生在屡次提到过的termDoc.read()里, 现在我知道这个方法的实现是SegmentTermDocs.read().</p>
<p>read ()的实现是简单的顺序读取文件流, 具体过程涉及Lucene索引文件的二进制结构, 我不想这时候过多地纠缠. 大致了解termDoc如何定位数据, 心中的疑惑就能解开一半. 关于索引文件结构、各文件的关系、程序如何厘清这些关系, 还值得更多的讨论.</p>
<p><span style="FONT-SIZE: 1.5em; FONT-FAMILY: Courier New"><strong>到</strong></span> 这一步, 结合已熟知的scorer调用TermDoc的方式, 查询过程的基本途径已经隐约呈现出来了.</p>
</p></div>
]]></content:encoded>
			<wfw:commentRss>http://blog.zye.me/2011/05/17463.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>提高lucene索引速度技巧汇总</title>
		<link>http://blog.zye.me/2011/05/9319.html</link>
		<comments>http://blog.zye.me/2011/05/9319.html#comments</comments>
		<pubDate>Thu, 19 May 2011 02:28:17 +0000</pubDate>
		<dc:creator>yezheng</dc:creator>
				<category><![CDATA[information Retrieval]]></category>
		<category><![CDATA[indexing]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[lucene]]></category>
		<category><![CDATA[信息检索]]></category>
		<category><![CDATA[索引]]></category>

		<guid isPermaLink="false">http://www.5yiso.cn/2008/02/9319.html</guid>
		<description><![CDATA[ImproveIndexingSpeed How to make indexing faster Here are some things to try to speed up the indexing speed of your Lucene application. Please see ImproveSearchingSpeed for how to speed up searching. doubanclaim4c08e02b1af5eace Be sure you really need to speed things up. Many of the ideas here are simple to try, but others will necessarily add <a href='http://blog.zye.me/2011/05/9319.html'>[...]</a>]]></description>
			<content:encoded><![CDATA[<h1>ImproveIndexingSpeed</h1>
<div class="u_bd">
<div>
<h2>How to make indexing faster</h2>
<p>Here are some things to try to speed up the indexing speed of your Lucene application. Please see <a href="http://wiki.apache.org/lucene-java/ImproveSearchingSpeed"><span style="color: #22148d;">ImproveSearchingSpeed</span></a> for how to speed up searching. doubanclaim4c08e02b1af5eace</p>
<ul>
<li><strong>Be sure you really need to speed things up.</strong>
<p>Many of the ideas here are simple to try, but others will necessarily add some complexity to your application. So be sure your indexing speed is indeed too slow and the slowness is indeed within Lucene.</li>
<li><strong>Make sure you are using the latest version of Lucene.</strong></li>
<li><strong>Use a local filesystem.</strong>
<p>Remote filesystems are typically quite a bit slower for indexing. If your index needs to be on the remote fileysystem, consider building it first on the local filesystem and then copying it up to the remote filesystem.</li>
<li><strong>Get faster hardware, especially a faster IO system.</strong></li>
<li><strong>Open a single writer and re-use it for the duration of your indexing session.</strong></li>
<li><strong>Flush by RAM usage instead of document count.</strong>
<p>Call <a href="http://lucene.apache.org/java/docs/api/org/apache/lucene/index/IndexWriter.html#ramSizeInBytes()"><img src="http://wiki.apache.org/wiki/modern/img/moin-www.png" alt="[WWW]" width="11" height="11" /> <span style="color: #22148d;">writer.ramSizeInBytes()</span></a> after every added doc then call <a href="http://lucene.apache.org/java/docs/api/org/apache/lucene/index/IndexWriter.html#flush()"><img src="http://wiki.apache.org/wiki/modern/img/moin-www.png" alt="[WWW]" width="11" height="11" /> <span style="color: #22148d;">flush()</span></a> when it&#8217;s using too much RAM. This is especially good if you have small docs or highly variable doc sizes. You need to first set <a href="http://lucene.apache.org/java/docs/api/org/apache/lucene/index/IndexWriter.html#setMaxBufferedDocs(int)"><img src="http://wiki.apache.org/wiki/modern/img/moin-www.png" alt="[WWW]" width="11" height="11" /> <span style="color: #22148d;">maxBufferedDocs</span></a> large enough to prevent the writer from flushing based on document count. However, don&#8217;t set it too large otherwise you may hit <a href="http://issues.apache.org/jira/browse/LUCENE-845"><img src="http://wiki.apache.org/wiki/modern/img/moin-www.png" alt="[WWW]" width="11" height="11" /> <span style="color: #22148d;">LUCENE-845</span></a> . Somewhere around 2-3X your &#8220;typical&#8221; flush count should be OK.</li>
<li><strong>Use as much RAM as you can afford.</strong>
<p>More RAM before flushing means Lucene writes larger segments to begin with which means less merging later. Testing in <a href="http://issues.apache.org/jira/browse/LUCENE-843"><img src="http://wiki.apache.org/wiki/modern/img/moin-www.png" alt="[WWW]" width="11" height="11" /> <span style="color: #22148d;">LUCENE-843</span></a> found that around 48 MB is the sweet spot for that content set, but, your application could have a different sweet spot.</li>
<li><strong>Turn off compound file format.</strong>
<p>Call <a href="http://lucene.apache.org/java/docs/api/org/apache/lucene/index/IndexWriter.html#setUseCompoundFile(boolean)"><img src="http://wiki.apache.org/wiki/modern/img/moin-www.png" alt="[WWW]" width="11" height="11" /> <span style="color: #22148d;">setUseCompoundFile(false)</span></a> . Building the compound file format takes time during indexing (7-33% in testing for <a href="http://issues.apache.org/jira/browse/LUCENE-888"><img src="http://wiki.apache.org/wiki/modern/img/moin-www.png" alt="[WWW]" width="11" height="11" /> <span style="color: #22148d;">LUCENE-888</span></a> ). However, note that doing this will greatly increase the number of file descriptors used by indexing and by searching, so you could run out of file descriptors if mergeFactor is also large.</li>
<li><strong>Re-use Document and Field instances</strong>
<p>As of Lucene 2.3 (not yet released) there are new setValue(&#8230;) methods that allow you to change the value of a Field. This allows you to re-use a single Field instance across many added documents, which can save substantial GC cost.</p>
<p>It&#8217;s best to create a single Document instance, then add multiple Field instances to it, but hold onto these Field instances and re-use them by changing their values for each added document. For example you might have an idField, bodyField, nameField, storedField1, etc. After the document is added, you then directly change the Field values (idField.setValue(&#8230;), etc), and then re-add your Document instance.</p>
<p>Note that you cannot re-use a single Field instance within a Document, and, you should not change a Field&#8217;s value until the Document containing that Field has been added to the index. See <a href="http://lucene.apache.org/java/docs/api/org/apache/lucene/document/Field.html"><img src="http://wiki.apache.org/wiki/modern/img/moin-www.png" alt="[WWW]" width="11" height="11" /> <span style="color: #22148d;">Field</span></a> for details.</li>
<li><strong>Re-use a single Token instance in your analyzer</strong>
<p>Analyzers often create a new Token for each term in sequence that needs to be indexed from a Field. You can save substantial GC cost by re-using a single Token instance instead.</li>
<li><strong>Use the char[] API in Token instead of the String API to represent token Text</strong>
<p>As of Lucene 2.3 (not yet released), a Token can represent its text as a slice into a char array, which saves the GC cost of new&#8217;ing and then reclaiming String instances. By re-using a single Token instance and using the char[] API you can avoid new&#8217;ing any objects for each term. See <a href="http://lucene.apache.org/java/docs/api/org/apache/lucene/analysis/Token.html"><img src="http://wiki.apache.org/wiki/modern/img/moin-www.png" alt="[WWW]" width="11" height="11" /> <span style="color: #22148d;">Token</span></a> for details.</li>
<li><strong>Use autoCommit=false when you open your <a href="http://wiki.apache.org/lucene-java/IndexWriter"><span style="color: #22148d;">IndexWriter</span></a></strong>
<p>In Lucene 2.3 (not yet released), there are substantial optimizations for Documents that use stored fields and term vectors, to save merging of these very large index files. You should see the best gains by using autoCommit=false for a single long-running session of <a href="http://wiki.apache.org/lucene-java/IndexWriter"><span style="color: #22148d;">IndexWriter</span></a> . Note however that searchers will not see any of the changes flushed by this <a href="http://wiki.apache.org/lucene-java/IndexWriter"><span style="color: #22148d;">IndexWriter</span></a> until it is closed; if that is important you should stick with autoCommit=true instead or periodically close and re-open the writer.</li>
<li><strong>Instead of indexing many small text fields, aggregate the text into a single &#8220;contents&#8221; field and index only that (you can still store the other fields).</strong></li>
<li><strong>Increase <a href="http://lucene.apache.org/java/docs/api/org/apache/lucene/index/IndexWriter.html#setMergeFactor(int)"><img src="http://wiki.apache.org/wiki/modern/img/moin-www.png" alt="[WWW]" width="11" height="11" /> <span style="color: #22148d;">mergeFactor</span></a> , but not too much.</strong>
<p>Larger <a href="http://lucene.apache.org/java/docs/api/org/apache/lucene/index/IndexWriter.html#setMergeFactor(int)"><img src="http://wiki.apache.org/wiki/modern/img/moin-www.png" alt="[WWW]" width="11" height="11" /> <span style="color: #22148d;">mergeFactors</span></a> defers merging of segments until later, thus speeding up indexing because merging is a large part of indexing. However, this will slow down searching, and, you will run out of file descriptors if you make it too large. Values that are too large may even slow down indexing since merging more segments at once means much more seeking fo<br />
r the hard drives.</li>
<li><strong>Turn off any features you are not in fact using.</strong>
<p>If you are storing fields but not using them at query time, don&#8217;t store them. Likewise for term vectors. If you are indexing many fields, turning off norms for those fields may help performance.</li>
<li><strong>Use a faster analyzer.</strong>
<p>Sometimes analysis of a document takes alot of time. For example, StandardAnalyzer is quite time consuming, especially in Lucene version &lt;= 2.2. If you can get by with a simpler analyzer, then try it.</li>
<li><strong>Speed up document construction.</strong>
<p>Often the process of retrieving a document from somewhere external (database, filesystem, crawled from a Web site, etc.) is very time consuming.</li>
<li><strong>Don&#8217;t optimize unless you really need to (for faster searching).</strong></li>
<li><strong>Use multiple threads with one IndexWriter.</strong>
<p>Modern hardware is highly concurrent (multi-core CPUs, multi-channel memory architectures, native command queuing in hard drives, etc.) so using more than one thread to add documents can give good gains overall. Even on older machines there is often still concurrency to be gained between IO and CPU. Test the number of threads to find the best performance point.</li>
<li><strong>Index into separate indices then merge.</strong>
<p>If you have a very large amount of content to index then you can break your content into N &#8220;silos&#8221;, index each silo on a separate machine, then use the writer.addIndexesNoOptimize to merge them all into one final index.</li>
<li><strong>Run a Java profiler.</strong>
<p>If all else fails, profile your application to figure out where the time is going. I&#8217;ve had success with a very simple profiler called <a href="http://www.khelekore.org/jmp"><img src="http://wiki.apache.org/wiki/modern/img/moin-www.png" alt="[WWW]" width="11" height="11" /> <span style="color: #22148d;">JMP</span></a> . There are many others. Often you will be pleasantly surprised to find some silly, unexpected method is taking far too much time.</li>
</ul>
</div>
<h2>See also:</h2>
<div></div>
<div class="u_bd">http://wiki.apache.org/jakarta-lucene/ImproveIndexingSpeed</div>
<div class="u_bd">http://hi.baidu.com/expertsearch/blog/item/393c702cbede6c33359bf706.html</div>
</div>
]]></content:encoded>
			<wfw:commentRss>http://blog.zye.me/2011/05/9319.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>如何把lucene索引放在内存中提供查询服务</title>
		<link>http://blog.zye.me/2007/02/6009.html</link>
		<comments>http://blog.zye.me/2007/02/6009.html#comments</comments>
		<pubDate>Thu, 22 Feb 2007 07:28:00 +0000</pubDate>
		<dc:creator>yezheng</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[indexing]]></category>
		<category><![CDATA[lucene]]></category>
		<category><![CDATA[信息检索]]></category>
		<category><![CDATA[内存]]></category>
		<category><![CDATA[索引]]></category>

		<guid isPermaLink="false">http://www.5yiso.cn/2007/02/6009.html</guid>
		<description><![CDATA[200W以下的数据量全部加载到内存最简单的方式是修改Lucene（1.9版本）源码 org.apache.lucene.index.IndexReader文件的第127行 将 return open(FSDirectory.getDirectory(path, false), true); 修改为 return open(new RAMDirectory(FSDirectory.getDirectory(path, false)), true); 将133行的 return open(FSDirectory.getDirectory(path, false), true); 修改为 return open(new RAMDirectory( FSDirectory.getDirectory(path, false)), true); 这样就可以在查询的时候将所有索引一次加载到内存，查询响应时间即使是第一次查询，也会小于0.1秒，但仅适用于索引文档数量不超过200W个，并且平均的页面大小不超过10K，文献检索系统 ，这种方式尤其适合。 创建索引的时候也能够使用 RAMDirectory ， 比FSDirectory 要快很多 。 令一种方法： FSDirectory directory = FSDirectory.getDirectory(args[0], false); 1 RAMDirectory directory = new RAMDirectory(args[0]); 2 用2代替1即可]]></description>
			<content:encoded><![CDATA[<p>200W以下的数据量全部加载到内存最简单的方式是修改Lucene（1.9版本）源码 org.apache.lucene.index.IndexReader文件的第127行 将 <br />return open(FSDirectory.getDirectory(path, false), true); <br />修改为 <br />return open(new RAMDirectory(FSDirectory.getDirectory(path, false)), true); </p>
<p>将133行的 <br />return open(FSDirectory.getDirectory(path, false), true); <br />修改为 <br />return open(new RAMDirectory( FSDirectory.getDirectory(path, false)), true); <br />这样就可以在查询的时候将所有索引一次加载到内存，查询响应时间即使是第一次查询，也会小于0.1秒，但仅适用于索引文档数量不超过200W个，并且平均的页面大小不超过10K，文献检索系统 ，这种方式尤其适合。 <br />创建索引的时候也能够使用 RAMDirectory ， 比FSDirectory 要快很多 。</p>
<p>令一种方法：</p>
<p style="TEXT-ALIGN: left">FSDirectory directory = FSDirectory.<em>getDirectory</em>(args[0], <strong>false</strong>); 1</p>
<p>RAMDirectory directory = new RAMDirectory(args[0]); 2</p>
<p>用2代替1即可</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.zye.me/2007/02/6009.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

