【问题标题】:Why is Solr so much faster than Postgres?为什么 Solr 比 Postgres 快这么多?
【发布时间】:2012-04-07 08:40:25
【问题描述】:

我最近从 Postgres 切换到 Solr,发现我们的查询速度提高了约 50 倍。我们运行的查询涉及多个范围,我们的数据是车辆列表。例如:“查找所有里程

我在 Postgres 的所有相关列上都创建了索引,所以这应该是一个相当公平的比较。查看 Postgres 中的查询计划,尽管它仍然只使用单个索引然后扫描(我假设是因为它无法使用所有不同的索引)。

据我了解,Postgres 和 Solr 使用大致相似的数据结构(B 树),并且它们都将数据缓存在内存中。所以我想知道这么大的性能差异来自哪里。

架构上的哪些差异可以解释这一点?

【问题讨论】:

  • 你是使用 Postgres 全文搜索还是简单的LIKE 查询?
  • 关系模型从来没有打算在像give me all the people who were born on a wednesday and owned a red car between 2003 and 2005这样的丑陋查询上表现良好。有搜索引擎(例如 Lucene)。索引有时会有所帮助,内存设置总是有帮助。
  • 我对你的问题有点困惑,所以我在这里问:dba.stackexchange.com/questions/34014/…即使不涉及全文搜索,Solr/Lucene 搜索是否会比 PostgreSQL 更快?
  • 如果有一个加载的问题 - 就是这样。我敢打赌你甚至没有考虑过使用任何部分索引。
  • 那是什么版本的 PostgreSQL?如果所有列都被索引,我很惊讶它没有使用位图索引扫描,除非您使用的是非常旧的 PostgreSQL 版本或者您的统计信息不是最新的。当它使用这样的计划时,它会扫描多个索引以查找值的范围,构建行位置的位图,然后将布尔逻辑应用于位图并按位置顺序仅检索匹配的行(以优化磁盘访问)。

标签: performance postgresql solr lucene rdbms


【解决方案1】:

首先,Solr 不使用 B 树。 Lucene(Solr 使用的底层库)索引由只读的segments 组成。对于每个段,Lucene 维护一个术语字典,它由出现在该段中的术语列表组成,按字典顺序排序。在此术语词典中查找术语是使用二分搜索进行的,因此单个术语查找的成本为O(log(t)),其中 t 是术语的数量。相反,使用标准 RDBMS 的索引成本为O(log(d)),其中 d 是文档数。当许多文档在某个字段上共享相同的值时,这可能是一个巨大的胜利。

此外,Lucene 提交者 Uwe Schindler 几年前增加了对高性能 numeric range queries 的支持。对于numeric field 的每个值,Lucene 会存储几个具有不同精度的值。这允许 Lucene 非常有效地运行范围查询。由于您的用例似乎大量利用数字范围查询,这可以解释为什么 Solr 如此之快。 (有关更多信息,请阅读非常有趣的 javadocs 并提供相关研究论文的链接。)

但 Solr 只能做到这一点,因为它没有 RDBMS 所具有的所有约束。例如,Solr 不擅长一次更新单个文档(它更喜欢批量更新)。

【讨论】:

  • 很好的答案(第一段)+1。
  • 虽然在这里我要说一件事。当你说“Lucene 维护一个术语字典,它由出现在段中的术语列表组成,按字典顺序排列。”。因此,如果术语按字典顺序排序并且需要 o(log t) 时间(二进制搜索),这意味着术语存储在数组中吗?这是正确的(我的意思是你拒绝了一个平衡的树结构来存储索引和哈希表不能存储直接排序的索引,所以我们只剩下一个二维数组(用术语,指向发布列表的指针作为它的元素)结构,在这个结构上进行二进制搜索进行中)
  • 这里要补充的另一件事是,即使 Lucene 索引不是 BTree,它也是一个倒排索引(就像大多数搜索引擎一样)。虽然你的回答对我来说是新事物。正如我所期望的平衡树结构来存储术语(在这种情况下,搜索也将是 log (t),并且每个节点还将包含指向发布列表的指针。使用平衡树,我们甚至可以维护按字典顺序排序的术语。跨度>
  • 不需要平衡树,因为数据永远不需要更新。将数据添加到 Lucene 索引时,会创建一个新段。此段优先于之前的段。当段太多时,MergeScheduler 会根据 MergePolicy(这些是 Lucene 中的类的名称)选择要合并的段。
  • 当一个新的 SegmentReader(一个在单个段上的 IndexReader)打开时,它默认加载内存中 Java 数组中索引的每 (n*16) 项(16 是 @987654326 @)。然后通过在内存中对该数组进行二进制搜索来执行查找,然后在磁盘上进行一次磁盘搜索和最多 15 个术语比较。所以总成本是O(log(t/16)) + O(1) + O(15) = O(log(t))
【解决方案2】:

您并没有真正说明您为调整 PostgreSQL 实例或查询所做的工作。通过调整和/或以更优化的格式重述您的查询,可以看到 PostgreSQL 查询的 50 倍速度提升并不罕见。

就在本周,有人使用 Java 和多个查询编写了一份工作报告,根据该报告在四个小时内所取得的进展,大约需要一个月才能完成。 (它需要访问五个不同的表,每个表都有数亿行。)我使用几个 CTE 和一个窗口函数重写了它,使它在不到十分钟的时间内运行并直接从查询中生成所需的结果。速度提高了 4400 倍。

也许您的问题的最佳答案与如何在每个产品中执行搜索的技术细节无关,而更多地与易用性有关对于您的特定用例。显然,您能够找到使用 Solr 进行搜索的快速方法,而且比 PostgreSQL 更轻松,而且可能仅此而已。

我提供了一个简短的示例,说明如何在 PostgreSQL 中完成对多个条件的文本搜索,以及一些小的调整如何产生很大的性能差异。为了保持快速和简单,我只是将 War and Peace 以文本形式运行到一个测试数据库中,每个“文档”都是一个文本行。如果必须松散定义数据,则可以将类似的技术用于使用hstore 类型或JSON 列的任意字段。如果有单独的列有自己的索引,使用索引的好处往往要大得多。

-- Create the table.
-- In reality, I would probably make tsv NOT NULL,
-- but I'm keeping the example simple...
CREATE TABLE war_and_peace
  (
    lineno serial PRIMARY KEY,
    linetext text NOT NULL,
    tsv tsvector
  );

-- Load from downloaded data into database.
COPY war_and_peace (linetext)
  FROM '/home/kgrittn/Downloads/war-and-peace.txt';

-- "Digest" data to lexemes.
UPDATE war_and_peace
  SET tsv = to_tsvector('english', linetext);

-- Index the lexemes using GiST.
-- To use GIN just replace "gist" below with "gin".
CREATE INDEX war_and_peace_tsv
  ON war_and_peace
  USING gist (tsv);

-- Make sure the database has statistics.
VACUUM ANALYZE war_and_peace;

设置好索引后,我会展示一些搜索,其中包含行数和两种类型索引的计时:

-- Find lines with "gentlemen".
EXPLAIN ANALYZE
SELECT * FROM war_and_peace
  WHERE tsv @@ to_tsquery('english', 'gentlemen');

84 行,要点:2.006 毫秒,杜松子酒:0.194 毫秒

-- Find lines with "ladies".
EXPLAIN ANALYZE
SELECT * FROM war_and_peace
  WHERE tsv @@ to_tsquery('english', 'ladies');

184 行,要点:3.549 毫秒,杜松子酒:0.328 毫秒

-- Find lines with "ladies" and "gentlemen".
EXPLAIN ANALYZE
SELECT * FROM war_and_peace
  WHERE tsv @@ to_tsquery('english', 'ladies & gentlemen');

1 行,要点:0.971 毫秒,杜松子酒:0.104 毫秒

现在,由于 GIN 索引比 GiST 索引快大约 10 倍,您可能想知道为什么有人会使用 GiST 来索引文本数据。答案是 GiST 通常维护起来更快。因此,如果您的文本数据高度不稳定,那么 GiST 索引可能会在整体负载上胜出,而如果您只对搜索时间或以读取为主的工作负载感兴趣,则 GIN 索引会胜出。

如果没有索引,上述查询需要 17.943 毫秒到 23.397 毫秒,因为它们必须扫描整个表并检查每一行是否匹配。

对同时包含“女士”和“绅士”的行进行 GIN 索引搜索比在完全相同的数据库中进行表扫描快 172 倍以上。显然,与本次测试相比,使用更大的文档进行索引的好处会更加显着。

当然,设置是一次性的。使用触发器来维护tsv 列,所做的任何更改都可以立即进行搜索,而无需重新进行任何设置。

对于慢速 PostgreSQL 查询,如果您显示表结构(包括索引)、问题查询以及运行 EXPLAIN ANALYZE 查询的输出,几乎总能有人发现问题并建议如何解决跑得更快。


更新(2016 年 12 月 9 日)

我没有提到我以前获得的时间,但根据日期可能是 9.2 主要版本。我刚刚遇到了这个旧线程,并使用版本 9.6.1 在相同的硬件上再次尝试,看看是否有任何干预性能调整有助于这个示例。仅对一个参数的查询只提高了大约 2% 的性能,但是使用“女士”“绅士”的行搜索速度大约翻了一番,达到 0.053 毫秒(即 53 微秒) GIN(倒排)索引。

【讨论】:

【解决方案3】:

Solr 主要用于搜索数据,而不是用于存储。这使它能够放弃 RDMS 所需的大部分功能。所以它(或者更确切地说是lucene)专注于纯粹的索引数据。

您无疑已经发现,Solr 能够从其索引中搜索和检索数据。正是后者(可选)功能导致了一个自然的问题……“我可以将 Solr 用作数据库吗?”

答案是肯定的,我建议您参考以下内容:

我个人的看法是,最好将 Solr 视为我的应用程序和数据库中掌握的数据之间的可搜索缓存。这样我就可以两全其美了。

【讨论】:

    【解决方案4】:

    最大的不同是 Lucene/Solr 索引就像一个单表数据库,不支持任何关系查询 (JOIN)。请记住,索引通常仅用于支持搜索,而不是数据的主要来源。因此,您的数据库可能处于“第三范式”,但索引将完全被反规范化,并且主要包含需要搜索的数据。

    另一个可能的原因是数据库通常存在内部碎片,它们需要对巨大的请求执行过多的半随机 I/O 任务。

    这意味着,例如,考虑到数据库的索引架构,查询会导致索引,而索引又会导致数据。如果要恢复的数据分布广泛,结果将需要很长时间,这似乎是数据库中发生的情况。

    【讨论】:

      【解决方案5】:

      请阅读thisthis

      Solr (Lucene) 创建了一个inverted index,它是检索数据变得更快的地方。我read PostgreSQL 也有类似的功能,但不确定您是否使用过。

      您观察到的性能差异也可以归因于“正在搜索什么?”、“用户查询是什么?”

      【讨论】:

      • 谢谢!那些非常有趣。不过,我希望有更多技术性的东西。就像 Solr 的架构概述,或类似的东西。
      • @Tejas:甚至数据库也可以创建倒排索引。是什么阻止他们创建倒排索引?
      • Yavar:我没有说数据库不能创建倒排索引。事实上,在第 2 行中,我使用 GIN 反转索引指出了有关 PostgreSQL 的链接。还有另一种类型:PostgreSQL 中基于 GiST(通用搜索树)的索引,它比 GIN 慢。 @cberner 使用的实际索引类型将是 PostgreSQL 性能低下的一个因素。
      猜你喜欢
      • 2014-05-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-25
      • 2014-04-11
      • 1970-01-01
      • 2018-09-25
      • 2014-01-11
      相关资源
      最近更新 更多