rum(全文检索加速)

当您需要在海量文本中进行全文检索,并且希望对检索结果基于相关度或时间戳等字段进行高性能排序时,RUM插件是比PostgreSQL内置GIN索引更优的选择。传统的GIN索引在排序时需要回表(二次访问数据表),导致性能瓶颈。RUM插件通过在索引中预存排序所需的位置和附加信息,避免了回表操作,可将特定场景下的查询性能提升数倍。

适用范围

  • 支持的PolarDB PostgreSQL的版本如下:

    • PostgreSQL 18(内核小版本2.0.18.1.2.0及以上)

    • PostgreSQL 17(内核小版本2.0.17.6.4.0及以上)

    • PostgreSQL 16(内核小版本2.0.16.8.3.0及以上)

    • PostgreSQL 15(内核小版本2.0.15.7.1.1及以上)

    • PostgreSQL 14(内核小版本2.0.14.5.3.0及以上)

    说明

    您可在控制台查看内核小版本号,也可以通过SHOW polardb_version;语句查看。如未满足内核小版本要求,请升级内核小版本

  • 插件兼容性:RUM插件的%操作符与smlar插件的%操作符存在冲突。因此,这两个插件不能在同一个数据库模式(Schema)下同时创建和使用。请在安装前确认您的环境中没有使用smlar插件,或将它们安装在不同的Schema中。

适用场景

在决定是否使用RUM插件前,请参考下表了解其与原生GIN索引的核心差异,以便做出最适合您业务场景的技术选型。

对比维度

GIN索引(内置)

RUM索引

决策建议

核心优势

写入性能好,索引体积相对较小。

排序性能高,支持短语搜索和附加列排序。

需要对检索结果排序的场景,请优先选择RUM。

排序性能

慢。需要回表获取排序依据,性能随数据量增大而急剧下降。

快。直接在索引内完成排序,无需回表。

对全文检索结果有高频排序需求(如按相关性、时间、价格),推荐使用RUM。

短语搜索

慢。需要回表获取词汇位置信息以验证短语。

快。词汇位置信息已在索引中,无需回表。

对短语搜索性能有要求的场景,推荐使用RUM。

附加列排序

不支持。无法在索引中存储额外列(如时间戳)的信息。

支持。可在索引中附加其他列,实现高性能自定义排序。

需要按文章发布时间、更新时间等排序的场景,RUM优势大。

写入性能

较快。

较慢。需要维护更复杂的索引结构。

在写入密集型(高频INSERT/UPDATE)的表中,需谨慎评估RUM带来的写入开销。

索引体积

较小。

较大。需要额外空间存储位置和附加信息。

评估您的存储成本,如果索引体积是主要瓶颈,可考虑RUMhash类型索引或继续使用GIN。

前缀搜索

支持。

rum_tsvector_ops支持,hash系列不支持。

如果前缀搜索是核心需求,避免使用RUMhash系列索引。

RUM以空间换时间,通过增加索引体积和写入开销,有效提升了特定查询场景(尤其是排序)的性能。如果您的业务核心是带有复杂排序的全文检索,RUM是理想选择。如果您的业务主要是写入密集型或仅需简单文本匹配,GIN则更具成本效益。

注意事项

  • 写入性能与索引体积:RUM索引为了加速查询,需要存储额外信息(如词汇位置),这导致其索引体积通常大于GIN索引,并且在数据写入和更新时,索引构建的开销也更高。因此,在写入密集型且对存储空间敏感的场景下,需谨慎评估其成本。

  • 不支持前缀搜索的场景:使用rum_tsvector_hash_opsrum_tsvector_hash_addon_ops操作符类创建的索引,由于存储的是词素的哈希值而非原文,因此不支持前缀搜索。

安装与卸载插件

安装插件

在开始使用前,您需要在数据库中执行以下命令来创建RUM插件。

CREATE EXTENSION rum;

卸载插件

如果您不再需要RUM插件,可以执行以下命令进行卸载。

DROP EXTENSION rum;

使用方法

RUM插件提供了多种操作符类(Operator Class),以支持不同的数据类型和查询场景。您可以根据具体需求选择合适的操作符类来创建索引。

说明

操作符类(Operator Class)明确了RUM索引在处理特定数据类型时所需使用的一组操作,从而让索引能够正确地存储和检索该类型的数据。每个操作符类(Operator Class)都包含一组特定的操作符。当你WHERE子句或ORDER BY子句中使用这些受支持的操作符时,PostgreSQL就能利用RUM索引来有效地加速查询。

因此,选择正确的操作符类(Operator Class)是让RUM索引生效的关键。更多信息,请参见Operator Classes and Operator Families

操作符

操作符

支持类型

返回值类型

描述

@@ B

tsvector,右tsquery

bool

返回全文向量是否匹配查询条件,会进行距离计算。

<=> B

tsvector,右tsquery

float4

返回全文向量和查询条件的距离值,值越小相关性越高。

timestamptimestamptzint2int4int8float4float8moneyoid

float8

返回两个值之间的绝对差值。

  • 时间差值以秒为单位,精确到小数点后六位,即微秒。

  • money差值以美分为单位,精确到美分。

<=| B

timestamptimestamptzint2int4int8float4float8moneyoid

float8

仅当A ≤ B时返回B - A,否则返回无穷大。

|=> B

timestamptimestamptzint2int4int8float4float8moneyoid

float8

仅当A > B时返回A - B,否则返回无穷大。

Operator Class(操作符类)

操作符类

适用数据类型

支持的关键操作符

核心功能与说明

rum_tsvector_ops

tsvector

  • WHERE:A @@ B

  • ORDER BY:A <=> B

存储tsvector的词素及其位置信息。支持全文检索、前缀搜索和相关性排序,这是最常用的全文检索操作符类。

rum_tsvector_hash_ops

tsvector

  • WHERE:A @@ B

  • ORDER BY:A <=> B

存储tsvector词素的哈希值和位置信息。

  • 支持全文检索和相关性排序,但不支持前缀搜索。

  • 相比rum_tsvector_ops索引体积可能更小。

  • 搜索时可能出现哈希冲突,此时需要回表重新检查 (recheck)。

rum_<TYPE>_ops

int2int4int8float4float8moneyoidtimetimetzdateintervalmacaddrinetcidrtextvarcharcharbyteabitvarbitnumerictimestamptimestamptz

  • WHERE:适用数据类型中,相同类型间的<<==>=>

  • ORDER BYint2int4int8float4float8moneyoidtimestamptimestamptz中,相同类型间的<=><=||=>操作。

对非文本、非数组类型的数据进行范围查询和距离排序。

rum_tsvector_addon_ops

tsvector

WHERE:A @@ B

tsvector索引基础上,附加一个额外列(如timestamp)的数据,支持对主列进行全文检索的同时,对附加列进行高效排序。

说明

附加列的数据类型需有对应的rum_<TYPE>_ops操作符类支持,并且排序时需要使用<=><=||=>操作符才能利用索引进行加速。

rum_tsvector_hash_addon_ops

tsvector

WHERE:A @@ B

功能同rum_tsvector_addon_ops

  • 因存储的是词组的哈希值,不支持前缀搜索。

  • 相比rum_tsvector_addon_ops索引体积可能更小。

  • 搜索时可能出现哈希冲突,此时需要回表重新检查 (recheck)。

  • 相比rum_tsvector_addon_ops索引搜索可能更慢。

rum_tsquery_ops

tsquery

WHERE:A @@ B

用于索引tsquery列。可以反向加速查询,快速找出哪些已存的查询条件(tsquery)能够匹配给定的文档(tsvector)。

rum_anyarray_ops

anyarray,例如 int[]text[]varchar[]

  • WHERE

    • &&:数组是否相交(两个数组有共同元素)。

    • @>:是否包含(左数组包含右数组的所有元素)。

    • <@:是否被包含。

    • =:数组是否相等。

    • :数组是否相似(会进行相似度计算,超过阈值即判定为相似)。

  • ORDER BY

    • <=>:两个数组之间的距离。

索引数组类型,支持包含、重叠等数组操作,并支持按数组间距离排序。

rum_anyarray_addon_ops

anyarray,例如 int[]text[]varchar[]

  • WHERE

    • &&:数组是否相交(两个数组有共同元素)。

    • @>:是否包含(左数组包含右数组的所有元素)。

    • <@:是否被包含。

    • =:数组是否相等。

    • :数组是否相似(会进行相似度计算,超过阈值即判定为相似)。

  • ORDER BY

    • <=>:两个数组之间的距离。

在数组索引的基础上,附加一个额外列的数据,以支持更复杂的查询场景。

说明

附加列的数据类型需有对应的rum_<TYPE>_ops操作符类支持,并且排序时需要使用<=><=||=>操作符才能利用索引进行加速。

场景一:加速全文检索结果的相关性排序

当您需要对全文检索结果按相关性排序时,使用RUM索引可以避免GIN索引所需的额外排序开销,实现高性能排序。

  1. 准备数据:首先,创建一张测试表。

    CREATE TABLE t1(
      t text,
      t_vec tsvector GENERATED ALWAYS AS (to_tsvector('pg_catalog.english', t)) STORED
    );
    
    -- 插入测试数据
    INSERT INTO t1(t) VALUES ('The situation is most beautiful');
    INSERT INTO t1(t) VALUES ('It is a beautiful');
    INSERT INTO t1(t) VALUES ('It looks like a beautiful place');
  2. 创建RUM索引:使用rum_tsvector_ops操作符类为tsvector列创建RUM索引。

    CREATE INDEX t1_t_vec_idx ON t1 USING rum (t_vec rum_tsvector_ops);
  3. 执行相关性排序查询:使用<=>操作符进行查询。此操作符计算查询与文本的距离,距离值越小,代表相关性越高。因此,通过ORDER BY即可实现按相关性降序排列。

    SET enable_seqscan TO off;
    
    SELECT t, t_vec <=> to_tsquery('english', 'beautiful | place') AS rank
    FROM t1
    WHERE t_vec @@ to_tsquery('english', 'beautiful | place')
    ORDER BY t_vec <=> to_tsquery('english', 'beautiful | place');

    返回结果如下:

                    t                |  rank   
    ---------------------------------+---------
     It looks like a beautiful place | 8.22467
     The situation is most beautiful | 16.4493
     It is a beautiful               | 16.4493

场景二:加速全文检索与附加列的联合排序

在日志分析、电商搜索等场景中,经常需要在全文检索的同时,根据时间戳或价格等附加字段进行排序。RUM通过addon功能,可以将附加列的信息存入索引,实现高效的联合查询和排序。

  1. 准备数据:创建一张包含tsvector列和时间戳列的表,并插入示例数据。

    CREATE TABLE tsts (id int, t tsvector, d timestamp);
    INSERT INTO tsts VALUES
    (354, to_tsvector('wr qh'), '2016-05-16 14:21:22.326724'),
    (355, to_tsvector('wr qh'), '2016-05-16 13:21:22.326724'),
    (356, to_tsvector('ts op'), '2016-05-16 18:21:22.326724'),
    (358, to_tsvector('ts op'), '2016-05-16 23:21:22.326724'),
    (371, to_tsvector('wr qh'), '2016-05-17 06:21:22.326724'),
    (406, to_tsvector('wr qh'), '2016-05-18 17:21:22.326724'),
    (415, to_tsvector('wr qh'), '2016-05-19 02:21:22.326724');
  2. 创建带附加列的RUM索引:使用rum_tsvector_addon_ops操作符类,并通过WITH子句指定附加列和主索引列。

    CREATE INDEX tsts_idx ON tsts USING rum (t rum_tsvector_addon_ops, d) WITH (attach = 'd', to = 't');
    说明

    关键语法WITH (attach = 'd', to = 't')的作用是将d列(附加列,此处为时间戳)的值附加到t列(主索引列,tsvector类型)的索引条目中。这使得数据库在一次索引扫描中,就能同时利用t列的索引进行全文检索,并利用附加的d列信息进行高效排序,避免了回表查询,从而有效提升性能。

  3. 执行联合排序查询:查询包含特定词汇的记录,并按时间戳与目标时间的接近程度排序。

    SET enable_seqscan TO off;
    
    EXPLAIN (costs off)
    SELECT id, d, d <=> '2016-05-16 14:21:25' AS distance
    FROM tsts
    WHERE t @@ 'wr&qh'
    ORDER BY d <=> '2016-05-16 14:21:25'
    LIMIT 5;

    执行计划如下,排序和过滤均在一次索引扫描中完成。

                                      QUERY PLAN                                  
    ------------------------------------------------------------------------------
     Limit
       ->  Index Scan using tsts_idx on tsts
             Index Cond: (t @@ '''wr'' & ''qh'''::tsquery)
             Order By: (d <=> '2016-05-16 14:21:25'::timestamp without time zone)

    查询结果如下:

     id  |             d              |   distance    
    -----+----------------------------+---------------
     354 | 2016-05-16 14:21:22.326724 |      2.673276
     355 | 2016-05-16 13:21:22.326724 |   3602.673276
     371 | 2016-05-17 06:21:22.326724 |  57597.326724
     406 | 2016-05-18 17:21:22.326724 | 183597.326724
     415 | 2016-05-19 02:21:22.326724 | 215997.326724

场景三:加速数组查询与相似度排序

对于标签系统或用户画像等场景,需要高效查询包含特定元素的数组,并按数组元素的重合度或相似度进行排序。

  1. 准备数据

    CREATE TABLE test_array (id serial, i int2[]);
    INSERT INTO test_array(i) VALUES ('{}'), ('{0}'), ('{1,2,3,4}'), ('{1,2,3}'), ('{1,2}'), ('{1}');
  2. 创建数组RUM索引:使用rum_anyarray_ops操作符类为数组类型的列创建索引。

    CREATE INDEX idx_array ON test_array USING rum (i rum_anyarray_ops);
  3. 执行数组查询与排序:查询包含元素1的记录,并按与{1}的相似度排序。

    SELECT *
    FROM test_array
    WHERE i && '{1}' -- '&&' 操作符表示数组重叠
    ORDER BY i <=> '{1}' ASC; -- '<=>' 操作符计算数组间的距离,值越小越相似

    返回结果如下:

         i
    -----------
     {1}
     {1,2}
     {1,2,3}
     {1,2,3,4}

场景四:反向索引,快速匹配查询规则

在构建用户订阅、告警规则匹配等系统时,需要用一条新数据(如一篇文章)去快速匹配海量的存量查询规则(如用户的订阅关键词)。RUM支持对tsquery类型创建索引,实现高效的反向匹配。

  1. 准备查询规则数据

    CREATE TABLE query (id serial, q tsquery, tag text);
    INSERT INTO query (q, tag) VALUES
    ('supernova & star', 'sn'),
    ('black', 'color'),
    ('big & bang & black & hole', 'bang'),
    ('spiral & galaxy', 'shape'),
    ('black & hole', 'color');
  2. 创建tsqueryRUM索引

    CREATE INDEX query_idx ON query USING rum(q rum_tsquery_ops);
  3. 执行反向匹配查询:使用一篇新文章的tsvector来匹配所有符合条件的tsquery规则。

    SELECT *
    FROM query
    WHERE to_tsvector('black holes never exists before we think about them') @@ q;

    返回结果如下:

    id   |    q     |  tag
    -----+----------+-------
    2    | 'black'  | color