为什么Solr比Postgres快得多?

cbe*_*ner 63 lucene postgresql performance rdbms solr

我最近从Postgres切换到了Solr,在查询中加速了~50倍.我们运行的查询涉及多个范围,我们的数据是车辆清单.例如:"查找里程<50,000,$ 5,000 <价格<$ 10,000,make = Mazda ......"的所有车辆

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

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

架构有什么不同可以解释这一点?

jpo*_*ntz 131

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

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

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

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

kgr*_*ttn 37

你没有真正说出你为调整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;
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

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

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

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

如果没有索引,上述查询需要从17.943 ms到23.397 ms,因为它们必须扫描整个表并检查每行的匹配.

GIN索引搜索"女士"和"绅士"的行比完全相同的数据库中的表扫描快172倍.显然,索引的好处在于文档比用于此测试的文档更大.

当然,设置是一次性的.通过触发器来维护tsv列,可以立即搜索所做的任何更改,而无需重做任何设置.

使用慢速PostgreSQL查询,如果您显示表结构(包括索引),问题查询以及查询运行的输出EXPLAIN ANALYZE,有人几乎总能发现问题并建议如何让它运行得更快.


更新(16年9月9日)

我没有提到我过去获得的时间,但根据日期可能会是9.2主要版本.我刚刚遇到这个旧线程,并使用版本9.6.1在相同的硬件上再次尝试,以查看是否有任何干预性能调整有助于此示例.仅一个参数的查询仅在性能上增加了大约2%,但是当使用GIN(倒置)索引时,搜索具有"女士" "绅士"的行的速度加倍到0.053毫秒(即53微秒).

  • 请注意,GiST的维护速度不会太快,参见 http://blog.pgaddict.com/posts/performance-since-postgresql-7-4-to-9-4-fulltext (4认同)

Mar*_*nor 6

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

毫无疑问,Solr能够从索引中搜索和检索数据.这是后者(可选)能力导致自然问题......"我可以将Solr用作数据库吗?"

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

我个人认为Solr最好被认为是我的应用程序和数据库中掌握的数据之间的可搜索缓存.这样我就可以获得两全其美.


Yav*_*var 6

最大的区别是Lucene/Solr索引就像单表数据库,不支持关系查询(JOIN).请记住,索引通常只支持搜索,而不是数据的主要来源.因此,您的数据库可能处于"第三范式",但索引将完全取消规范化,并且主要包含需要搜索的数据.

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

这意味着,例如,考虑到数据库的索引体系结构,查询会导致索引进而导致数据.如果要恢复的数据广泛传播,结果将花费很长时间,这似乎是数据库中发生的事情.