天天看點

文字檢索性能提升 40 倍,Apache Doris 反向索引深度解讀

作者:SelectDB

在 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。以下為資料集的樣例:

文字檢索性能提升 40 倍,Apache Doris 反向索引深度解讀

在子集中,每行包含使用者 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 :具體操作請參考:快速開始
  1. 建立資料表:按照下列建表語句進行資料表建立
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

  • amazon_reviews_2010
  • amazon_reviews_2011
  • amazon_reviews_2012
  • amazon_reviews_2013
  • amazon_reviews_2014
  • amazon_reviews_2015

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 索引之後,再次執行相同的查詢。執行結果如下,查詢耗時縮短至 0.93 秒,相較于未開啟索引,查詢效率提高了 8 倍。

+------------+------------------------------------------+--------------------+-------+
| 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 (0.93 sec)
           

接下來,我們根據代碼示例展開說明。使用 ALTER TABLE 語句為表增加 Ngram BloomFilter 索引時,gram_size 和 bf_size 參數具有特定的含義:

  • gram_size:表示 n-gram 中的 n 值,即連續字元的長度。在上述代碼示例中,"gram_size"="10" 表示每個 n-gram 包含 10 個字元。這意味着文本将被切割成數個字元長度為 10 的字元串,這些字元串将用于建構索引。
  • bf_size:表示 Bloom Filter 的大小,以位元組(Byte)為機關。例如,"bf_size"="10240"表示所使用 Bloom Filter 資料大小占用空間為 10240 位元組。

在了解基本的參數定義後,我們來探索 Ngram BloomFilter 加速查詢的原理:

  • Ngram 分詞:使用 gram_size 對每行資料進行分詞,當 gram_size=5 時,"hello world" 被切分為 ["hello", "ello ", "llo w", "lo wo", "o wor", " worl", "world"]。這些子字元串經過哈希函數計算後,将被添加到相應大小(bf_size)的 Bloom Filter 中。由于 Doris 資料是按頁面(page)組織存儲,相應的 Bloom Filter 也會按頁面(page)生成。
  • 查詢加速:以“hello”為例,在比對過程中也将被切分并生成對應的 Bloom Filter,用于與各頁面的 Bloom Filter 進行對比。如果 Bloom Filter 判斷為包含比對字元串(可能會出現假陽性),則加載相應的頁面以進一步比對;否則,将跳過該頁面。其原理即通過跳過不需要加載的頁面(page),減少需要掃描的資料量,進而顯著降低了查詢延時。
文字檢索性能提升 40 倍,Apache Doris 反向索引深度解讀
文字檢索性能提升 40 倍,Apache Doris 反向索引深度解讀

通過上述原理描述可以看出,針對不同的場景合理的配置 Ngram BloomFilter 的參數會達到更好的效果, gram_size 的大小直接影響比對時效率,而 bf_size 的大小影響存儲容量和誤判率。通常情況下,較大的 bf_size 可以降低誤判率,但這樣也會占用更多的存儲空間。是以,我們建議從以下兩方面綜合考量配置參數:

資料特性: 考慮要索引的資料類型。對于文本資料,需要根據文本的平均長度和字元分布來确定。

  • 對于較短的文本(如單詞或短語):較小的 gram_size(例如 2-4)和較小的 bf_size 可能更合适。
  • 對于較長的文本(如句子或大段描述:較大的 gram_size(例如 5-10)和較大的 bf_size 可能更有效。

查詢模式: 考慮查詢的典型模式。

  • 如果查詢通常包含短語或接近完整的單詞,較大的 gram_size 可能更好。
  • 對于模糊比對或包含多種變化的查詢,較小的 gram_size 可以提供更靈活的比對。

利用反向索引加速查詢

除了采用 Ngram BloomFilter 索引進行查詢加速,還可以選擇基于 反向索引 進一步加速文本搜尋的效率。可以通過以下步驟來建構反向索引:

1.新增反向索引: 對 amazon_reviews 表的 review_body 列添加反向索引,該索引采用英文分詞,并支援 Phrase 短語查詢,短語查詢即進行文本搜尋時,分詞後的詞語順序将會影響搜尋結果。 2.為曆史資料建立索引: 按照新增索引資訊對曆史資料進行索引建構,使曆史資料就也可以使用反向索引進行查詢。

ALTER TABLE amazon_reviews ADD INDEX review_body_inverted_idx(`review_body`) 
    USING INVERTED PROPERTIES("parser" = "english","support_phrase" = "true"); 
BUILD INDEX review_body_inverted_idx ON amazon_reviews;           

3.檢視及驗證: 建構完索引之後,可以通過以下方式對索引建構情況進行檢視:

mysql> show BUILD INDEX WHERE TableName="amazon_reviews";
+-------+----------------+----------------+-----------------------------------------------------------------------------------------------------------------------------------+-------------------------+-------------------------+---------------+----------+------+----------+
| JobId | TableName      | PartitionName  | AlterInvertedIndexes                                                                                                              | CreateTime              | FinishTime              | TransactionId | State    | Msg  | Progress |
+-------+----------------+----------------+-----------------------------------------------------------------------------------------------------------------------------------+-------------------------+-------------------------+---------------+----------+------+----------+
| 10152 | amazon_reviews | amazon_reviews | [ADD INDEX review_body_inverted_idx (
review_body
) USING INVERTED PROPERTIES("parser" = "english", "support_phrase" = "true")],  | 2024-01-23 15:42:28.658 | 2024-01-23 15:48:42.990 | 11            | FINISHED |      | NULL     |
+-------+----------------+----------------+-----------------------------------------------------------------------------------------------------------------------------------+-------------------------+-------------------------+---------------+----------+------+----------+
1 row in set (0.00 sec)
           

如果對分詞效果不确定,可以使用 TOKENIZE 函數進行分詞測試。TOKENIZE 函數接收兩個輸入:一個是需要進行分詞的文本,一個是分詞的屬性字段。

mysql> SELECT TOKENIZE('I can honestly give the shipment and package 100%, it came in time that it was supposed to with no hasels, and the book was in PERFECT condition.
super awesome buy, and excellent for my college classs', '"parser" = "english","support_phrase" = "true"');
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| tokenize('I can honestly give the shipment and package 100%, it came in time that it was supposed to with no hasels, and the book was in PERFECT condition. super awesome buy, and excellent for my college classs', '"parser" = "english","support_phrase" = "true"')                                              |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ["i", "can", "honestly", "give", "the", "shipment", "and", "package", "100", "it", "came", "in", "time", "that", "it", "was", "supposed", "to", "with", "no", "hasels", "and", "the", "book", "was", "in", "perfect", "condition", "super", "awesome", "buy", "and", "excellent", "for", "my", "college", "classs"] |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.05 sec)
           

在反向索引建立完成後,我們使用 MATCH_PHRASE 來查詢包含關鍵詞"is super awesome"的産品評論資訊(具體需求可回顧前文)。

SELECT
    product_id,
    any(product_title),
    AVG(star_rating) AS rating,
    COUNT() AS count
FROM
    amazon_reviews
WHERE
    review_body MATCH_PHRASE 'is super awesome'
GROUP BY
    product_id

ORDER BY
    count DESC,
    rating DESC,
    product_id
LIMIT 5;
           

以上述代碼示例進行說明,review_body MATCH_PHRASE 'is super awesome' 表示對 review_body 列進行短語比對查詢。具體而言,查詢會在 review_body 中按照英文分詞後,尋找同時包含 "is"、"super" 和 "awesome" 這三個詞語的文本片段,同時要求這三個詞語的順序是 "is" 在前,"super" 在中間,"awesome" 在後,并且詞語之間沒有間隔(不區分大小寫)。

這裡需要說明的是,MATCH 與 LIKE 查詢的差異在于,MATCH 查詢時會忽略大小寫,把句子切分成一個個詞來比對,能夠更快速定位符合條件的結果,特别是在大規模資料集情況下,MATCH 的效率提升更為明顯。

執行結果如下所示,開啟反向索引後查詢耗時僅 0.19 秒,性能較僅開啟 Ngram BloomFilter 索引時提升了 4 倍,較未開啟索引時提升了近 40 倍,極大幅度提升了文字檢索的效率。

+------------+------------------------------------------+-------------------+-------+
| product_id | any_value(product_title)                 | rating            | count |
+------------+------------------------------------------+-------------------+-------+
| B00992CF6W | Minecraft                                | 4.833333333333333 |    18 |
| B009UX2YAC | Subway Surfers                           |               4.7 |    10 |
| B00DJFIMW6 | Minion Rush: Despicable Me Official Game |                 5 |     7 |
| B0086700CM | Temple Run                               |                 5 |     6 |
| B00KWVZ750 | Angry Birds Epic RPG                     |                 5 |     6 |
+------------+------------------------------------------+-------------------+-------+
5 rows in set (0.19 sec)
           

究其加速原因可知,反向索引是通過将文本分解為單詞,并建立從單詞到行号清單的映射。這些映射關系按照單詞進行排序,并建構跳表索引。在查詢特定單詞時,可以通過跳表索引和二分查找等方法,在有序的映射中快速定位到對應的行号清單,進而擷取行的内容。這種查詢方式避免了逐行比對,将算法複雜度從 O(n) 降低到 O(logn),在處理大規模資料時能顯著提高查詢性能。

文字檢索性能提升 40 倍,Apache Doris 反向索引深度解讀

為深入了解反向索引的加速原理,需從反向索引内部引讀寫邏輯說起。在 Doris 中,從邏輯角度來看,反向索引應用于表的列級别,而從實體存儲和實作角度來看,反向索引實際是建立在資料檔案級别上的。具體如下:

  • 寫入階段: 資料在寫入資料檔案的同時,也将同步寫入排索引檔案中,對于每個寫入資料的行号,均與反向索引中的行号一一對應的。
  • 查詢階段: 如果查詢 WHERE 條件中包含已建立反向索引的列,Doris 會自動查詢索引檔案,傳回滿足條件的行号清單,再利用 Doris 通用的行号過濾機制,跳過不必要的行和頁面,隻讀取滿足條件的行,以達到查詢加速的效果。

總的來說,Doris 的反向索引機制在實體層面是通過資料檔案和索引檔案配合工作,而在邏輯層面則通過列和行的映射來實作高效的資料檢索和查詢加速。

非主鍵列查詢加速

為了進一步驗證反向索引對非主鍵列查詢加速的影響,我們選擇對産品 ID 和使用者 ID 的次元資訊進行查詢。

未開啟反向索引

當查詢使用者 13916588 對産品 B002DMK1R0 的評論資訊時,執行以下 SQL 語句進行查詢時,需要對全表資料進行掃描,查詢耗時為 1.81 秒。

mysql> SELECT product_title,review_headline,review_body,star_rating 
FROM amazon_reviews 
WHERE product_id='B002DMK1R0' AND customer_id=13916588;
+-----------------------------------------------------------------+----------------------+-----------------------------------------------------------------------------------------------------------------------------+-------------+
| product_title                                                   | review_headline      | review_body                                                                                                                 | star_rating |
+-----------------------------------------------------------------+----------------------+-----------------------------------------------------------------------------------------------------------------------------+-------------+
| Magellan Maestro 4700 4.7-Inch Bluetooth Portable GPS Navigator | Nice Features But... | This is a great GPS. Gets you where you are going. Don't forget to buy the seperate (grr!) cord for the traffic kit though! |           4 |
+-----------------------------------------------------------------+----------------------+-----------------------------------------------------------------------------------------------------------------------------+-------------+
1 row in set (1.81 sec)
           

反向索引查詢加速

接下來,我們為 product_id 和 customer_id 添加反向索引。在這個場景中,反向索引的使用與文本搜尋時不同,該場景無需對 product_id 和 customer_id 進行分詞,隻需對這兩列的 Value→RowID 的建立倒排映射表。

首先,通過執行以下 SQL 語句建立反向索引:

ALTER TABLE amazon_reviews ADD INDEX product_id_inverted_idx(product_id) USING INVERTED ;
ALTER TABLE amazon_reviews ADD INDEX customer_id_inverted_idx(customer_id) USING INVERTED ;
BUILD INDEX product_id_inverted_idx ON amazon_reviews;
BUILD INDEX customer_id_inverted_idx ON amazon_reviews;
           

其次,當索引建構完成後,執行同樣的查詢語句,查詢耗時從 1.81 秒降到了 0.06 秒,查詢耗時顯著降低,相比未添加索引的情況,查詢效率提升了約 30 倍。

mysql> SELECT product_title,review_headline,review_body,star_rating FROM amazon_reviews WHERE product_id='B002DMK1R0' AND customer_id='13916588';
+-----------------------------------------------------------------+----------------------+-----------------------------------------------------------------------------------------------------------------------------+-------------+
| product_title                                                   | review_headline      | review_body                                                                                                                 | star_rating |
+-----------------------------------------------------------------+----------------------+-----------------------------------------------------------------------------------------------------------------------------+-------------+
| Magellan Maestro 4700 4.7-Inch Bluetooth Portable GPS Navigator | Nice Features But... | This is a great GPS. Gets you where you are going. Don't forget to buy the seperate (grr!) cord for the traffic kit though! |           4 |
+-----------------------------------------------------------------+----------------------+-----------------------------------------------------------------------------------------------------------------------------+-------------+
1 row in set (0.06 sec)           

通過觀察可發現,反向索引在于類似非主鍵列的次元查詢中具有非常出色的加速效果。為更深入且直覺的檢視加速效果,可通過 Doris Profile 資訊來進一步探索。

Profile 分析

需要注意的是,在開啟查詢的 Profile 之前,需先在 MySQL 用戶端執行 SET enable_profile=true; 指令。完成後再執行查詢語句,并通路 http://FE_IP:FE_HTTP_PORT/QueryProfile, 來檢視與本次查詢相關的 Profile ID 以及詳細的 Profile 資訊。

本文中僅截取一個特定片段的 SegmentIterator Profile 資訊來說明反向索引查詢加速原因。

SegmentIterator:
  - FirstReadSeekCount: 0
  - FirstReadSeekTime: 0ns
  - FirstReadTime: 13.119ms
  - IOTimer: 19.537ms
  - InvertedIndexQueryTime: 11.583ms
  - RawRowsRead: 1
  - RowsConditionsFiltered: 0
  - RowsInvertedIndexFiltered: 16.907403M (16907403)
  - RowsShortCircuitPredInput: 0
  - RowsVectorPredFiltered: 0
  - RowsVectorPredInput: 0
  - ShortPredEvalTime: 0ns
  - TotalPagesNum: 27
  - UncompressedBytesRead: 3.71 MB
  - VectorPredEvalTime: 0ns           

從上述 Profile 中的 RowsInvertedIndexFiltered: 16.907403M (16907403)以及RawRowsRead: 1,我們可以觀察到:反向索引過濾了 16907403 行資料,最終隻保留 1 行資料(即命中的那條資料)。根據 FirstReadTime: 13.119ms 可知,在讀取這行資料所在的頁面(page)耗時 13.119 ms,而根據InvertedIndexQueryTime: 11.583ms 可知,反向索引執行時間僅耗時 11.58 ms。這意味着反向索引僅在 11.58 ms 内過濾了 16907403 行資料,執行效率非常高。

為更直接對比,接下來展示未增加反向索引情況下 SegmentIterator 的執行情況:

SegmentIterator:
  - FirstReadSeekCount: 9.374K (9374)
  - FirstReadSeekTime: 400.522ms
  - FirstReadTime: 3s144ms
  - IOTimer: 2s564ms
  - InvertedIndexQueryTime: 0ns
  - RawRowsRead: 16.680706M (16680706)
  - RowsConditionsFiltered: 226.698K (226698)
  - RowsInvertedIndexFiltered: 0
  - RowsShortCircuitPredInput: 1
  - RowsVectorPredFiltered: 16.680705M (16680705)
  - RowsVectorPredInput: 16.680706M (16680706)
  - RowsZonemapFiltered: 226.698K (226698)
  - ShortPredEvalTime: 2.723ms
  - TotalPagesNum: 5.421K (5421)
  - UncompressedBytesRead: 277.05 MB
  - VectorPredEvalTime: 8.114ms
           

根據上述 Profile 觀察可知,由于沒有索引進行過濾, FirstRead 需要花費 3.14s 的時間來加載 16680706 行資料,然後使用 Predicate Evaluate 進行條件過濾,過濾掉其中 16680705 行,而條件過濾本身隻消耗了不到 10ms 的時間,由此可見,大部分時間被消耗在加載原始資料上。

通過對比可知,建立反向索引可以大大減少加載原始資料的時間,提高查詢的執行效率。索引能夠快速定位滿足條件的行,進而減少不必要的資料加載和處理,節省時間和資源。

低基數文本列索引加速

衆所周知,反向索引對于高基數文本列的查詢來說,加速效果十分顯著。然而,在低基數列的情況下,可能由于需建立過多的索引項而導緻更大的開銷,進而對查詢性能産生負面影響。接下來,我們将以 product_category 作為謂詞列進行過濾,來檢驗 Apache Doris 反向索引在低基數文本列的加速效果如何。

mysql> SELECT COUNT(DISTINCT product_category) FROM amazon_reviews ;
+----------------------------------+
| count(DISTINCT product_category) |
+----------------------------------+
|                               43 |
+----------------------------------+
1 row in set (0.57 sec)
           

通過上述操作可知,到 product_category 僅有 43 種分類,是一個典型的低基數文本列。接下來,我們對其增加反向索引

ALTER TABLE amazon_reviews ADD INDEX product_category_inverted_idx(`product_category`) USING INVERTED;
BUILD INDEX product_category_inverted_idx ON amazon_reviews;           

添加反向索引之後,運作如下 SQL 查詢,指查詢産品分類為 Mobile_Electronics 産品中評價數量最多的前三名産品資訊

SELECT 
    product_id,
    product_title,
    AVG(star_rating) AS rating,
    any(review_body),
    any(review_headline),
    COUNT(*) AS count 
FROM 
    amazon_reviews 
WHERE 
    product_category = 'Mobile_Electronics' 
GROUP BY 
    product_title, product_id 
ORDER BY 
    count DESC 
LIMIT 10;
           

從下方結果可知,增加反向索引之後,查詢耗時為 1.54s。

+------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------------------+-------+
| product_id | product_title                                                                                                                                                                                          | rating             | any_value(review_body)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | any_value(review_headline)      | count |
+------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------------------+-------+
| B00J46XO9U | iXCC Lightning Cable 3ft, iPhone charger, for iPhone X, 8, 8 Plus, 7, 7 Plus, 6s, 6s Plus, 6, 6 Plus, SE 5s 5c 5, iPad Air 2 Pro, iPad mini 2 3 4, iPad 4th Gen [Apple MFi Certified](Black and White) | 4.3766233766233764 | Great cable and works well. Exact fit as Apple cable. I would recommend this to anyone who is looking to save money and for a quality cable.                                                                                                                                                                                                                                                                                                                                                             | Apple certified lightning cable |  1078 |
| B004911E9M | Wall AC Charger USB Sync Data Cable for iPhone 4, 3GS, and iPod                                                                                                                                        | 2.4281805745554035 | A total waste of money for me because I needed it for a iPhone 4.  The plug will only go in upside down and thus won't work at all.                                                                                                                                                                                                                                                                                                                                                                      | Won't work with a iPhone 4!     |   731 |
| B002D4IHYM | New Trent Easypak 7000mAh Portable Triple USB Port External Battery Charger/Power Pack for Smartphones, Tablets and more (w/built-in USB cable)                                                        | 4.5216095380029806 | I bought this product based on the reviews that i read and i am very glad that i did. I did have a problem with the product charging my itouch after i received it but i emailed the company and they corrected the problem immediately. VERY GOOD customer service, very prompt. The product itself is very good. It charges my power hungry itouch very quickly and the imax battery power lasts for a long time. All in all a very good purchase that i would recommend to anyone who owns an itouch. | Great product & company         |   671 |
+------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------------------+-------+
3 rows in set (1.54 sec)
           

接下來,我們關閉反向索引,以觀察未加反向索引時的查詢耗時。這裡需要說明的是,當需要關閉索引或在增加索引後發現效果不理想,可以在 MySQL 用戶端中執行 set enable_inverted_index_query=false;,便捷且快速地臨時關閉反向索引。我們再次運作查詢 SQL,如下所示,查詢耗時為 1.8s。

+------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------+-------+
| product_id | product_title                                                                                                                                                                                          | rating             | any_value(review_body)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | any_value(review_headline)            | count |
+------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------+-------+
| B00J46XO9U | iXCC Lightning Cable 3ft, iPhone charger, for iPhone X, 8, 8 Plus, 7, 7 Plus, 6s, 6s Plus, 6, 6 Plus, SE 5s 5c 5, iPad Air 2 Pro, iPad mini 2 3 4, iPad 4th Gen [Apple MFi Certified](Black and White) | 4.3766233766233764 | These cables are great. They feel quality, and best of all, they work as they should. I have no issues with them whatsoever and will be buying more when needed.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | Just like the original from Apple     |  1078 |
| B004911E9M | Wall AC Charger USB Sync Data Cable for iPhone 4, 3GS, and iPod                                                                                                                                        | 2.4281805745554035 | I ordered two of these chargers for an Iphone 4. Then I started experiencing weird behavior from the touch screen. It would select the wrong area of the screen, or it would refuse to scroll beyond a certain point and jump back up to the top of the page. This behavior occurs whenever either of the two that I bought are attached and charging. When I remove them, it works fine once again. Needless to say, these items are being returned.                                                                                                                                                                                                                                                                                                                                                                              | Beware - these chargers are defective |   731 |
| B002D4IHYM | New Trent Easypak 7000mAh Portable Triple USB Port External Battery Charger/Power Pack for Smartphones, Tablets and more (w/built-in USB cable)                                                        | 4.5216095380029806 | I received this in the mail 4 days ago, and after charging it for 6 hours, I've been using it as the sole source for recharging my 3Gs to see how long it would work.  I use my Iphone A LOT every day and usually by the time I get home it's down to 50% or less.  After 4 days of using the IMAX to recharge my Iphone, it finally went from 3 bars to 4 this afternoon when I plugged my iphone in.  It charges the iphone very quickly, and I've been topping my phone off (stopping around 95% or so) twice a day.  This is a great product and the size is very similar to a deck of cards (not like an iphone that someone else posted) and is very easy to carry in a jacket pocket or back pack.  I bought this for a 4 day music festival I'm going to, and I have no worries at all of my iphone running out of juice! | FANTASTIC product!                    |   671 |
+------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------+-------+
3 rows in set (1.80 sec)
           

綜上可知,反向索引對于低基數列場景也有 15% 的查詢性能提升,雖不如高基數列場景的提升效果,但并未産生退化效果或負面影響。此外,Apache Doris 針對低基數列采用了較好的編碼(如字典編碼)方式和壓縮技術,并且可以通過内置索引(如 zonemap)進行有效過濾。是以,即使不添加反向索引仍能展現較好的查詢效果。

總結語

總而言之,Apache Doris 中的反向索引顯著優化了針對謂詞列的過濾操作,即 SQL 查詢中的 Where 子句。通過精确比對行号,減少了存儲層需要掃描的資料量,進而提高了查詢性能。即使在性能提升有限的情況下,反向索引也不會對查詢效率産生負面影響。此外,反向索引還支援輕量級的索引管理操作,如對增加或删除索引(ADD/DROP INDEX)以及建構索引(BUILD INDEX)操作進行管理。同時,還提供了在 MySQL 用戶端便捷地啟用或關閉索引(enable_inverted_index_query=true/false)的功能,使使用者能夠輕松利用反向索引來檢驗查詢加速效果。

反向索引和 NGram Bloom Filter 索引為不同場景提供了查詢加速方案,在選擇索引類型時,資料集的特定特征和查詢模式是關鍵考慮因素。以下是一些常見的适配場景:

  • 大規模資料非主鍵列點查場景: 在這種場景下,往往存在大量分散的數值列在值,且查詢的值命中量很低。為了加速查詢,除了在建表時利用 Doris 内置的智能索引能力之外,還可以通過給對應的列增加反向索引來加速查詢。反向索引對字元類型、數值類型、日期等标量類型支援比較完整。
  • 短文本列的文字檢索場景: 如果短文本分布比較離散(即文本之間相似度低),則适合使用 Ngram Bloom Filter 索引,能夠有效地處理短文本的模糊比對查詢(LIKE)。同時,在短文本場景下 Apache Doris 的向量化處理能力可以得到更加充分和高效的應用和發揮。如果短文本分布比較集中(如大量文本相似,少量文本不同),則适合使用倒排分詞索引,這樣可以保證詞典比較小,适合快速檢索擷取行号清單。
  • 長文本列的文本搜尋場景: 針對長文本列,倒排分詞索引是更好的方案。相比于暴力字元串比對,反向索引提供了更高效的查詢性能,避免了大量的 CPU 資源消耗。

自 Apache Doris 最早引入反向索引至今已有近一年時間,從 早期 2.0 Preview 版本至最近釋出的 2.0.4,這一年間經曆了大量開源使用者在真實業務環境海量資料下的打磨和驗證,性能與穩定性已經得到充分驗證。而在後續的規劃中,我們也将持續在現有基礎上進行疊代和優化,包括:

  • 自定義反向索引分詞能力, 針對使用者在不同場景下分詞效果的需求,提供使用者對自定義分詞器。
  • 支援更多類型的反向索引, 後續會增加對 Array、Map 等複雜資料類型的支援,以更全面地滿足各類查詢需求。