注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

Koala++'s blog

计算广告学 RTB

 
 
 

日志

 
 

企业级搜索引擎Solr 第二章 Schema和文本分析(Schema and Text Analysis)[1]  

2012-08-10 20:17:59|  分类: 搜索引擎 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

Schema and Text Analysis

Author: David Smiley Eric Pugh

译者:Koala++ / 屈伟

         本章的主题是Solr的索引基础知识。你会学习到下面的内容:

l  Schema设计,它会决定如何将你的源数据映射到Lucene有限的结构中去。本书中我们将使用来自MusicBrainz.org的数据。

l  Schema的定义在schema.xml配置文件中,它定义了域的类型和数据的保存方式。

l  文本分析,配置要索引的文本应该如何处理的(比如分词)。这个配置会影响一个查询是否会匹配一个文档。

下图展示了Solr相关的功能。这一章我们将关注于最基础的一层——索引。

企业级搜索引擎Solr 第二章 Schema和文本分析(Schema and Text Analysis)[1] - quweiprotoss - Koala++s blog

 

MusicBrainz.org

         我们将使用来自http://musicbrainz.org的音乐数据库中的元数据。这些数据是由大量的社区用户提交的。MusicBrainz提供这些数据的其中这一种方式是提供一个大的SQL文件,你可以用这个SQL文件将数据导入PostgreSQL数据库。为了让你更容易地用这份数据,本书的资料中提供可以直接供Solr使用的数据。但如果你已经有自己的数据了,我建议你就使用自己的数据,将本书中的内容作为参考。

         MusicBrainz.org数据库有着复杂的关联关系,所以这是一个讨论Solr Schema选择的极好选择。MusicBrainz数据库Schema很复杂,就算只去分析它一半内容都会浪费我们很多的精力,使我们分心。我这里只使用数据的一个子集,这一子集与MusicBrainz网页的用户接口有着明显的对应关系。下图的每个表都可以用SQL子查询或是视图从真实的MusicBrainz表中得到。

企业级搜索引擎Solr 第二章 Schema和文本分析(Schema and Text Analysis)[1] - quweiprotoss - Koala++s blog

 

         我用我最喜欢的乐队Smashing Pumpkins来解释上面的表。

l  Smashing Pumpins是一个artist(艺术家),它的类型值是“group”(乐队)。有些artists(特别是乐队)是有几个artists(艺术家)成员的,而这些成员也是artist,他们的类型值是“person”。所以它是一个自关联的关系。Smashing PumpkinsBilly CorganJimmy Chamberline和其它成员。

l  Artistrelease(发行版)的作者。大多数release是一个“专辑”(album),但也有一些是单曲,EPs(短专辑),和精选等等。另外,release还有一个“status”属性它的值可以是official(官方),promotional(促销),或是bootleg(盗版)。Smashing Pumpkins一张流行的官方出版的专辑是“Siamese Dream”。

l  Release可以在多个时间不同地区发布,MusicBrainz称其为“event”(一个Release-event)。每个event包含时间,国家,唱片公司,和格式(CD或是磁带)。

l  一个Release是由一个或多个Tracks(音轨)组成的。Siamese Dream13 Tracks,以“Cherub Rock”开始,以“Luna”结束。注意一个Track与歌(Song)不是同义词。比如“Cherub Rock”它不仅是“Siamese Dream”的Track,而且是“Greatest Hits”中的Track。一个Track有一个PUIDPotableUniqueIdentifier,它是一个音频指纹技术产生的一个ID。另一个MusicBrainz有意思的数据是它保存PUID的查看次数(lookup count)。

我在讲数据建模的时候有时会使用“实体”这个词,它就是一个真实的事物概念,比如:ArtistReleaseEvent,和Track都是实体类型。在关系数据库中,大部分表都是实体类型。在Solr中,每个文档都有一个主要的实体,文档也可能有其它的实体。

One combined index or separate indices

         下面主要介绍如何支持搜索多种不同类型的数据,比如搜索MusicBrainz中的ArtistsRelease。在示例MusicBrainz的配置中,每种类型中的每个文档都有自己的索引,但它们共享相同的配置。虽然我通常不推荐这种方式,但这种方式很方便,并能减少本书的复杂性。

One combined index

         结合索引(combined index)也通常被称为聚合索引(aggregate index)。一个索引概念上像是一个单表数据库,所以与一些NoSQL数据库有着些类似性。尽管结合索引有它的局限性,但是也没什么能阻止你把数据中所有的信息(比如,ArtistRelease)放到一个索引中去。你需要做的事就是将不同表中的每个字段用一个域来表示。每个域的名字必须是唯一的,这很容易做到,你把表名当前缀加到字段名前面,就成为唯一的域名了。这也许从关系数据库设计的角度来看是丑陋的,但是这不是数据库!

         下面是结合索引schema.xml配置的一部分:

<field name="id" ... /><!-- example: "artist:534445" -->

<field name="type" ... /><!-- example: "artist", "track", "release",

... -->

<field name="name" ... /><!-- (common to various types) -->

<!-- track fields: -->

<field name="PUID" ... />

<field name="num" ... /><!-- i.e. the track # on the release -->

<!-- ... -->

<!-- artist fields: -->

<field name="startDate" ... /><!-- date of first release -->

<field name="endDate" ... /><!-- date of last release -->

<field name="homeCountry" ... />

<!-- etc. -->

         在本书中,我们将采用一种混合的方式,即我们对每种数据类型使用分离的Solr索引,但是索引使用同一份配置,包括Schema

Problems with using a single combined index

         尽管使用结合索引很容易开始,但是使用结合索引有下面一些问题:

l  域的名字可能会冲突,除非你在域名前都加上表名:比如artist_startDatetrack_PUID。在例子的数据中我们看到大部分实体都有一个字段名是name。一个直观的想法是它们都放到一个域中。如果是如果域的类型是不同的,那么你就必须将它们命名为不同的域名。

l  如果你共享不同实体中的相同字段,比如name,你在一个查询中查找这个字段的内容时会出现一些问题。

?  你得到的得分会因为受文档频率(document frequence的影响,那以得分可能不那么准确,因为你本来是想找某张表中的一个字段的某个值,现在你是把多张表中的相同字段名的值放到了一张表中去,那么文档频率就会低很多。

?  前缀,通配符,和模糊查询会耗更长的时间,并且可能会达到内部查询扩展的极限。如果你共享多个表中的一个字段,会使这个域中词的数量增大,也就使得支持这三种查询的时间变长,并且内部产生的查询可能超过maxBooleanClauses值(在solrconfig.xml)中配置。

l  得分的准确会变差,因为总的文档数变大,所以会影响IDF的值。

l  对于大规模的文档集,使用多个索引被证明是更具有可扩展性的。只有实验才能证明对于你的文档和你的查询来说,你的文档集是否很大,但是如果你的文档少于100万,使用多个索引不太可能有好处。一个索引中1000万个文档一般是合理的值。在MusicBrainz中有700万个Track,所以我们当然要把它放到它自己的索引中了。

Separate Indices

         对于分离索引(separate indices),你只需要独立设计你的Schema就可以了。你可以把有的Schema写到一个配置文件中,这样你不需要管理多个配置文件,本书就采用这种方式。我们后面的介绍都假设Schema都是相互独立的。

         如果你在设计一个分离Schema,并且你需要在一次搜索中查找多个索引,那么你必须进行分布式搜索(distributed search,相关内容将在最后一章介绍。通常大规模数据才会引入分布式搜索。你要使用之前请注意去阅读最后一章,因为它有一些局限。和在结合索引中一样,文档都要有一个唯一ID,并且你还需要一个“type”域来区分你搜索返回结果中的文档。通常你不需要去指定搜索哪个域,因为在每个索引配置中都配置了到哪个域去查询,比如,配置默认查找的域是什么。

Schema Design

         你要认识到的是你的Schema设计是由你要支持的查询所驱动的。与之相反的是,关系数据库设计时是遵循三范式的。因为查询驱动着Schema设计,所以准则是所有需要匹配的文档的数据,必须要在这个文档中,而不是一个相关联的文档。为了满足这个需求,数据如果不在这个文档中,而在相关联的文档中,则要将相关联的文档数据拷贝过去。比如如果要在MusicBrainz中支持通过Artist名字搜索Track,但是Artist的名字只在Artist文档中,而不在Track中,所以需要将Artist的名字拷贝到Artist文档中去。

         我将给出设计Schema的几个步骤,通过这几个步骤,你可以设计各种Solr应用。我们将用MusicBrainz.org网站为例,并假想它的工作方式。我们设计出的Schema暂不考虑文本分析和一些搜索特性,比如FacetingSchema设计是需要思考的,并且可能有迭代设计。所以不要将下面的步骤看成是操作手册,它是一个步骤指导。

Step 1: Determine which searches are going to be powered by Solr

         首先你要确定的是你所需要Solr提供的搜索功能是什么。在MusicBrainz的页面上,你可以通过导航中的search进入搜索页面,进入后,你会发现下面还有一个高级的搜索功能,它有多个选项,我们可以从下面的图看到它的搜索表单。

企业级搜索引擎Solr 第二章 Schema和文本分析(Schema and Text Analysis)[1] - quweiprotoss - Koala++s blog

 

Step 2: Determine the entities returned from each search

         确定你每次搜索需要返回的实体是什么。从MusicBrainz上面的表单上看:要返回的实体分别是:ArtistsReleasesTracksLabelsEditors。虽然MusicBrainz是这样做的,但你不一定非要如此。注意每次搜索返回的内容都对应Solr索引中的文档,所以每个实体要与返回文档对应。实体就对应数据库表中的一行记录。

Step 3: Denormalize related data

         对于每种实体,找出所有搜索在Schema中需要的所有数据,“所有搜索”,我是指在有多种搜索表单的情况下,就像在Step 1 见到的一样。所有数据包括任何要被搜索的数据和任何需要在搜索结果中出现的数据。Denormalization的结果就是每个文档都是不需要Join操作就可以作为最终的返回结果的,这可能会在索引中产生一些冗余数据。但这是因为Solr不支持Join操作(译注:Solr4已经出来了)。

         让我们看一下例子。考虑搜索 Cherub RockTracks结果。

Denormalizing—'one-to-one' associated data

         Track的名字和时间长度是在Track表中,但是ArtistAlbum名字是在他们自己的表中。这其实是最简单的情况,因为每个Track都只有一个Artist,并每个Track只属于一张Album。在Solr Track Schema设计中,需要将Artist名字和Album名字加入其中。在我们其它的Solr Schema设计中,也需要这样。因为ArtistAlbum的名字可能会重名,所以需要将ArtistAlbum的表的ID加入到Schema,这样可以在页面上支持ArtistAlbum链接。

Denormalizing—'one-to-many' associated data

         在一个字段是多值这种情况时,一对多关联很容易处理。在Solr中支持域有多个值。在MusicBrainz中,一个类型是Group(乐队)的Artist可以有多个Artist成员。尽管MusicBrainz当前的搜索引擎还不支持这个功能,但我们将研究如何实现它。Solr Schema仅需要有一个乐队成员名字域,并将其它设置成多值类型。只有member_id域是不够的,因为denormalization需要将乐队成员的名字拷贝到Artist中。这个例子展示了在需要变得复杂时如何去做。

         如果我们只是保存乐队成员的名字,那么我们想在页面上有一个通过乐队成员链接到这个成员详细介绍的页面就困难了。是这因为我们只有每个成员的名字,而没有他们的ID。所以我们要有一个多值字段来保存成员的ID。这两个字段的顺序要保持一致,即成员的名字和他的ID要在两个字段中一一对应。注意,有可能成员的名字或是ID为空这种情况,那么你要使用占位符,并且客户端代码要能识别这个占位符。

         在要引入其它实体的多值域,并需要搜索多个这种字段时,denormalizing一对多数据就会有问题。这里假想一个问题,假设要在Release中搜索包括某个词的Track并且这个Track播放时间长度要大于某一值。ReleaseTrack名字和播放时间长度域都是多值。不幸的是,Solr会返回错误的Release。它返回的Release是满足一半条件的Release,或是满足名字匹配,或是满足播放长度,但不一定是两个都满足。一个临时解决方法是不搜索Release而搜索Track索引,再用Solr新的Result Grouping特征对Release进行分组合并(group by)。这种解决方法依赖另一个索引来保存实体关系。如果你正面临这种挑战,但为如果要创建再多一个索引,对你的数据来说要消耗大量的空间,那么你可以等到Solr 4提供Join支持。(译注:你真的等到了)

Step 4: (Optional) Omit the inclusion of fields only used in search results

         去除那些仅仅用于返回的域。你也许不需要做这一步,但是理解这一概念是很重要的。如果在搜索结果中有的域不是用于查询,不用于排序,不用于Facet,也不用高亮标记,也不用于其它Solr特性,只是作为搜索的返回内容,那么在Schema中包含这个域是没有意义的。在进行查询Tracks时,唯一用于查询,排序等等的域是Track的名字。你可以选择不将Artist的名字等等放入Track实体中。当你的应用查询Track时,需要搜索结果得到Artist的名字,你的应用可以从别的地方得到数据,而不是从Solr中取得。应用可以从数据库,或是Cache中间件,甚至Solr Artist索引中取得。

         显然上面的做法让产生一个搜索结果更复杂了,因为你要从不止一个地方取数据。并且,如果你需要通过返回整个文档的方式从数据源取数据,而不是一列一列地取数据。另外,也许你应该考虑通过Cache策略来减少对数据源的压力。这样做很可能会增加搜索处理时间。但是好处是你不需要再得到这些数据并将它们建索引。你需要保存的字段也许很多,这很占用大量的索引空间,并且如果这些域如果经常改变,会导致索引频繁的更新。

         如果你在使用分布式搜索,这时候返回的数据量多少就会变得更加重要了。假设你有每个Track的歌词数据,它们分布在20台机器上。如果你要取100个结果,那么20台机器都会返回100个结果,那么就是2000个结果。如果仅是返回ID,那就会减少大量网络开销,但是你就要从其它地方读取这些数据了。你想知道哪一种方式更适合你,你只能测试这两种场景看哪种更适合你了。通常,如果数据真的不是很大,那你还是将它保存在Solr中吧。

         我们考虑另一个极端,为什么不将所有的数据全都保存到Solr中?至少在MusicBrainz这个例子中,这是不合适的。以Top Voters(贡献最多的人)统计为例。在MusicBrainz的语境下,这些用户都是真正的编辑者。Top Voters是将一个编辑者所进行的编辑次数统计再取Top得到的。在这个例子中,编辑是实体,下面的截图展示了Top Voter,每行前面是用户名,后面是编辑次数:

企业级搜索引擎Solr 第二章 Schema和文本分析(Schema and Text Analysis)[1] - quweiprotoss - Koala++s blog

 

         这个数据显然不应该在索引中,因为搜索编辑者是没有意义的,它只在我们在查看其它实体比如一个Artist时才有用。如果你坚持将所有的编辑数据(它们非常多)放到索引中,仅为了在列出Top Voters时进行简单的统计。这真是不值得的。

  评论这张
 
阅读(3395)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017