优化 postgres 相似性查询(pg_trgm + gin 索引)

2023-11-21

我定义了以下索引:

CREATE INDEX
    users_search_idx
ON
    auth_user
USING
    gin(
        username gin_trgm_ops,
        first_name gin_trgm_ops,
        last_name gin_trgm_ops
    );

我正在执行以下查询:

PREPARE user_search (TEXT, INT) AS
    SELECT
        username,
        email,
        first_name,
        last_name,
        ( -- would probably do per-field weightings here
            s_username + s_first_name + s_last_name
        ) rank
    FROM
        auth_user,
        similarity(username, $1) s_username,
        similarity(first_name, $1) s_first_name,
        similarity(last_name, $1) s_last_name
    WHERE
        username % $1 OR
        first_name % $1 OR
        last_name % $1
    ORDER BY
        rank DESC
    LIMIT $2;

The auth_user表有 620 万行。

查询的速度似乎在很大程度上取决于可能返回的结果数量similarity query.

通过增加相似度阈值set_limit有帮助,但通过消除部分匹配而降低了结果的有用性。

有些搜索会在 200 毫秒内返回,有些则需要 10 秒左右。

我们已经使用 Elasticsearch 实现了此功能,任何查询的返回时间均小于 200 毫秒,同时进行更复杂(更好)的排名。

我想知道是否有任何方法可以改进这一点以获得更一致的性能?

据我了解,GIN 索引(倒排索引)与 Elasticsearch 使用的基本方法相同,因此我认为可以进行一些优化。

An EXPLAIN ANALYZE EXECUTE user_search('mel', 20) shows:

Limit  (cost=54099.81..54099.86 rows=20 width=52) (actual time=10302.092..10302.104 rows=20 loops=1)
  ->  Sort  (cost=54099.81..54146.66 rows=18739 width=52) (actual time=10302.091..10302.095 rows=20 loops=1)
        Sort Key: (((s_username.s_username + s_first_name.s_first_name) + s_last_name.s_last_name)) DESC
        Sort Method: top-N heapsort  Memory: 26kB
        ->  Nested Loop  (cost=382.74..53601.17 rows=18739 width=52) (actual time=118.164..10293.765 rows=8380 loops=1)
              ->  Nested Loop  (cost=382.74..53132.69 rows=18739 width=56) (actual time=118.150..10262.804 rows=8380 loops=1)
                    ->  Nested Loop  (cost=382.74..52757.91 rows=18739 width=52) (actual time=118.142..10233.990 rows=8380 loops=1)
                          ->  Bitmap Heap Scan on auth_user  (cost=382.74..52383.13 rows=18739 width=48) (actual time=118.128..10186.816 rows=8380loops=1)"
                                Recheck Cond: (((username)::text % 'mel'::text) OR ((first_name)::text % 'mel'::text) OR ((last_name)::text %'mel'::text))"
                                Rows Removed by Index Recheck: 2434523
                                Heap Blocks: exact=49337 lossy=53104
                                ->  BitmapOr  (cost=382.74..382.74 rows=18757 width=0) (actual time=107.436..107.436 rows=0 loops=1)
                                      ->  Bitmap Index Scan on users_search_idx  (cost=0.00..122.89 rows=6252 width=0) (actual time=40.200..40.200rows=88908 loops=1)"
                                            Index Cond: ((username)::text % 'mel'::text)
                                      ->  Bitmap Index Scan on users_search_idx  (cost=0.00..122.89 rows=6252 width=0) (actual time=43.847..43.847rows=102028 loops=1)"
                                            Index Cond: ((first_name)::text % 'mel'::text)
                                      ->  Bitmap Index Scan on users_search_idx  (cost=0.00..122.89 rows=6252 width=0) (actual time=23.387..23.387rows=58740 loops=1)"
                                            Index Cond: ((last_name)::text % 'mel'::text)
                          ->  Function Scan on similarity s_username  (cost=0.00..0.01 rows=1 width=4) (actual time=0.004..0.004 rows=1 loops=8380)
                    ->  Function Scan on similarity s_first_name  (cost=0.00..0.01 rows=1 width=4) (actual time=0.002..0.002 rows=1 loops=8380)
              ->  Function Scan on similarity s_last_name  (cost=0.00..0.01 rows=1 width=4) (actual time=0.002..0.002 rows=1 loops=8380)
Execution time: 10302.559 ms

服务器是在 Amazon RDS 上运行的 Postgres 9.6.1

update

1.

发布问题后不久我发现了以下信息:https://www.postgresql.org/message-id/[电子邮件受保护]

所以我尝试了

-> SHOW work_mem;
4MB
-> SET work_mem='12MB';
-> EXECUTE user_search('mel', 20);
(results returned in ~1.5s)

这取得了很大的进步(之前 > 10 秒)!

对于类似的查询,1.5s 仍然比 ES 慢很多,所以我仍然想听到任何优化查询的建议。

2.

回复评论,并在看到这个问题后(Postgresql GIN 索引比 pg_trgm 的 GIST 慢),我尝试了完全相同的设置,用 GIST 索引代替 GIN 索引。

尝试与上面相同的搜索,它在〜3.5秒内返回,使用默认值work_mem='4MB'。增加work_mem没有什么区别。

由此我得出的结论是,GIST 索引的内存效率更高(没有像 GIN 那样遇到病态情况),但当 GIN 正常工作时,它比 GIN 慢。这与推荐 GIN 索引的文档中描述的内容一致。

3.

我还是不明白为什么要花这么多时间:

 ->  Bitmap Heap Scan on auth_user  (cost=382.74..52383.13 rows=18739 width=48) (actual time=118.128..10186.816 rows=8380loops=1)"
     Recheck Cond: (((username)::text % 'mel'::text) OR ((first_name)::text % 'mel'::text) OR ((last_name)::text %'mel'::text))"
     Rows Removed by Index Recheck: 2434523
     Heap Blocks: exact=49337 lossy=53104

我不明白为什么需要这一步或者它在做什么。

有以下三个Bitmap Index Scan在其下面的每个username % $1子句...然后将这些结果与BitmapOr步。这些部分都非常快。

但即使在我们没有耗尽工作内存的情况下,我们仍然花费了近一整秒的时间Bitmap Heap Scan.


我预计much使用这种方法可以更快获得结果:

1.

创建一个 GiST 索引,其中 1 列保存连接值:

CREATE INDEX users_search_idx ON auth_user
USING gist((username || ' ' || first_name || ' ' || last_name) gist_trgm_ops);

假设所有 3 列均已定义NOT NULL(你没有指定)。否则你需要做更多的事情。
为什么不简化为concat_ws()?

  • 合并两列并添加到一个新列中
  • 通过多个文本字段的模式匹配加快查询速度
  • 合并两列并添加到一个新列中

2.

使用适当的最近的邻居查询,匹配上面的索引:

SELECT username, email, first_name, last_name
     , similarity(username  , $1) AS s_username
     , similarity(first_name, $1) AS s_first_name
     , similarity(last_name , $1) AS s_last_name
     , row_number() OVER () AS rank  -- greatest similarity first
FROM   auth_user
WHERE     (username || ' ' || first_name || ' ' || last_name) %   $1  -- !!
ORDER  BY (username || ' ' || first_name || ' ' || last_name) <-> $1  -- !!
LIMIT  $2;

中的表达式WHERE and ORDER BY必须匹配索引表达式!

尤其ORDER BY rank(就像你有的那样)对于小规模来说总是表现不佳LIMIT从更大的合格行池中进行选择,因为它不能直接使用索引:背后的复杂表达式rank必须计算为every合格行,则必须先对所有行进行排序,然后才能返回最佳匹配的小选择。这是贵得多与真正的最近邻查询相比,它可以直接从索引中选择最佳结果,甚至无需查看其余结果。

row_number()空窗口定义仅反映了由ORDER BY一样的SELECT.

相关回答:

  • 相似度函数的最佳索引
  • 使用 pg_trgm 搜索 3 亿个地址

至于你的物品3.,我添加了您提到的问题的答案,这应该可以解释它:

  • PostgreSQL GIN 索引比 pg_trgm 的 GIST 慢?
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

优化 postgres 相似性查询(pg_trgm + gin 索引) 的相关文章

随机推荐

  • 使用 MVVM 从 WPF 应用程序启动对话框/子窗口的标准方法

    所有 我想知道使用 MVVM 模式从 WPF 启动 子 对话框 窗口的公认最佳方法 行业标准 我遇到过以下文章 A CodeProject 使用 MVVM 模式时显示对话框 这种方法对我来说似乎不错 但有些过分了 这是某种程度的代码复制 我
  • Python 3 中大于 10^2000 的数字的平方根

    我想在Python中计算大于10 2000的数字的平方根 如果我将这个数字视为普通整数 我总是会得到这个结果 Traceback most recent call last File line 3 in
  • 在项目“MyProject”上运行构建器“Faceted Project Validation Builder”时出错

    我正在研究 Blackberry webworks Phonegap 框架 Apache Ant 并使用示例 index html 在 Eclipse 3 6 中配置它们 我关注了这篇文章PhoneGap BlackBerry WebWor
  • 您可以从 GitHub 上的命令行发出拉取请求吗?

    似乎您必须与 github com 交互才能发起拉取请求 是这样吗 UPDATE The hub命令现已成为官方github项目 也支持创建拉取请求 ORIGINAL 似乎添加到 hub 命令中特别有用 http github com de
  • ES6 类私有属性只是语法糖吗?

    使用 语法我们现在可以创建私人财产在 ES6 类中是这样的 class Person name constructor name this name name getName return this name let ron new Per
  • Lambda 表达式以及如何组合它们?

    如何使用 OR 将两个 lambda 表达式合并为一个 我已尝试以下操作 但合并它们需要我将参数传递到表达式 调用调用 但是我希望将传递到新 lambda 的值传递到每个子 lambda 上 Expression
  • Java:以编程方式确定类路径上加载的所有包名称

    关于如何找到列表的任何建议包名存在于当前类路径 这需要在运行时由类路径上加载 和执行 的类之一以编程方式完成 即 反了 而不是从外到内 更多细节 我考虑的一种方法是对类加载器迄今为止加载的每个类使用反射 并从中提取包名称 但是 我的应用程序
  • iOS 6 ViewController 正在旋转但不应该旋转

    我希望我的几个应用程序视图控制器在 iOS 6 0 中不旋转 这就是我为使 iOS 6 中的轮换成为可能而所做的 1 在 application didFinishLaunchingWithOptions 中设置 windows rootv
  • 动态生成的 HTML 的格式 - 没人关心吗?

    I have veryWeb开发经验很少 所以这可能是一个非常基本的问题 只是 以我有限的经验来看do有 一点PHP 一点Ruby on Rails 动态生成HTML的方式似乎是格式化的只是 没关系 它最终变得丑陋 有奇怪的缩进 没有人关心
  • 流式传输 xml-conduit 解析结果

    我想用xml conduit 具体来说Text XML Stream Parse为了从大型 XML 文件中延迟提取对象列表 作为测试用例 我使用最近重新发布的 StackOverflow 数据转储 为了简单起见 我打算从中提取所有用户名st
  • 理解范围和数组中的 ruby​​ splat

    我试图理解之间的区别 1 9 and 1 9 如果我将它们分配给变量 它们的工作方式是相同的 splat1 1 9 splat1 1 2 3 4 5 6 7 8 9 splat2 1 9 splat2 1 2 3 4 5 6 7 8 9 但
  • 如何启用/禁用 FloatingActionButton 行为

    我正在开发一些片段中的应用程序 我想隐藏浮动操作按钮 当我设置android 可见性 消失 当我上下滑动时 行为动画向我显示浮动操作按钮 有什么方法可以禁用 启用 FloatingActionButton 行为 谢谢你提前 这是我的代码 Q
  • 使用 JavaScript 算出 DIV 可以容纳多少个字符

    有谁知道使用 JavaScript 计算出 HTML 中的 DIV 块可以容纳多少个字符的最佳方法是什么 任何建议都会有很大帮助 您可以迭代地将字符添加到隐藏的 div 中并检查其宽度 不确定是否有更好的方法 编辑 类似这样的事情 var
  • 查找与所有给定字符串匹配的最简单的正则表达式

    是否有一种算法可以从一组字符串生成正则表达式 可能仅限于简化语法 以便对与正则表达式匹配的所有可能字符串进行求值 从而重现初始字符串集 为具有非常 复杂 语法 包括任意重复 断言等 的正则表达式语法找到这样一种算法可能是不现实的 所以让我们
  • 如何解决 Angular“已达到 10 $digest() 迭代”错误

    已达到 10 次 digest 迭代 流产 有很多 在最近 5 次迭代中触发的观察者 等意义上的支持文本 但其中很多文本是来自各种函数的 Javascript 代码 是否有诊断此问题的经验法则 这是一个总是可以缓解的问题 还是存在足够复杂的
  • 在 Firefox 中使用 History.pushState 使我的图标消失

    使用类似的东西 history pushState null document title 在我的网站中 我的网站图标在 Firefox 中消失 但它在 chrome 中有效 这是在页面加载时添加 favicon 的 javascript
  • 为什么我的 D2009 exe 会生成带有名为 ATTnnnnn.DAT 的附件的电子邮件

    为什么我的 D2009 exe 会生成带有名为 ATTnnnnn DAT 的附件的电子邮件 而在 D2007 中编译的相同源代码会生成带有正确命名为原始文件名的附件的电子邮件 我正在使用 D2007 和 D2009 附带的相应 Indy 库
  • Android - 按住按钮重复操作

    我会立即承认我是开发新手 并且正在尝试 Android 我一直在尝试在网络上搜索 以找到有关如何实现一些 按住按钮重复操作 的建议 我已经从按钮创建了一个自定义数字键盘 并且想要类似退格的行为 到目前为止 我拜访了一位以前没有编写过 And
  • 异步 lambda 中的参数[重复]

    这个问题在这里已经有答案了 我试图同时运行多个任务 但遇到了一个我似乎无法理解或解决的问题 我曾经有一个这样的功能 private void async DoThings int index bool b await SomeAsynchr
  • 优化 postgres 相似性查询(pg_trgm + gin 索引)

    我定义了以下索引 CREATE INDEX users search idx ON auth user USING gin username gin trgm ops first name gin trgm ops last name gi