文本检索性能提升 40 倍,Apache Doris 倒排索引深度解读

本文以Amazon customer reviews数据集为例,介绍Apache Doris在查询时利用倒排索引和NGram Bloom Filter索引加速的方法。对比了有索引和无索引时的查询性能,解析索引加速原理,还探讨了不同场景下索引的适配情况,后续将对倒排索引进行迭代优化。

在 OLAP 领域,Apache Doris 已成为高性能、高并发以及高时效性的代名词。在面向海量数据的复杂查询需求时,除硬件配置、集群规模、网络带宽等因素外,提升性能的核心在于如何最大程度地降低 SQL 执行时的 CPU、内存和 IO 开销,而这其中数据库索引扮演着至关重要的角色。合理的索引结构设计可以跳过大量不必要的底层数据读取、快速检索定位到所需数据,并进一步提升后续计算的执行效率、降低查询 SQL 的运行时间和资源消耗。

Apache Doris 提供了丰富的索引以加速数据的读取和过滤,依据是否需要用户手工创建,索引类型大体可以分为智能内建索引和用户创建索引两类,其中智能内建索引是指在数据写入时自动生成的索引,无需用户干预,包括前缀索引和 ZoneMap 索引。用户创建索引需要用户根据业务特点手动创建,包括 Bloom Filter 索引和 2.0 版本新增的倒排索引与 NGram Bloom Filter 索引。

相较于用户比较熟悉的前缀索引、Bloom Filter 索引,2.0 版本所新增的倒排索引和 NGram Bloom Filter 在文本检索、模糊匹配以及非主键列检索等场景有着更为明显的性能提升。本文将以 Amazon customer reviews 数据集为例,介绍 Apache Doris 在查询该数据集以及类似场景中,如何充分利用倒排索引以及 NGram Bloom Filter 索引进行查询加速,并详细解析其工作原理与最佳实践。

数据集样例

在本文中,我们使用的数据集包含约 1.3 亿条亚马逊产品的用户评论信息。该数据集以 Snappy 压缩的 Parquet 文件形式存在,总大小约为 37GB。以下为数据集的样例:

数据集样例.png

在子集中,每行包含用户 ID(customer_id)、评论 ID(review_id)、已购买产品 ID(product_id)、产品分类(product_category)、评分(star_rating)、评论标题(review_headline)、评论内容(review_body)等 15 列信息。
根据上述可知,列中包含了适用于索引加速的各种特征。例如,customer_id 是高基数的数值列,product_id 是低基数的定长短文本列,product_title 是适合文本检索的短文本列,review_body 则是适合文本搜索的长文本列。

通过这些列,我们可以模拟两个典型索引查询场景,具体如下:

  • 文本搜索查询:搜索 review body 字段中包含特定内容的产品信息。
  • 非主键列明细查询:查询特定产品 ID(product_id)或者特定用户 ID(customer_id)的评论信息。

接下来,我们将以文本搜索和非主键列明细查询为主要方向,对比在有索引和无索引的情况下查询性能的差异。同时,我们也将详细解析索引减少查询耗时、提高查询效率的原理。

环境搭建

为了快速搭建环境,并进行集群创建和数据导入,我们使用单节点集群(1FE、1BE)并按照以下步骤进行操作:

  1. 搭建 Apache Doris :具体操作请参考:快速开始

  2. 创建数据表:按照下列建表语句进行数据表创建

CREATE TABLE `amazon_reviews` (  
  `review_date` int(11) NULL,  
  `marketplace` varchar(20) NULL,  
  `customer_id` bigint(20) NULL,  
  `review_id` varchar(40) NULL,
  `product_id` varchar(10) NULL,
  `product_parent` bigint(20) NULL,
  `product_title` varchar(500) NULL,
  `product_category` varchar(50) NULL,
  `star_rating` smallint(6) NULL,
  `helpful_votes` int(11) NULL,
  `total_votes` int(11) NULL,
  `vine` boolean NULL,
  `verified_purchase` boolean NULL,
  `review_headline` varchar(500) NULL,
  `review_body` string NULL
) ENGINE=OLAP
DUPLICATE KEY(`review_date`)
COMMENT 'OLAP'
DISTRIBUTED BY HASH(`review_date`) BUCKETS 16
PROPERTIES (
"replication_allocation" = "tag.location.default: 1",
"compression" = "ZSTD"
);

3.下载数据集:从下方链接分别下载数据集,数据集为 Parque 格式,并经过 Snappy 压缩,总大小约为 37GB

4.导入数据集:下载完成后,分别执行以下命令,导入数据集

curl --location-trusted -u root: -T amazon_reviews_2010.snappy.parquet -H "format:parquet" http://${BE_IP}:${BE_PORT}/api/${DB}/amazon_reviews/_stream_load
curl --location-trusted -u root: -T amazon_reviews_2011.snappy.parquet -H "format:parquet" http://${BE_IP}:${BE_PORT}/api/${DB}/amazon_reviews/_stream_load
curl --location-trusted -u root: -T amazon_reviews_2012.snappy.parquet -H "format:parquet" http://${BE_IP}:${BE_PORT}/api/${DB}/amazon_reviews/_stream_load
curl --location-trusted -u root: -T amazon_reviews_2013.snappy.parquet -H "format:parquet" http://${BE_IP}:${BE_PORT}/api/${DB}/amazon_reviews/_stream_load
curl --location-trusted -u root: -T amazon_reviews_2014.snappy.parquet -H "format:parquet" http://${BE_IP}:${BE_PORT}/api/${DB}/amazon_reviews/_stream_load
curl --location-trusted -u root: -T amazon_reviews_2015.snappy.parquet -H "format:parquet" http://${BE_IP}:${BE_PORT}/api/${DB}/amazon_reviews/_stream_load

5.查看与验证:完成上述步骤后,可以在 MySQL 客户端执行以下语句,来查看导入的数据行数和所占用空间。从下方代码可知:共导入 135589433 行数据,在 Doris 中占用空间 25.873GB,比压缩后的 Parquet 列式存储进一步降低了 30%。

mysql> SELECT COUNT() FROM amazon_reviews;
+-----------+
| count(*)  |
+-----------+
| 135589433 |
+-----------+
1 row in set (0.02 sec)
mysql> SHOW DATA FROM amazon_reviews;
+----------------+----------------+-----------+--------------+-----------+------------+
| TableName      | IndexName      | Size      | ReplicaCount | RowCount  | RemoteSize |
+----------------+----------------+-----------+--------------+-----------+------------+
| amazon_reviews | amazon_reviews | 25.873 GB | 16           | 135589433 | 0.000      |
|                | Total          | 25.873 GB | 16           |           | 0.000      |
+----------------+----------------+-----------+--------------+-----------+------------+
2 rows in set (0.00 sec)

文本搜索查询加速

无索引硬匹配

环境及数据准备就绪后,我们尝试对 review_body 列进行文本搜索查询。具体需求是在数据集中查出评论中包含“is super awesome”关键字的前 5 种产品,并按照评论数量降序排列,查询结果需显示每种产品的 ID、随机一个产品标题、平均星级评分以及评论总数。review_body 列的特征是评论内容比较长,因此进行文本搜索会有一定的性能压力。

首先我们直接进行查询,以下是查询的示例语句:

SELECT
    product_id,
    any(product_title),
    AVG(star_rating) AS rating,
    COUNT() AS count
FROM
    amazon_reviews
WHERE
    review_body LIKE '%is super awesome%'
GROUP BY
    product_id
ORDER BY
    count DESC,
    rating DESC,
    product_id
LIMIT 5;

执行结果如下,查询耗时为 7.6 秒

+------------+------------------------------------------+--------------------+-------+
| product_id | any_value(product_title)                 | rating             | count |
+------------+------------------------------------------+--------------------+-------+
| B00992CF6W | Minecraft                                | 4.8235294117647056 |    17 |
| B009UX2YAC | Subway Surfers                           | 4.7777777777777777 |     9 |
| B00DJFIMW6 | Minion Rush: Despicable Me Official Game |              4.875 |     8 |
| B0086700CM | Temple Run                               |                  5 |     6 |
| B00KWVZ750 | Angry Birds Epic RPG                     |                  5 |     6 |
+------------+------------------------------------------+--------------------+-------+
5 rows in set (7.60 sec)

利用 Ngram BloomFilter 索引加速查询

接下来,我们尝试使用 Ngram BloomFilter 索引进行查询加速

ALTER TABLE amazon_reviews ADD INDEX review_body_ngram_idx(review_body) USING NGRAM_BF PROPERTIES("gram_size"="10", "bf_size"="10240");

添加 Ngram BloomFilter 索引之后,再次执行相同的查询。执

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SelectDB技术团队

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值