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

Ane*_*pic 6 postgresql similarity trigram postgresql-9.6 pg-trgm

我已经定义了以下索引:

CREATE INDEX
    users_search_idx
ON
    auth_user
USING
    gin(
        username gin_trgm_ops,
        first_name gin_trgm_ops,
        last_name gin_trgm_ops
    );
Run Code Online (Sandbox Code Playgroud)

我正在执行以下查询:

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;
Run Code Online (Sandbox Code Playgroud)

auth_user表有620万行.

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

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

有些搜索在200ms内返回,其他搜索需要大约10秒.

我们使用Elasticsearch对此功能进行了实现,对于任何查询都返回<200ms,同时执行更复杂(更好)的排名.

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

我的理解是GIN索引(倒排索引)与Elasticsearch使用的基本方法相同,所以我认为可以进行一些优化.

一个EXPLAIN ANALYZE EXECUTE user_search('mel', 20)节目:

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
Run Code Online (Sandbox Code Playgroud)

Server是在Amazon RDS上运行的Postgres 9.6.1

更新

1.

在发布问题后不久,我发现了这个信息:https://www.postgresql.org/message-id/464F3C5D.2000700@enterprisedb.com

所以我试过了

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

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

对于类似的查询,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
Run Code Online (Sandbox Code Playgroud)

我不明白为什么需要这个步骤或者它正在做什么.

Bitmap Index Scan每个username % $1条款下面都有三个......这些结果然后与一个BitmapOr步骤相结合.这些部分都非常快.

但即使在我们没有用完工作的情况下,我们仍然花了将近一秒钟Bitmap Heap Scan.

Erw*_*ter 5

我希望用这种方法更快的结果:

1。

创建一个Gist索引,其中1列包含连接的值:

CREATE INDEX users_search_idx ON auth_user
USING gist((username || ' ' || first_name || ' ' || last_name) gist_trgm_ops);
Run Code Online (Sandbox Code Playgroud)

假定要定义所有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;
Run Code Online (Sandbox Code Playgroud)

在表达WHEREORDER BY必须匹配索引表达式!

特别是ORDER BY rank(如您所料)LIMIT从较大的合格行池中进行少量选择时,总是表现不佳,因为它不能直接使用索引:rank必须为每个合格行计算后面的复杂表达式,然后所有操作在返回最佳匹配的一小部分之前进行排序。这比真正的最近邻查询要昂贵得多,后者可以直接从索引中选择最佳结果,而无需查看其余部分。

row_number()空窗口定义只反映ORDER BY相同的产生的顺序SELECT

相关答案:


至于您的项目3.,我为您所引用的问题添加了答案,应该可以对其进行解释: