如何针对倒排索引和关系数据库优化“文本搜索”?

时间:2012-05-30 16:07:33

标签: architecture search-engine inverted-index text-search

更新2015-10-15

早在2012年,我正在构建一个个人在线应用程序,并且实际上想要重新发明轮子,因为我天生好奇,出于学习目的并提高我的算法和架构技能。我可以使用apache lucene和其他人,但正如我所提到的,我决定建立自己的迷你搜索引擎。

问题:除了使用elasticsearch,lucene等可用服务外,真的没有办法增强这种架构吗?


原始问题

我正在开发一个Web应用程序,用户在其中搜索特定的标题(例如:book x,book y等),这些数据位于关系数据库(MySQL)中。

我遵循以下原则:从db中获取的每条记录都缓存在内存中,以便应用程序对数据库的调用较少。

我开发了自己的迷你搜索引擎,具有以下架构:
Architecture diagram
这是它的工作原理:

  • a)用户搜索记录名称
  • b)系统检查查询的起始字符,检查是否在那里查询:获取记录。如果没有,请添加它并使用两种方式从数据库中获取所有匹配的记录:
    • 表中的“查询”(这是一种历史表)中已有查询,因此根据ID获取记录(快速性能)
    • 或者,否则使用 Mysql LIKE %% statement 来获取记录/ ids(然后用户在历史表中查询查询以及它映射到的ID)。
      - >然后它将记录及其ID添加到缓存,只将id添加到反向索引图。
  • c)结果返回到UI

系统工作正常,但我有两个主要问题,我找不到一个好的解决方案(过去一个月一直在尝试):

第一期:
如果你检查点(b),没有找到查询“历史”并且必须使用 Like %% 语句的情况:当查询时,此过程变为时间匹配数据库中的大量记录(而不是一个或两个):

  • 从Mysql获取记录需要一些时间(这就是我在特定列上使用INDEXES的原因)
  • 然后是时间保存查询记录
  • 然后是时候将记录/ ID添加到缓存和反向索引映射

第二期:
该应用程序允许用户添加自己的新记录,立即由登录到应用程序的其他用户使用。
然而,为了实现这一点,必须更新反向索引映射和表“查询”,以便在任何旧查询与新单词匹配的情况下。例如,如果添加了 new 记录“woodX”,旧的查询“wood”仍会映射到它。因此,为了重新勾选查询“wood”到这个新记录,这就是我现在正在做的事情:

  • 新记录“woodX”被添加到“记录”表
  • 然后我运行一个 Like %% 语句来查看表“查询”中哪个已存在的查询映射到此记录(例如“wood”),然后使用新记录ID作为新行添加此查询:[wood,new id]。
  • 然后在内存中,通过将新记录ID添加到此列表中来更新反向索引Map的“wood”键值(即列表)

- >因此,现在如果远程用户搜索“wood”,它将从记忆:wood和woodX

此处的问题也是时间消耗。将所有查询历史(在表查询中)与新添加的单词匹配需要花费大量时间(匹配查询越多,时间越多)。然后内存更新也需要很多时间。

做出修复此时间问题的方法是将所需结果返回给用户首先,然后让应用程序POST ajax 使用所需数据调用以实现所有这些UPDATE任务。但我不确定这是一种不良做法还是一种不专业的做事方式?
所以在过去的一个月(多一点)我试着想到这个架构的最佳优化/修改/更新,但我不是文档检索领域的专家(实际上它是我的第一个迷你搜索引擎)。

如果能够实现这种架构我应该做些什么的反馈或指导,我将不胜感激 提前谢谢。

PS:

  • 它是一个使用servlet的j2ee应用程序。
  • 我正在使用MySQL innodb(因此我无法使用全文搜索选项)

4 个答案:

答案 0 :(得分:2)

我不假装有解决方案,但这是我的想法。 首先,我喜欢你喜欢耗时的查询LIKE %%:我会在MySQL中执行一个限于几个答案的查询,比如十几个,将其返回给用户,然后等待用户想要更多匹配的记录,或者启动在后台进行全查询,具体取决于您对未来搜索的索引需求。

更一般地说,我认为将内存中的所有内容存储在某一天可能导致内存消耗过多。当搜索引擎将所有内容保存在内存中时,搜索引擎变得越来越快,在添加或更新数据时,您必须使所有这些缓存保持最新,并且肯定会花费越来越多的时间。 / p>

这就是为什么我认为我在"开源论坛软件中看到的解决方案的原因" (我无法记住它的名字)对于帖子中的文本搜索来说并不算太糟糕:每次插入数据时,都会有一个名为" Words"记录每个现有单词和另一个表格(让我们说" WordsLinks")每个单词和它出现的帖子之间的链接。 这种解决方案有一些缺点:

  • 数据库中的每个Insert,Delete,Update都慢得多
  • 必须预料到搜索引擎的数据选择:如果您选择保留两个字母单词而不保留,则对已记录的数据来说为时已晚,除非您启动完整的数据重新处理。
  • 您必须处理DELETE以及UPDATE和INSERT

但我认为有一些很大的优势:

  • 计算时间可能与"内存解决方案相同" (最终),但它在每个数据库中分为创建/更新/删除,而不是在查询时。
  • 寻找整个单词或单词"以"开头是瞬时的:当索引时,搜索"单词"表是二分法的。和#34; WordLinks"使用索引,表查询速度非常快。
  • 同时寻找多个单词可能很简单:收集一组" WordLinks"对于每个找到的Word,并在它们上执行交集以仅保留"数据库ID"所有这些群体都很常见。例如,使用" tree"和" leaf",第一个可以给表记录{1,4,6},第二个可以给{1,3,6,9}。因此,通过交叉点,仅保留公共部分很简单:{1,6}。
  • A"喜欢%%"在单列表中可能比很多"像%%"在不同表的不同领域。并且每个数据库引擎都处理一些缓存:" Words"表可能足够小,可以留在记忆中
  • 如果数据变得庞大,我认为存在性能和内存问题的小风险。
  • 由于每次搜索都很快,您甚至可以查找同义词。例如搜索" network"如果用户没有找到任何" ethernet"。
  • 您可以应用规则,例如拆分驼峰案例单词以生成例如3个单词" wood"," X"," woodX"来自" woodX"。每个"字"存储和查找非常轻量级,因此您可以做很多事情。

我认为您需要的解决方案可以是方法的混合:例如,您可以保持轻量级UPDATE,INSERT,DELETE和启动" Words"和#34; WordsLinks"从TRIGGER喂食。

仅仅因为轶事,我看到了我公司开发的软件,其中决定保留"一切" (!) 在记忆中。它引导我们向客户推荐购买64GB RAM的服务器。有点贵。它解释了为什么当我看到可能最终导致记忆填充的解决方案时,我非常谨慎。

答案 1 :(得分:2)

我不得不说,我不认为你的设计很适合这个问题。你现在看到的问题是这样的结果。除此之外,您当前的解决方案无法扩展。

这是一个可能的解决方案:

  1. 将数据库重新设计为仅包含权威数据,但不包含派生数据。因此,所有缓存条目必须从MySQL中消失。

  2. 仅在应用程序内存中的请求期间保留数据。这使您的应用程序的设计更加简单(考虑竞争条件),并使您能够扩展到合理数量的客户端。

  3. 引入缓存层。我强烈建议使用既定产品,而不是自己构建。这使您可以在应用程序中释放所有自定义构建的缓存逻辑,甚至可以更好地完成工作。

  4. 您可以查看Redis或Memcached以获取缓存层。我认为LRU策略应该适合这里。根据查询的复杂程度,像Lucene这样的专用索引搜索机制也可能有意义。

答案 2 :(得分:2)

我强烈推荐Sphinx Search Server,最好在全文搜索中进行优化。访问http://sphinxsearch.com/

它设计用于MySQL,因此它是您当前工作区的补充。

答案 3 :(得分:1)

我确信这可以在MySQL中实现,但是使用现有的面向搜索的数据库(例如Elasticsearch)要少得多。它使用Lucene库来实现反向索引,具有丰富的文档,支持横向缩放,相当简单的查询语言等等。我想要做到这一点已经做了相当多的工作,处理缓存,竞争条件,错误,性能问题等以使解决方案“生产等级”将会更加成功。