68

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

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

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

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

4

5 回答 5

138

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

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

但是 Solr 只能做到这一点,因为它没有 RDBMS 所具有的所有约束。例如,Solr 在一次更新单个文档方面非常糟糕(它更喜欢批量更新)。

于 2012-04-07T11:31:43.813 回答
41

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

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

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

我将包含一个简短的示例,说明如何在 PostgreSQL 中完成对多个条件的文本搜索,以及一些小的调整如何产生很大的性能差异。为了保持快速和简单,我只是将文本形式的战争与和平运行到一个测试数据库中,每个“文档”都是一个文本行。如果必须松散地定义数据,则可以对使用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% 的性能,但在使用 GIN(倒排)索引时,搜索同时包含“女士”“绅士”的行的速度大约翻了一番,达到 0.053 毫秒(即 53 微秒)。

于 2012-04-07T14:55:47.990 回答
7

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

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

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

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

于 2012-04-07T09:30:25.027 回答
6

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

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

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

于 2012-04-07T10:19:33.070 回答
1

请阅读这个这个

Solr (Lucene) 创建了一个倒排索引,在该索引中检索数据变得相当快。我读到PostgreSQL 也有类似的功能,但不确定你是否使用过。

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

于 2012-04-07T08:52:29.947 回答