TT的作者出新作品鸟:kyoto tycoon

嗯 ,这个TT不是那个宾馆床头的TT.它是tokyo tyrant.作者在推出了一堆性能出色的开源产品后,近期又推出了一个基于kyoto cabinet的server:kyoto tycoon.
其实关于kyoto cabinet,我在这里提到过。kyoto cabinet对应的就是tokyo cabinet,而跟tokyo tyrant对应的,就是今天要介绍的kyototycoon.
直接上翻译,原文在:http://www.1978th.net/kyototycoon/spex.html

Kyoto Tycoon 是一个轻量级的数据库服务器, 支持自动过期的实现,这个在各种需要处理缓存数据的程序中很有用。Kyoto Tycoon 也是一个叫Kyoto cabinet的DBM的网络接口。虽然这个DBM拥有很高的性能和并发能力,但是你在处理多进程共享一个数据库,或是需要远程访问一个数据库时感到头痛。而Kyoto Tycoon就是提供来处理Kyoto cabinet的并发处理和远程连接的。Kyoto tycoon由管理各种各样数据库的服务器进程和访问服务器的客户端程序库组成。

The server features high concurrency due to thread-pool modeled implementation and the epoll/kqueue mechanism of the modern Linux/*BSD kernel. It can handle more than 10 thousand connections at the same time. Because such system-specific features as epoll/kqueue are encapsulated and abstracted as the same interface, Kyoto Tycoon has high portability and works almost all UNIX-like systems and Windows.

这个服务器的特性是高并发,这是源于它用了现在linux/BSD内核上的epoll/Kqueue机制以及一个线程池模型实现。它能同时处理高于10k的并发连接。因于像epoll/kqueue这些OS的属性都做了封闭并且抽象得如此之相像,Kyoto Tycoon拥有较高的移植性,能工作在几乎所有类unix系统 和windows系统上。(嗯,作者你就吹吧…)

它的客户端和服务器端是通过HTTP协议来交互的。因此,你可以用任何流行的语言来写它的客户端库和客户端程序。Restful风格的GET,HEAD,PUT,DELETE方法和RPC风格的POST式方法都是它所支持的。RPC风格的接口是基于一个叫TSV-RPC的协议之上的。(嗯 ,这个协议好像不是一个标准协议,如果我没猜错的话,应该是作者自创的,在之前的tokyocabinet什么的库中就出现地p).整个请求的主体系和响应数据都是用tab键分格的值 ,所以解析起来是相当容易。(嗯,是很容易 ,那我想问一下,大哥你怎么表示复杂数据?我猜您的答案:那我们就不支持复杂数据类型好了。。。)。

服务器端可以嵌入lua,一个轻量级的脚本语言。虽然你可能无法找到符合你需求的内置操作类型,你可以在lua中定义函数来实现各种各样的变态的无聊的抑或是吃饱了没事的干的需求。(我这个翻译是不是离主题太远了).Lua脚本的API提供了Kyoto cabinet的全套的各种操作,包括有访问,游标和事务机制。

Kyoto Tycoon的服务器端是用C++ 实现的(好吧,最近作者除开原始c和lua以外,终于学会C++了?嗯 ,其实kyoto cabinet也是c++写的),它在各种有 支持c++03标准TR1扩展的系统上都是可用的。Kyoto tycoon是一个以GNU GPL 的license发布的开源程序。如果你使用HTTP协议和服务端通信而不使用核心库的话,你可以写各种不受我们license控制的客户端程序。

[译文]Xapian 1.0索引/查询时的term处理方式

这是译文,原文地址:http://xapian.org/docs/termgenerator.html

翻译:一米六二(renlu.xu@gmail.com)

Xapian 1.0索引/查询时的term处理方式

在Xapian 1.0,默认的索引方式已发生了重大变化(这些都是来自旧的索引方式在真实世界 中的实际经验教训).本文档介绍了新计划,并跟过去的旧方式做对比.

词干处理

最明显的区别是抽取词干后term的处理。

在此之前,所有的词都是词干后再索引,但是没有前缀;所有大写词都并不做词干抽取处理(但是会变小写),并加一下”R”前缀.这样做的理由是,人们希望能够搜索准确的名字(比如像英语词干提取就合并了Tony和Toni 。但当然,这也使得句子开头的一个词,标题文字,另外德语所有名词都是大写,这些都是会这样编入索引。无论是正常的和带R的前缀都会带着位置信息被索引。

现在,我们索引所有词的小写形同时带上位置信息,以及词语未做词干抽取状态加上”Z”的前缀,但这时不带位置信息.默认情况下会使用一个Xapian::Stopper 以避免索引stopwords的词根(测试表明,这会省下数据库1%的大小)。

新的索引方式允许精确短语搜索(这是旧的方式所不支持的)。现在NEAR 操作符只能应用在词语的未取词干的形式上,不过这也够了.我们还可以禁用对查询中大写词语的词根抽取,以在对物定名词搜索时获得更好的效果。现在Omega的$topterms 总是能正确建议一个未抽取词根的形式!

为词根形式添加前缀的主要理由是,他们的量比较少!另外,一个附带好处是,它开创了一种多种语言的词根形式,如Z:en ,Z:fr: 或其他的随便什么语言.

QueryParser中对.结尾的特殊处理(这会因为人们粘贴过来的一段文字而错误地被触发)已经去掉了.此功能之所以出现,主要是为了支持欧米茄的topterms加入词根形式;但是现在Omega已经可以建议未取词干的形式,因此就不再需要这个功能了.

成词字符(能组成词的字符,比如英文中26个字符就是,而|符号和:号就不是)

默认情况下,Unicode字符的CONNECTOR_PUNCTUATION 分类下的字符(_和其他常见的一些字符)不用做其他形式的处理就是成词字符了,这样可以更好地索引一些标识符.以前像time_t这种就需要一个短语搜索了.

尾部的+和#仍然可以包含在Term中(尾部最多可以有3个这样的字符),但-默认不可以了。把他们包含有进来会有益的例子(nethack–,cl-),好处并不十分明显,相反,他们作为连字符更常见更有用.这些例子并不引人注目的好处是它( 的nethack -和 Cl -),它往往胶连字符到条款。

嵌入的单引号’((撇号)现在可以包含在Term中了.以前版本中这会使得短语搜索变慢,同时索引中会被带进来垃圾Term(didn’t 会变成ditn 和t)。各种Unicode使用的撇号都映射到了ASCII的形式。

其他很少量的字符(这些字符取一个词的Unicode的定义中)如果是包含在两个成词字符中的话也可以视为是Term的一部分;,。,,和少量其他字符在出现在两个十进制字符之间时会视为Term的一部分.

Xapian的查询分析器

英文地址:http://xapian.org/docs/queryparser.html

Xapian::QueryParser的语法

本文档介绍了Xapian::QueryParser类支持的查询语法.这套语法设计得跟其他基于Web的搜索引擎的语法类似,这样用户就会很熟悉,不用从头学习一个全新的语法。

操作符

AND

Expression AND expression 匹配两个条件都符合的文档.

OR

Expression AND expression 匹配符合两个条件中任何一个的文档.

NOT

expression NOT expression 匹配那些只匹配第一个条件的文档.这个也可以写成expression AND NOT expression.如果您设置了FLAG_PURE_NOT,那么

NOT expression 就匹配了所有不符合这个表达式的文档

XOR

expression XOR expression 匹配那些符合两个条件中的一个的文档;但是不能两个都符合.XOR可能有点不好理解。

带括号的表达式

你可以括号来调整布尔运算符的优先级.在one OR two AND three这个查询中,AND优先,这跟one OR (two AND three)是一样的.你可以用(one OR two) AND three来改变优先级 。

+和 –

一组带+和-符号的term,会匹配所有带+的term,但是不匹配带-的term。而不带有+或-的term则影响搜索结果中文档的排名.你也可以给短语和括号表达式加上+或-符号

NEAR

one NEAR two NEAR three匹配那些各个term间的距离在10以内的文档.你也可以通过NEAR/n这种方法把默认的10这个值设置为其他的值,如one NEAR/6 two.

ADJ

ADJ像NEAR ,但只匹配的文字以相同的顺序出现的文档。因此, one ADJ two ADJ three匹配那些包含了这几个term词并且term间的距离在10以内的文档.您也可以通过ADJ/n语法来修改这个默认的阈值,例如:one ADJ/6 two.

短语搜索

用一对由双引号包起来的短语来搜索时,结果是精确匹配 这个短语的文档.带连字符的语语也作为短语处理,像邮箱和文件名称等 (比如. /etc/passwd 或是 president@whitehouse.gov).

搜索相关的域

如果数据库索引时已经为一些域设置了前缀,你可以设置一个域-前缀的Map,这样用户可以搜定搜索某些特定字段。例如author:dickens title:shop就可以搜索作者是狄更斯,名字包含”shop”的书。您也可以为双引号括起来一个短语指定前缀,例如(author:”charles dickens” ),或是为括号内的表达式指定前缀:(例如title:(mice men) )。

搜索固有名词

如何你搜一个首字母大写的词,它搜索时将不做词干原.

区间搜索

The QueryParser can be configured to support range-searching using document values.

这种范围搜索的语法是start .. end -例如, 01/03/2007..04/04/2007 , $10..100 , 5..10kg 。

同义词

QueryParser可以配置来支持同义词,它可以明确指定(使用语法~ term )或隐式(为所有term使用同义司,或是某些特别指出的一组term)。

通配符

QueryParser支持在结尾使用一个’*’通配符的操作,wildc*将匹配wildcard, wildcarded, wildcards, wildcat, wildcats, etc等.此功能默认禁用. 在调用Xapian::QueryParser::parse_query( query_stringflags )函数时通过传递Xapian::QueryParser::FLAG_WILDCARD给flags参数就可以启用它.使用QueryParser::set_database( database )来告诉QueryParser用哪个数据库来扩展通配符.

query 片断的匹配

查询分析器(Query Parser)支持搜索只输入了片断的查询.计划实现的是这样一种”增量搜索”的系统,这种系统里,不等用户输完查询条件就可以显示一些查询结果.例如,在一个系统中,用户每输入一个字符,系统就输出新的搜索结果(也可能是另一种情况,如用户每停顿一小会就输出新的搜索结果,或其他情况).这种搜索有一个毛病就是,通过搜查询条件的一个片断,得到的结果跟实际想要的可能完全没有语义上的联系.比如,想搜dynamic cat,可能返回的是dynamic categorisation的结果.这种情况下每输入一个字符搜索结果 都会快速地变化.如果将输入部分的视为包含一个隐含的尾部通配符,它会匹配所有以已输入字符结开头的词,这样我们可以得到更平滑变动的搜索结果—-每当新字符键入时,搜索结果都会更接近我们想要的主题. 一个简单有效的办法就是开启通配符选荐,并在查询字符串的尾部加上”*”字符串. 不过这会跟那些以标点结束或其他一些字符结束的的搜索搅不清.此功能默认情况下是禁用.

Xapian::QueryParser::parse_query( query_stringflags )函数时通过传递Xapian::QueryParser::FLAG_PARTIAL给flags参数就可以启用它.使用QueryParser::set_database( database )来告诉QueryParser用哪个数据库来扩展通配符.

译文:Xapian搜索结果的排序

这是译文,原文地址:http://xapian.org/docs/sorting.html

搜索结果的排序

默认情况下,Xapian按相关性递减的原则来排序搜索结果.然而,它也可以让搜索结果按其他指标,或其他指标和相关性的混合排序。

如果两个搜索结果从排序标准来判断是相同的分值,那他们的返回顺序就取决于它们的文档ID了.默认情况下,他们会按文档ID的升序排序(这样,一个较小的文档ID就会排前面),但可以设置为降序排列enquire.set_docid_order(enquire.DESCENDING);。如果你无所谓,你可以告诉Xapian随便使用任何一种排序都有序enquire.set_docid_order(enquire.DONT_CARE);。

按相关性排序

默认情况下Xapian使用的是BM25加权公式,它有许多参数可以设置。其中的部分参数我们使用默认值,在应付一般的工作时,它们工作得很好。这些参数的最佳值取决于数据被索引的方式和要运行的查询的类型,所以你也许可以改善一下,通过调整这些值来使搜索系统更有效,但它是一个繁琐的过程,人们通常懒得这么干。

BM25文件了更多的细节。

其他权重方式包括TradWeight和BoolWeight.

TradWeight实现了原来的概率加权公式,基本上是BM25的一种特殊情况(它就是BM25在K2 = 0,K3= 0,b = 1和min_normlen = 0的加权,只不过用一个常量线性缩放鸟)。

BoolWeight给所有文档,因此排序完全取决于其他因素。

您也可以实现自己的权重系统,只要用一个数值的形式来量化匹配的条件的(减去不匹配的条件),再考虑一些跟查询term无关的统计资料(比如说,标准化文件长度),就大功靠成鸟。

例如,这里有一个“几何匹配法” – 每个匹配就给一分:

class CoordinateWeight : public Xapian::Weight {
public:
CoordinateWeight * clone() const { return new CoordinateWeight; }
CoordinateWeight() { }
~CoordinateWeight() { }

std::string name() const { return “Coord”; }
std::string serialise() const { return “”; }
CoordinateWeight * unserialise(const std::string &) const {
return new CoordinateWeight;
}

Xapian::weight get_sumpart(Xapian::termcount, Xapian::doclength) const {
return 1;
}
Xapian::weight get_maxpart() const { return 1; }

Xapian::weight get_sumextra(Xapian::doclength) const { return 0; }
Xapian::weight get_maxextra() const { return 0; }

bool get_sumpart_needs_doclength() const { return false; }
};

由其他属性来排序

如果你想提供一个按日期“排序”功能,并且让文档按日期顺序被索引,那么通过使用一个布尔搜索,你可以实现一个非常有效的日期“排序”功能。布尔搜索通过enquire.set_weighting_scheme(Xapian::BoolWeight())实现,同时还要使用 enquire.set_docid_order(Xapian::Enquire::DESCENDING);如果你想最老的文档在前面,你可以用: enquire.set_docid_order(Xapian::Enquire::ASCENDING).很难说清楚有什么内在原因使这种技术不能用于按其他东东排序,通常我们总是最后索引新文章罢了,而最后索引的文章拥有较大的文档ID.嗯,其实我们只是在按文档ID排序而已.

按Value排序

你可以让文档通过比较某一特定Value来决定排序先后.请注意,在比较中是按字节序列来比较的,所以1 <10 <2。如果你要编码value,(使它按数值方式比较),请在索引时用Xapian::sortable_serialise()来编码Value;这同时适用于整数和浮点值:

Xapian::Document doc;
doc.add_value(0, Xapian::sortable_serialise(price));

有3种方法用来用来决定Value如何进行排序,而这取决于在排序里你是否使用和想如何使用相关性:

  • Enquire::set_sort_by_value(),相关性完全并不影响排序。
  • Enquire::set_sort_by_value_then_relevance()相关性只是用来决定在Value相同时文档如何排序.
  • Enquire::set_sort_by_relevance_then_value()指定了文档先按相关度排序,相关度相同时再按文档的Value排序(相同相关度:要的是权重也相同,而不只是用百分比表示的分数相同)。这种方法对于默认的BM25权重方式基本没啥用,因为默认的BM25权重方式几乎不会给不同的文档指定相同的分数.

按生成值排序

为了做到更精细的排序,Xapian正在考虑允许您从Xapian::Sorter继承一个子类,这个子类的函数可以用来为每个匹配的文件生成一个值,这个值就用来决定排序。这个函数针对每个匹配的文档顶多调一次,然后生成一个排序值,这个排序值会被按字节序来比较,以决定排序。

系统内置了一个标准的子类Xapian::MultiValueSorter,,它允许指定由文档的多个Value来决定文档的排序(当文档的第一个value相同时,第二个Value决定排序,第二个Value相同时,第3个Value决定排序,以此类推)。

Xapian::Sort也可以衍生出子类,以提供按地理距离排序的功能。子类可以用一个坐标(比如[纬度,经度])来代表用户的位置,然后用存储在文档中的座标来计算出跟用户之间的距离,使跟用户位置最近的结果得到更高的分数。

Xapian 术语表

原文地址:http://xapian.org/docs/glossary.html

术语表

本术语表定义了在使用xapian时可能遇到的一些专业术语.其中一些是信息检索领域的标准概念,而另一些则在xapian中有特别的意义.

BM25
xapian默认使用的加权方法。BM25是原来的概率加权算法的,而最近的TREC测试表明,BM25是已知的相关性衡量体系中最好的。有时它也被称为“Okapi BM25”,因为它是最先是在一个叫Okapi的学术性的IR系统中实现的。
布尔检索
检索跟一个布尔查询(例如,由一些运算符像AND,OR,AND_NOT等连接的term串)相匹配的一系列文档.在许多系统中,这些文件没有按照相关性来打分排序。在的xapian,一个纯粹的布尔查询可以使用,或者一个布尔式查询可以过滤检索文件,然后下令使用概率的排名。在Xpian中,你可以用一个单纯的Bool查询;也可以用一个布尔风格的查询,这时先检索相关的文档,并按排相关性来排序.
Chert
Chert是目前正在开发的1.1.x中使用的数据库格式.我们正在努力使它像flint一样可靠,唯一的不同是做了一些不兼容的改动,因此你可能需要重新索引您的的数据.而flint格式的数据库在xapian的几个版本中是相兼容的.在1.2.0版本中,chert将会成为可靠版本,并且成为默认的后端,而flint会被废弃.
数据库
跟关系性数据库不同,Xapian的数据库并不只是索引了文档内容,因为Xapian的目标是成为一个信息检索系统,而不是一个信息存储系统.这些问题偶尔也可能被称为索引。
文档ID
在xapian的数据库中,标识一个文章档的唯一ID,是一个正档数.
文档数据
文档数据是附加在一个文档上的的数据信息,可以是好几种信息,其内容可以设置在各种格式,比如可以存储URL,文档的标题,文档的文章本摘要等级.如果你想[篡改](我用词一向这么猥琐地选贬义词)Omega,可以发现Oemga的文档数据应该包含key-value对,每行一个.Omega的最新版本还支持每行一个字段值,在查询中可以指定行号和字段名的匹配关系)。
<String Name=”Document”>文档</String>
这就是一个个要被检索的项目。通常常他们会是文本文件(如网页,电子邮件,文字处理文档),但你也可以在文档中附加文件,或照片,视频,音乐,用户配置文件,或是其他任何你想要索引的玩意儿。
编辑距离
从一个词变到另一个词必须经过多少个”编辑”步骤.常用在拼写建议中.Xapian使用的算法,会将任何一个字符插入,删除,改变字符,或交换两个相邻字符都统计上.
ESet(展开集)
Eset是一个一系列的带分值的Term的列表,是原始查询的延伸.这些词统计学上都能比较好地区分相关文档和不相关文档的.
Flint
Flint是当前xapian使用的数据库格式从xapian 1.0起成为默认格式,并取代了Quartz。Flint相当地高效,也具有高可扩展性。它支持增量修改,并且支持对数据库单写多读的并发模式。
索引
如果一个文档是由一个Term描述,我们就说这个词索引了该文档。同样,在xapian和其他的信息检索系统中的数据库有时也叫做索引(形象地说,就像一本书背后的术语表)。
索引器
索引器接收(各种格式)的文章档并处理他们,以便能够有效地进行搜索,这些文档会被人存储在数据库中。
目标信息
用户想查到的信息。人们通常会用一个查询字符串来描述他们的目标信息
信息检索(IR)
信息检索是一门关于搜索的科学。它是一个学术上的名称,主要包含跟搜索相关的有关课题的研究.
MSet(匹配集)
匹配集(MSet)是从一个查询返回的一个带有分值的文档清单。这份名单根据文档的比重来打分,因此打分最高的文档具有最高的相关性,第二个文档第二位,依此类推。一个Mset里的文档数是可控的,因此它通常不包含匹配的所有文件。
标称文档长度(ndl)
标称文档长度(ndl)是一个文件的长度(它包含有的term的数目)除以系统内所有文档的平均长度得到的数值。因此,一个刚好是平均长度的文档,他的标称文档长度等于1,而较短的文件标称文档长度都小于1,和更长的文件则大于1。
Omega
Omega是用xapian来构建的,包含一个CGI的搜索程序和两个索引器.
Posting List
Posting list是一个term所索引的文档的列表这通以可以被看作是一列数字 – 这些数字都是文档ID。
Posting
一个term索引一篇文章的这样一个过程
准确率
准确率:索引结果中跟结果真正相关的文档的数量除以返回的文档总数.
基于概率的信息检索系统
概率IR是基于概率理论上产生的检索,它可以产生根据相关性来打分排名的文档清单。Xapian 利用了概率方法(唯一的例外是当用户选择了一个纯粹的布尔查询时)
Quartz
Quartz是xapian 1.0之前版本使用的数据库格式。现在已经过时了,并且xapian 1.1.0之后将不再支持。新的部署应该使用Flint,现有的则应考虑迁移到Flint。
查询
查询是信息检索系统能接收的对你的目标信息的描述.它通常是一个包含了一系列term的文本字符串,可能包括如AND或OR等布尔操作符.
Query Expansion
为了扩大搜索结果而修改查询
Rset(相关集)
相关集是由用户标记为相关的一系列文章档的集合.他们可以用来建议用户可能需要添加进查询的term(从Eset中来的term),也可以用来调校term的权重以对结果重新排序.
召回率
召回率是指检索到的真正相关的文档除以系统中存在的相关的文档的总数
相关性
基本上,用户想要的文档就是相关的文档。理想情况下,检索到的文件都将相关的,没检索到的都是不相关的。
搜索器
搜索器是信息检索系统中的一部分,它们接收查询,读取数据库并返回相关的文档.
词根
词根化,表示去掉单词的各种变式,(像过去式啦,动名形式啦),得到正常状态的词.在英语中,这主要涉及取消后缀 – 例如将讨论的字眼,像”talking”,”taks”,”talked”最终都回归’talk’这个词根.
停用词
在索引或搜索中被忽略的词,忽略原因一般是因为它们太常见了,或是根本不表达任何含意。例如,”the”,”a”,”to”.
同义词
Xapian可以存储一个term的同义词,这可以用来实现Query Expansion.
Term列表
Term 列表是索引一个特定文档的一系列term的列表.在某些信息检索系统中,这可以是一系列数字的列表(在系统内部一个term由一个对应的数字来描述),而在Xapian中则就是一个字符串(term)的列表.
词频
词频就是在一个信息检索系统中由一个term所检索的文档的数量.
Term
一个term通常是一个比特串(通常是一个词或一个词根),一个个的term就描述了整个文档.Term就跟一本书背后的术语表差不多,一个文档通常由许多的term来描述.(是不是一个书背后的术语表基本也就描述了这本书呢)查询是由一串Term构成的(也许由布尔运算符连接)。
Term前缀
按照惯例,Xapian中的Term可以加上前缀,用来实现对不同字段信息的区分,比如这文档从哪儿来,以及其他一些信息.(就是这东西实现了lucene中的field)这个Term前缀通常是一个大写字母。
测试集
测试集由一系列文档和一组查询构成,每个查询都已经指定出了完整的相关文档列表 – 这是用来测试不同的信息检索系统工作的能力如何。
UTF-8
一个变长的Unicode编码标准.
Value
附加到文档的离散的元数据.每个文件可以有许多Value,每个value都存在一个不同的带编号的位置中。Value被设计用来在匹配过程的快速访问,可用于排序,重复文档去除,范围搜索,以及其他用途。如果您只是想存储一个可以用来在结果显示的值,您最好储在文档的Data中。
文档内频率(wdf)
一个Term在一个文档里的的文档内频率(wdf),是这个term在索引过程中能取出文档的次数.通常就是wdp向量的大小,但在xapian可以比它大,因为我们可以为文档中的某一段儿申请额外的wdf.
文档内位置(wdp)
因为term来源于一个文档中实际存在的词,一个term的文档内位置就是指这些单词在文档中出现的位置(通常是向量,因为词往往在文档中出现多次)所以,如果一个Term来源于在文章档的第5,第二2个词和第131个词(其实就是这词在这三个位置出现),这个term的wdp就是(5,22,131)
查询内频率(wqf)
一个查询中,一个Term的查询内频率是在这个查询中这个term出现的次数.这一统计数字在BM25中用到。

Xapian搜索体系结构

这是对http://www.flax.co.uk/blog/2009/04/02/xapian-search-architecture/ 的简单翻译.

严格意义上,这篇投递跟flax无关,只是为向直接使用Xapian的人们介绍一下Xapian搜索的体系结构。这不是给有经验的Xapian hacker提供的,也不是介绍如何使用Xapian的入门文章(入门文章请访问这里)。
Xapian API是相当复杂的,而且在索引和搜索时,QueryParser,Term,document values 经常困惑着人们.要特别指出的是,Xapian本身并无一个”field”的概念,field这东西是flax的组件做的更高层次的抽象和封装.Xapian只是有Document ,包含一个整数标识ID,document包含:
Terms (通常是词或短语,可以带位置信息,带位置信息的叫POST),
VAlue (通常是一个简短的字符串,也可能是包含的二进制数据),以及
data (可以是任何数据,但往往是一些适合显示的文本)。
这三种类型的对象是完全独立的,虽然在一个应用程序级相关(terms经常是从document data中切分得到的)。在索引时,Term会由一个文本处理器生成,当然也可能是单独地直接给出.term一般是专为字串搜索设计的 –比如一个特定的词是否在某个文档中出现?Value被用于其他多种用途,例如如范围搜索(如日期),排序,以及其他一些跟应用程序相关的方面。document data不能被用于搜索,它主要被搜索程序在得到搜索结果后使用,像是在搜索结果列表中显示文件的信息等.
当document被添加到一个Xapian数据库时,term,value和document data会单独存储在专门为了查询而优化的不同的数据结构里.如下图显示:

这是搜索过程的一个相当简化的说明.但从根本上来说,查询就是对term和value的匹配.一个典型的查询可以是如下示例:
llamas和yaks
Xapian匹配器查找数据库中的词条,并收集一个含有这个词条的document的列表。如果查询只包括terms,那么这个列表就构成了一个搜索结果并作为一个MSet返回给客户端代码。
但是,如果查询包含range组件(如范围2009年1月1日- 2009年4月2日 ),那将会需要一个寻第二查询阶段。匹配器会检查候选MSet中每个document中相应的value,并与查询范围比较。如果它不属于查询范围,该document就被弹出结果集,否则该document就被返回到客户端。
最后,通过自定义一个MatchDecider类的子类,还可以定制Xapian的搜索过程.这些子类可以访问document的任何属性,但因为性能方面的原因,通常只使用value。比如说,一个典型的应用是,根据用户的权限来过滤搜索结果:匹配器会比较用户的权限等级和候选文档的ACL设置,并决定是否在查询结果中返回该文档.
搜索优化

虽然这个模型过于简单,它仍然可以帮助我们改进数据库设计和搜索规划。在一般情况下,在磁盘上,term和postion list比value要远大得多,因此基于term的搜索一般受限于IO.这就是为什么有足够的RAM来提供磁盘缓存(数据库大小的10%或以上)时,搜索性能可以大大提高的原因。
相比之下,value range的搜索和匹配器的计算更密集,往往是受CPU限制。在查询没有指定任何term以限制候选MSet的大小时,单儿的value搜索会一个个地碾过所有的文档集(documents),在大型数据库上这个会灰常灰常地慢.
如果需要range查询搜索,添加一个term条件以给数据库分区,可以大大减轻性能压力.例如,日期可能由一个合适的索引前缀+YYYYMM构成的term来描述。然后,一个对日期范围的搜索就可以由一个term搜索来完成,这能大大减少要处理的document数量.例如,,2009年1月16日- 2009年4月2日的日期范围搜索可以由:
(XD200901或XD200902或XD200903或XD200904)的term搜索来限定.
这包括1月到4月的数据,超过必要的数据了,但是随后的范围子搜索中我们会去掉多余的数据。这时这个term搜索会大大减少需要处理的文件的数量,进而提高搜索速度。
不过,必须选择适当级别的粒度.如果我们按单天产生term而不是按月,日期term将产生76个,在IO方面可能就会有性能风险。
所有这一切都是相当复杂的,这就是为什么我们添加到flax中来处理的原因。我们搞flax的目标是针对数据源输入的各式的数据文档,能自动地产生优化的规划.而用户不太需要去关注这个过程请继续关注这里的消息以获得更新的消息:http://www.flax.co.uk/

用PHP和xapian构建全文检索[转]

大约从07年起,本博客就不转载了;
这篇算是以译文发的,原文在:http://www.contentwithstyle.co.uk/content/searching-with-xapian-and-php
========邪恶的分割线============
有的时候呢,嗯 ,mysql 就是不够快;尤其是在做全文检索的时候.各个字段都得正确地检索才行,而当我们的各个字段带有不同的权重时,事情就马上变得特别复杂了,这时你就需要xapian来救急了.
Xapian是什么东东
xapian是一个全文检索库,就和lucene和sphinx一样;它需要从c++代码编译,比较底层;现在已经有直接可用的php,perl,python绑定可以用了.目前提供了redhat和ubuntu的包;你可以在Mac os上编译,还可以通过cygwin来在windows下运行.
示例脚本
我不想去解释why和how,我只想展示一个简单的脚本;我封装的php文件有点大,读者可以从下载;
db.sql

CREATE DATABASE `demo`;

CREATE TABLE `demo`.`demo` (
`id` INT( 10 ) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`unique_key` VARCHAR( 255 ) NOT NULL ,
`name` VARCHAR( 255 ) NULL DEFAULT NULL ,
`summary` TEXT NULL DEFAULT NULL ,
`date` DATETIME NULL DEFAULT NULL ,
UNIQUE (`unique_key`));

INSERT INTO `demo`.`demo`
(`id`, `unique_key`, `name`, `summary`, `date`)
VALUES (NULL, ‘foo’, ‘foo’, ‘foo bar test’, ‘2008-11-05 00:00:00’),
(NULL , ‘bar’, ‘bar’, ‘test foo bar’, ‘2009-11-05 00:00:00’);

XapianWrapper.php

xapian_read_db = new XapianDatabase(self::SETTINGS_XAPIAN_DB);
$this->xapian_stemmer = new XapianStem(“english”);
$this->xapian_enquire = new XapianEnquire($this->xapian_read_db);
} catch(Exception $e) {
throw new Exception(‘Could initialize Xapian: ‘ . $e->getMessage());
}
}

private function xapian_init_writable() {
try{
$this->xapian_write_db = new XapianWritableDatabase(self::SETTINGS_XAPIAN_DB, Xapian::DB_CREATE_OR_OPEN);
$this->xapian_indexer = new XapianTermGenerator();
$this->xapian_stemmer = new XapianStem(“english”);
$this->xapian_indexer->set_stemmer($this->xapian_stemmer);
} catch(Exception $e) {
throw new Exception(‘Could initialize Xapian: ‘ . $e->getMessage());
}
}

private function mysql_init() {
$this->mysql_link = mysql_connect(self::SETTINGS_MYSQL_HOST, self::SETTINGS_MYSQL_USER, self::SETTINGS_MYSQL_PASS);
if (!$this->mysql_link) {
throw new Exception(‘Could not connect: ‘ . mysql_error());
}

$db_selected = mysql_select_db(self::SETTINGS_MYSQL_DB, $this->mysql_link);
if (!$db_selected) {
throw new Exception(‘Can’t use db : ‘ . mysql_error());
}
}

/**
* Index method
*
*/
public function index($params) {
$this->xapian_init_writable();
$this->mysql_init();

$start = microtime(true);

$response = new stdClass();
$response->indexed = array();

$offset = (isset($params[‘offset’])) ? intval($params[‘offset’]) : 0;
$count = (isset($params[‘count’])) ? intval($params[‘count’]) : self::DEFAULT_COUNT;
$sql = ‘SELECT * FROM ‘.self::SETTINGS_MYSQL_TABLE.’ LIMIT ‘ . $offset . ‘, ‘ . $count . ‘;’;

$result = mysql_query($sql);

if (!$result) {
throw new Exception(‘Invalid query: ‘ . mysql_error());
}

$this->xapian_write_db->begin_transaction();

while ($row = mysql_fetch_array($result, MYSQL_ASSOC)) {
$response->indexed[] = $this->index_row($row);
}

$this->xapian_write_db->commit_transaction();
mysql_free_result($result);
mysql_close($this->mysql_link);

return $response;
}

private function index_row($row) {
$doc = new XapianDocument();

$this->xapian_indexer->set_document($doc);
$this->xapian_indexer->index_text($row[‘name’],50);
$this->xapian_indexer->index_text($row[‘summary’], 1);

$GUID = self::XAPIAN_PREFIX_UID . $row[‘unique_key’];
$doc->add_term($GUID);

$doc->add_value(self::XAPIAN_FIELD_URL, $row[‘url’]);
$doc->add_value(self::XAPIAN_FIELD_DATE, date(‘Ymd’, strtotime($row[‘date’])));
$doc->add_value(self::XAPIAN_FIELD_UID, $row[‘unique_key’]);
$doc->add_value(self::XAPIAN_FIELD_NAME, $row[‘name’]);
$doc->add_value(self::XAPIAN_FIELD_SUMMARY, $row[‘summary’]);

$this->xapian_write_db->replace_document(strval($GUID), $doc);

$row_response = array();
$row_response[‘name’] = $row[‘name’];
$row_response[‘guid’] = $row[‘unique_key’];
$row_response[‘url’] = $row[‘url’];
return $row_response;
}

/**
* Delete method
*
*/
public function delete($params) {
$this->xapian_init_writable();

$this->xapian_write_db->begin_transaction();

$response = array();

foreach($params[‘items’] as $param_guid) {
$GUID = self::XAPIAN_PREFIX_UID . $param_guid;
$this->xapian_write_db->delete_document(strval($GUID));
$response[] = $param_guid;
}

$this->xapian_write_db->commit_transaction();
return $response;
}

/**
* Search method
*
*/
public function search($params) {
$this->xapian_init_readonly();

$start = microtime(true);

// queries array to later construct full query
$arr_queries = array();

// from date
if(!empty($params[‘date_from’])) {
$arr_queries[] = new XapianQuery(XapianQuery::OP_VALUE_GE, 6, date(‘Ymd’, strtotime($params[‘date_from’])));
}

// to date
if(!empty($params[‘date_to’])) {
$arr_queries[] = new XapianQuery(XapianQuery::OP_VALUE_LE, 6, date(‘Ymd’, strtotime($params[‘date_to’])));
}

// unique key
if(!empty($params[‘unique_key’])) {
$arr_queries[] = new XapianQuery(self::XAPIAN_PREFIX_UID . $params[‘unique_key’]);
}

// normal search query parsed
if(!empty($params[‘search’])) {
$qp = new XapianQueryParser();
$qp->set_stemmer($this->xapian_stemmer);
$qp->set_database($this->xapian_read_db);
$qp->set_stemming_strategy(XapianQueryParser::STEM_SOME);
$arr_queries[] = $qp->parse_query($params[‘search’]);
}

// Find the results for the query.
// construct final query
$query = array_pop($arr_queries);

foreach($arr_queries as $sq) {
$query = new XapianQuery(XapianQuery::OP_AND, $query, $sq);
}
$this->xapian_enquire->set_query($query);

// set the count to the specified params
$offset = (isset($params[‘offset’])) ? intval($params[‘offset’]) : 0;
$count = (isset($params[‘count’])) ? intval($params[‘count’]) : self::DEFAULT_COUNT;
$matches = $this->xapian_enquire->get_mset($offset, $count);

$response = new stdClass();
$response->result_count = $matches->get_matches_estimated();
$results = array();

$i = $matches->begin();
while (!$i->equals($matches->end())) {
$m = array();

$n = $i->get_rank() + 1;
$doc = $i->get_document();

$m[‘position’] = $n;
$m[‘url’] = $doc->get_value(self::XAPIAN_FIELD_URL);
$m[‘name’] = $doc->get_value(self::XAPIAN_FIELD_NAME);
$m[‘summary’] = $doc->get_value(self::XAPIAN_FIELD_SUMMARY);
$m[‘date’] = $doc->get_value(self::XAPIAN_FIELD_DATE);
$m[‘unique_key’] = $doc->get_value(self::XAPIAN_FIELD_UID);
$m[‘percent’] = $i->get_percent();

$results[count($results)] = $m;
$i->next();
}

$response->results = $results;
$end = microtime(true);

// runtime info
$response->execute = new stdClass();
$response->execute->call = ‘search’;
$response->execute->offset = $offset;
$response->execute->count = $count;
$response->execute->start = $start;
$response->execute->end = $end;
$response->execute->time = $end – $start;

// debug stuff
$response->execute->debug = $query->get_description();

return $response;
}
}

index.php

index(array());
print_r($res);

Search.php

‘foo’);
$res = $x->search($params);
print_r($res);

delete.php

array(‘foo’),
);
$res = $x->delete($params);
print_r($res);


使用示例:
您下载刚才的源码包后,就可以导入db.sql,并在命令里运行程序;

bash$ php index.php
stdClass Object
(
[indexed] => Array
(
[0] => Array
(
[name] => foo
[guid] => foo
[url] =>
)

[1] => Array
(
[name] => bar
[guid] => bar
[url] =>
)

)

)
bash$ php search.php
stdClass Object
(
[result_count] => 2
[results] => Array
(
[0] => Array
(
[position] => 1
[url] =>
[name] => foo
[summary] => foo bar test
[date] => 20081105
[unique_key] => foo
[percent] => 100
)

[1] => Array
(
[position] => 2
[url] =>
[name] => bar
[summary] => test foo bar
[date] => 20091105
[unique_key] => bar
[percent] => 50
)

)

[execute] => stdClass Object
(
[call] => search
[offset] => 0
[count] => 10
[start] => 1256674866.79
[end] => 1256674866.79
[time] => 0.000944852828979
[debug] => Xapian::Query(Zfoo:(pos=1))
)

)
bash$ php delete.php
Array
(
[0] => foo
)
bash$ php search.php
stdClass Object
(
[result_count] => 1
[results] => Array
(
[0] => Array
(
[position] => 1
[url] =>
[name] => bar
[summary] => test foo bar
[date] => 20091105
[unique_key] => bar
[percent] => 100
)

)

[execute] => stdClass Object
(
[call] => search
[offset] => 0
[count] => 10
[start] => 1256674876.02
[end] => 1256674876.02
[time] => 0.000872850418091
[debug] => Xapian::Query(Zfoo:(pos=1))
)

)

接下来,扩展您自己的程序来满足您的各种需求吧,欢迎反馈.好好地享受检索的乐趣吧.

tokyo cabinet的替代产品:kyoto cabinet

说起tokyocabinet,大家应该都知道,知名的key-value对数据库,memcache的替代产品,tokyotyrant和flare都是用的tokyocabinet来做的底层存储,嗯;tokyocabinet是作者对他之前的qdbm的一个升华.

作者果然”很猛很持久”,最近新推出了kyoto cabinet,是用C++写的,嗯,作者终于学会C++了(嗯,我是调侃一下,作者之前的所有作品都是用纯C写的,我很欣赏;现在用C++,可不是个好兆头!);

之前的tokyo cabinet意思好像是”东京柜子”,不知道为什么作者这么怀念东京的小柜子呢…刚出炉的这个kyoto cabinet,呃,名字似乎就是京都小柜子….

来自评论采集机器人:tokyo cabinet的意思是,东京内阁,kyoto cabinet 的意思是,京都内阁, 而tokyo tyrant的意思是…..东京暴君;

长话短说,拿kyoto cabinet和tokyo cabinet比较一下:(来源:作者pdf说明文档)

空间效率更高(数据库尺寸更小)

并发性能:在 多线程情况下性能更好(使用了CAS等原子操作)

可移植性:不再需要posix的依赖(也就是说,windows上应该也能跑了)

可用性:面向对象的编程(当然,用上C++了嘛)

鲁棒性(通俗一点:健壮性):自动的事务和回滚

另外:

kyoto cabinet 依赖于现代的C++实现,并且在线程实现多了锁的开销.

项目地址:http://www.1978th.net/kyotocabinet/

XHProf中文文档

XHProf是facebook在用的php性能分析工具,跟Xdebug相比,性能开销更低,可以用在生产环境。
原文地址在:
http://mirror.facebook.net/facebook/xhprof/doc.html
半成品:XHProf中文文档
约在本周内完成。
[addons at 2009-08-14:目前已经推进到了75%。欢迎斧正。]

测试FusionIO:strict_sync太秀逗了…

这是一篇译文,原文地址是:   http://www.mysqlperformanceblog.com/2009/06/15/testing-fusionio-strict_sync-is-too-strict/

随着新的更新FusionIO驱动的更新,我能够在我的戴尔R900与Ubuntu 8.10上进行尝鲜,而不再需要痛苦地编译驱动或是降级kernel.现在我决定测试一下strict_sync模式。

据我所知FusionIO默认情况下,比如英特尔的SSD ,是对应用程序“撒谎”了,fsync()并不是真正的发生了,它仍然只是提交到了内存而不是最终的磁盘。FusionIO还有一个“ strict_sync ”模式,可以保证fsync函数可靠地操作。

好啊兄弟,让我们来压一下性能—通常我在100w数据(约9GB数据),O_DIRECT模式下开着3GB的buffer_pool来搞这个测试。
原始测试数量参见这里
pub
结果用TPM (每分钟的事务数)表示 ,这样更清楚一些。图形显示,结果是是随着时间[X轴]不断变化 。
这个图形已经很明显,我都不需要再用文字来说明了。。。

但是我没有测试,在断电的情况下,在默认模式下处理、结束事务的情况。这个需要读者您检查一下。