全部產品
Search
文件中心

MaxCompute:Bloomfilter index(Beta)

更新時間:Feb 15, 2025

布隆過濾器(Bloomfilter,簡稱BF)是一種高效的機率型資料結構,MaxCompute支援使用Bloomfilter index處理大規模資料點查情境,減少查詢過程中不必要的資料掃描,從而提高整體的查詢效率和效能。本文為您介紹Bloomfilter index的使用說明及樣本。

背景資訊

大規模點查是一種常見的數倉使用情境,通常會通過指定不定列的值對大量資料進行檢索,得到條件匹配的結果。在巨量資料情境下,結果資料可能分散在海量的檔案中,因此高效能的大規模點查需要極強的檢索能力。

MaxCompute是按照表或分區讀取資料,儲存層謂詞下推能力會根據表或分區下檔案的本地中繼資料資訊(參考ANALYZE)過濾資料,如果資料分散,則根據本地中繼資料資訊過濾資料(例如基於某列的MIN或MAX值進行過濾)的效果就比較有限。

如果查詢的列是固定的,可以使用聚簇表,並將Clustering Key作為過濾條件,這樣可以快速排除掉不需要讀取的分桶,在分桶內也可以過濾掉不需要讀取的資料,加快查詢速度。如果在條件欄位上彙總或者和其他表進行關聯等操作,可以利用聚簇附帶的Shuffle Removal能力,進一步發揮聚簇表優勢,但是聚簇表在以下幾方面仍有不足:

  • 對於Hash聚簇表,只有當查詢條件包含了所有的Clustering Key之後才能進行資料過濾;對於Range聚簇表,只有當查詢條件中包含了Clustering Key的首碼過濾條件,並且按照Clustering Key的順序從左至右進行匹配時,才能有較好的過濾效果,如果不包含首碼過濾條件則效果不佳。

  • 如果查詢條件不包含Clustering Key,則沒有過濾效果,因而對於查詢無固定條件的表來說,聚簇表可能無效。

  • 資料寫入時需要按照指定的欄位進行Shuffle,會導致成本增加,如果遇到個別傾斜的Key,會導致任務長尾。

因此MaxCompute引入布隆過濾器索引(Bloomfilter index)應對大規模資料點查的情境。

前提條件

  • 已建立MaxCompute專案。具體操作請參見建立MaxCompute專案。

  • 專案已支援Schema Evolution操作。若您的專案未支援Schema Evolution操作,需要在專案層級設定setproject odps.schema.evolution.enable=true;,以確保後續操作成功執行。否則會出現類似於Failed to run ddltask - Schema evolution DDLs is not enabled in project:default的報錯。

功能介紹

點查本質上是檢索某一個元素是否存在於一個集合中,而Bloomfilter可以用極高的效率實現該目標,因而在資料庫以及資料湖技術中都引入了Bloomfilter index,以便支援更細粒度的資料或檔案裁剪。

不同於資料庫中的索引(如BTree、RTree等)用來具體定位到某一行記錄,巨量資料下基於索引構建、維護代價的考慮,更多的是引入更輕量級的索引,而空間效率和查詢效率都非常高的Bloomfilter很適合在點查情境進行檔案的裁剪,因此MaxCompute中也引入了該索引。

相對聚簇表,Bloomfilter index的優勢如下:

  • 高效。能以極小的代價過濾無效資料。

  • 擴充性高。可以對錶的一列或者多列建立BF索引,並且可以和聚簇索引配合使用,即可以對非Clustering Key建立BF索引。

  • 在高基數、資料分布緊湊的情境下有很好的過濾效果。

Bloomfilter index的優缺點

  • 優點

    • 效率高,插入和查詢操作的資源消耗都比普通索引低。

    • 節省空間的,採用位元組,232=4294967296,可以看到42億長度的位元組只需佔用512 MB的記憶體空間。

      說明

      位元組佔用的記憶體空間計算方式:4294967296/8/1024/1024=512 MB。

  • 缺點

    有一定誤判率,可能會把不屬於該集合的元素誤判為存在於該集合,但是對大多數情境而言,消耗一點資源讀取無資料的檔案並不影響整體效率,且結果不影響最終的業務準確性。

Bloomfilter index適用情境

  • 以表中的一列或多列作為條件進行點查過濾,則在有明顯過濾效果的查詢列上構建Bloomfilter index。

  • 對聚簇表Clustering Key之外的欄位進行點查過濾,則在查詢列上構建Bloomfilter index。

  • 聚簇表的Clustering Key、Sorted Key、Zorder函數排序後,在插入的資料列上再構建Bloomfilter index,可以獲得進一步的過濾效果。

Bloomfilter index使用限制

  • 只適合等值判斷(包括=、in),不支援>、>=、<、<=等範圍查詢及IS NULL、IS NOT NULL查詢。

  • 過濾效果的好壞依賴於資料分布,如果資料分布比較離散,即使使用Bloomfilter index,也可能無法有效地過濾資料。

  • 不支援在DECIMAL、INTERVAL_DAY_TIME、INTERVAL_YEAR_MONTH,或複雜類型STRUCT、MAP、ARRAY、JSON上建立Bloomfilter index。

費用說明

  • 索引構建後會佔用儲存空間,並且是由儲存在Pangu上的儲存計量進行統計,因此這部分計量按普通儲存收費。

  • 索引的構建和使用會觸發額外的計算任務,一次查詢首先基於索引檔案計算查詢所需資料所在的檔案資訊,然後基於索引計算結果,進一步在查詢執行計畫階段過濾減少輸入資料量,並最終計算得到結果。索引計算和查詢都會產生計算費用。

  • 使用索引,可以讓預付費客戶的作業運行得更快,同時,構建和運行索引計算的資源也是基於使用者的預付費資源,所以商業化不會對預付費作業的費用產生影響。

  • SQL操作觸發的索引相關任務說明如下:

    SQL操作

    觸發任務

    索引相關任務的輸入資料量

    計費情況

    CREATE

    DDL操作不會觸發索引構建任務。

    無

    不收費

    REBUILD

    索引構建。

    • 已有索引:執行REBUILD操作會觸發索引重建。

    • 無現有索引:執行REBUILD操作會觸發索引建立。

    由於重建索引時,索引列和其他列同時進行讀寫操作,所以輸入資料量為重建索引查詢條件過濾後的全量資料。

    • 索引計費方式:SQL後付費單價*複雜度1*索引相關任務輸入資料量。

    • SQL查詢計費方式:採用正常SQL後付費邏輯。

    INSERT

    • 對新插入的資料構建索引。

    • SELECT部分的查詢。

    SELECT查詢的表中,帶有索引的列的資料量。

    SELECT

    • 使用索引計算的查詢任務,輸出下一步SELECT部分用於過濾資料的資訊。

    • 基於已構建的索引執行SELECT查詢。

    索引檔案的資料量。

使用說明

產生Bloomfilter index、使用Bloomfilter index、查看Bloomfilter index、刪除Bloomfilter index及更改Bloomfilter index屬性的方法如下:

產生Bloomfilter index

建立Bloomfilter index

文法如下:

CREATE BLOOMFILTER INDEX <index_name>
ON TABLE <table_name>
FOR COLUMNS(<column_name>)
IDXPROPERTIES('numitems'='xxx', 'fpp' = 'xx')
[COMMENT 'idxcomment']
;

參數說明如下:

參數

描述

index_name

指定的索引名稱。

table_name

索引所在的表名。

column_name

要建立索引的列名。

numitems

Bloomfilter中儲存的元素數量的預估值,用於指定Bloomfilter的容量大小,以便在建立時分配足夠的記憶體空間來儲存預期數量的元素。該設定會影響Bloomfilter中使用的總位元,對過濾品質很重要。

  • 若設定的值過大,會導致Bloomfilter的位元組填充非常稀疏,浪費磁碟空間並導致查詢效能下降。

  • 若設定的值過小,則會導致Bloomfilter的位元組填充得太滿,增加Bloomfilter的誤判率(FPP較高)。

該值必須大於0,您可根據構建索引列的非重複值數量進行預估,最大不能超過1000萬。

fpp

誤判率。取值範圍為(0,1],該值越小,BF的準確性越高,但佔用的儲存也越大,建議值為0.1。

說明

目前僅支援一次對錶中的一個列建立Bloomfilter index,您可以為表的多個列分別建立Bloomfilter index。

彙總Bloomfilter index

對於增量資料不需要任何額外操作,執行資料的插入語句即可彙總Bloomfilter index。

文法如下:

INSERT OVERWRITE TABLE <table_name> [PARTITION <partition_spec>]
SELECT ......

參數說明如下:

  • table_name:索引所在的表名。

  • pt_spec:需要插入資料的分區資訊,不允許使用函數等運算式,只能是常量。格式為(partition_col1 = partition_col_value1, partition_col2 = partition_col_value2, ...)。

在構建了Bloomfilter index的表中插入新資料,會增量產生資料檔案的本地Bloomfilter索引資料,支援儲存層謂詞下推,然後啟動BloomfilterAutoMergeTask自動彙總成新的Bloomfilter index索引檔案,支援在planning階段進行過濾,更準確地啟動任務所需資源。此時Logview中Json Summary出現如下關鍵字則說明Bloomfilter index彙總成功:image

此時,您可通過Logview中的SubStatusHistory查看Bloomfilter index彙總所耗費的時間:image

說明
  • 無論Bloomfilter index彙總任務成功與否,寫入資料的任務都會正常完成。

  • Bloomfilter index因為系統原因彙總失敗,會導致新增資料無法在planning階段過濾資料,新增資料的本地Bloomfilter索引資料仍然能夠支援儲存層謂詞下推進行過濾。您可以通過執行計畫判斷是否利用到了Bloomfilter index,如果沒有,可以對查詢時索引不生效的分區索引進行REBUILD。

  • 動態分區暫時不支援自動Merge,需要在資料寫入完成後,手動對更新的分區進行REBUILD。

  • 如果上圖中自動Merge任務狀態返回顯示執行失敗,則需要顯式執行如下Merge命令,但注意一次只能對一個分區索引進行REBUILD:

    ALTERTABLE <table_name> [PARTITION <partition_spec>] REBUILD BLOOMFILTER INDEX;

使用Bloomfilter index

在查詢前執行如下語句,啟用布隆過濾器索引功能:

SET odps.sql.enable.bloom.filter.index=true;
說明
  • Bloomfilter index生效後,query中會增加一個job進行檔案裁剪,裁剪後的檔案即為任務需要讀取的資料。

  • 如果沒有設定該參數(Beta發布期間預設值為false,後續會根據線上使用方式將預設值置為true),則無法在planning階段裁剪檔案,但此時仍可以利用儲存層謂詞下推能力,基於Bloomfilter本地索引資料進行資料的過濾。

  • 如果Bloomfilter index的過濾效果不顯著(例如:結果檔案分散在多個檔案中,無法通過裁剪明顯過濾檔案),則設定該參數需要多執行一個索引檢索任務,有可能導致任務效能回退,此時可以置為false。

Bloomfilter index生效的任務樣本如下:

SET odps.sql.enable.bloom.filter.index=true;
SELECT * FROM bloomfilter_index_test WHERE key=392 AND value="val_392";

查看Logview可知,下圖中的job_1即為使用Bloomfilter index進行檔案裁剪的job(稱為index job)。

image

Summary中攜帶bf尾碼的是虛擬表,對應Bloomfilter index檔案。

image

說明

海量資料下產生的BF檔案可能會非常大,因此MaxCompute啟動了一個分布式作業進行檔案的裁剪。

查看錶的Bloomfilter index

SHOW INDEXES ON <table_name>;

參數說明如下。

table_name:索引所在的表名。

刪除Bloomfilter index

DROP INDEX [IF EXISTS] <idx_name> ON TABLE <table_name>;

參數說明如下。

  • idx_name:索引名稱。

  • table_name:索引所在的表名。

更改Bloomfilter index屬性

ALTER INDEX <idx_name> ON <table_name> 
SET IDXPROPERTIES(['comment' = 'a'], ['fpp' = '0.01']);

參數說明如下。

  • idx_name:索引名稱。

  • table_name:索引所在的表名。

使用樣本

樣本1:基於普通分區表建立Bloomfilter index

  1. 資料準備。

    SET odps.namespace.schema=true;
    SELECT * FROM bigdata_public_dataset.TPCDS_10G.call_center;
  2. 建立分區測試表call_center_test。

    CREATE TABLE IF NOT EXISTS call_center_test(
        cc_call_center_sk BIGINT NOT NULL, cc_call_center_id CHAR(16) NOT NULL, 
        cc_rec_start_date DATE, cc_rec_end_date DATE, 
        cc_closed_date_sk BIGINT, cc_open_date_sk BIGINT, 
        cc_name VARCHAR(50), cc_class VARCHAR(50), 
        cc_employees BIGINT, cc_sq_ft BIGINT, 
        cc_hours CHAR(20), cc_manager VARCHAR(40), 
        cc_mkt_id BIGINT, cc_mkt_class CHAR(50), 
        cc_mkt_desc VARCHAR(100), cc_market_manager VARCHAR(40), 
        cc_division BIGINT, cc_division_name VARCHAR(50), 
        cc_company BIGINT, cc_company_name CHAR(50), 
        cc_street_number CHAR(10), cc_street_name VARCHAR(60), 
        cc_street_type CHAR(15), cc_suite_number CHAR(10), 
        cc_city VARCHAR(60), cc_county VARCHAR(30), 
        cc_state CHAR(2), cc_zip CHAR(10), 
        cc_country VARCHAR(20), cc_gmt_offset DECIMAL(5,2), 
        cc_tax_percentage DECIMAL(5,2)
    )
    PARTITIONED BY (ds STRING )
    ;
  3. 建立索引。

    CREATE BLOOMFILTER INDEX call_center_test_idx01 
    ON table call_center_test 
    FOR columns(cc_call_center_sk) 
    IDXPROPERTIES('fpp' = '0.03', 'numitems'='1000000') 
    COMMENT 'cc_call_center_sk index';
  4. 匯入資料。

    將公用資料集中的bigdata_public_dataset.TPCDS_10G.call_center表資料匯入至call_center_test。

    SET odps.namespace.schema=true;
    INSERT OVERWRITE TABLE call_center_test PARTITION (ds='20241115') SELECT * FROM bigdata_public_dataset.TPCDS_10G.call_center LIMIT 10000;

    Logview中Json Summary出現如下關鍵字則說明Bloomfilter index彙總成功:image

  5. 查詢資料。

    SET odps.sql.enable.bloom.filter.index=true;
    SELECT * FROM call_center_test WHERE cc_call_center_sk =10 AND ds='20241115';

    返回結果如下:

    +-------------------+-------------------+-------------------+-----------------+-------------------+-----------------+---------------+------------+--------------+------------+------------+----------------+------------+----------------------------+---------------------------------------------------------------------------------------+-------------------+-------------+------------------+------------+-----------------+------------------+----------------+----------------+-----------------+------------+---------------+------------+------------+---------------+---------------+-------------------+------------+
    | cc_call_center_sk | cc_call_center_id | cc_rec_start_date | cc_rec_end_date | cc_closed_date_sk | cc_open_date_sk | cc_name       | cc_class   | cc_employees | cc_sq_ft   | cc_hours   | cc_manager     | cc_mkt_id  | cc_mkt_class               | cc_mkt_desc                                                                           | cc_market_manager | cc_division | cc_division_name | cc_company | cc_company_name | cc_street_number | cc_street_name | cc_street_type | cc_suite_number | cc_city    | cc_county     | cc_state   | cc_zip     | cc_country    | cc_gmt_offset | cc_tax_percentage | ds         |
    +-------------------+-------------------+-------------------+-----------------+-------------------+-----------------+---------------+------------+--------------+------------+------------+----------------+------------+--------------+-------------+---------------------------------------------------------------------------------------+-------------------+-------------+------------------+------------+-----------------+------------------+----------------+----------------+-----------------+------------+---------------+------------+------------+---------------+---------------+-------------------+------------+
    | 10                | AAAAAAAAKAAAAAAA  | 1998-01-01        | 2000-01-01      | NULL              | 2451050         | Hawaii/Alaska | large      | 187          | 95744      | 8AM-8AM    | Gregory Altman | 2          | Just back responses ought  | As existing eyebrows miss as the matters. Realistic stories may not face almost by a  | James Mcdonald    | 3           | pri              | 3          | pri             | 457              | 1st            | Boulevard      | Suite B         | Midway     | Walker County | AL         | 31904      | United States | -6            | 0.02              | 20241115   |
    +-------------------+-------------------+-------------------+-----------------+-------------------+-----------------+---------------+------------+--------------+------------+------------+----------------+------------+----------------------------+---------------------------------------------------------------------------------------+-------------------+-------------+------------------+------------+-----------------+------------------+----------------+----------------+-----------------+------------+---------------+------------+------------+---------------+---------------+-------------------+------------+

    Logview中顯示如下資訊,表示Bloomfilter index已經生效,攜帶bf尾碼的是虛擬表,對應Bloomfilter index檔案。

    image

  6. 查看錶的Bloomfilter index。

    SHOW INDEXES ON call_center_test;

    返回結果如下:

    ID = 20241115093930589g9biyii****
    {"Indexes": [{
                "id": "aabdaeb10a7b4e99a94716dabad8****",
                "indexColumns": [{"name": "cc_call_center_sk"}],
                "name": "call_center_test_idx01",
                "properties": {
                    "comment": "cc_call_center_sk index",
                    "fpp": "0.03",
                    "numitems": "1000000"},
                "type": "BLOOMFILTER"}]}
    
    OK
  7. 更改Bloomfilter index屬性。

    由上一步可知,原始numitems屬性值為1000000,執行以下命令,將其屬性值更改為10000。

    -- 更改屬性
    ALTER INDEX call_center_test_idx01 ON call_center_test SET IDXPROPERTIES('fpp' = '0.03', 'numitems'='10000');
    
    -- 查看屬性
    SHOW INDEXES ON call_center_test;

    返回結果如下:image

樣本2:基於Hash Cluster分區表建立Bloomfilter index

  1. 資料準備。

    1. 建立scope_tmp表。

      CREATE TABLE if NOT EXISTS scope_tmp(
          phone STRING, 
          card STRING, 
          machine STRING, 
          geohash STRING);
    2. 在MaxCompute用戶端(odpscmd)中使用Tunnel命令向scope_tmp表中上傳資料。以scope2.csv檔案為例,假設scope2.csv檔案位於MaxCompute用戶端(odpscmd)的bin目錄下,Tunnel命令如下:

      Tunnel upload scope2.csv scope_tmp;
  2. 建立分區測試表scope_hash_pt。

    CREATE TABLE scope_hash_pt (
        phone STRING, 
        card STRING, 
        machine STRING,
        geohash STRING 
    )
    PARTITIONED BY (ds STRING)
    clustered by (phone) 
    sorted by (card) 
    into 512 buckets; 
  3. 為分區表scope_hash_pt建立索引。

    CREATE BLOOMFILTER INDEX scope_hash_pt_index01 
    ON TABLE scope_hash_pt 
    FOR columns(card) 
    IDXPROPERTIES('fpp' = '0.03', 'numitems'='1000000') 
    COMMENT 'card index';
  4. 匯入資料。

    INSERT OVERWRITE TABLE scope_hash_pt PARTITION (ds='20241115') SELECT * FROM scope_tmp;

    Logview中Json Summary出現如下關鍵字則說明Bloomfilter index彙總成功:

    image

  5. 查詢scope_hash_pt表的資料。

    SET odps.sql.enable.bloom.filter.index=true;
    SELECT * FROM scope_hash_pt WHERE card='073415764266290' and ds='20241115';

    返回結果如下:

    +-------------+-----------------+----------------+------------+------------+
    | phone       | card            | machine        | geohash    | ds         |
    +-------------+-----------------+----------------+------------+------------+
    | 1576426**** | 073415764266290 | 51133960245770 | fWbDDsf    | 20241115   |
    +-------------+-----------------+----------------+------------+------------+

    Logview中顯示如下資訊,表示Bloomfilter index已經生效,攜帶bf尾碼的是虛擬表,對應Bloomfilter index檔案。

    image

樣本3:基於Zorder資料重分布的分區表建立Bloomfilter index

  1. 資料準備。具體操作請參見Hash BloomFilter資料準備。

  2. 建立分區測試表scope_zorder_pt。

    CREATE TABLE scope_zorder_pt(
        phone STRING, 
        card STRING, 
        machine STRING, 
        geohash STRING, 
        zvalue BIGINT
    )
    PARTITIONED BY (ds STRING)
    ;
  3. 建立索引。

    CREATE BLOOMFILTER INDEX scope_zorder_pt_index01 
    ON TABLE scope_zorder_pt 
    FOR COLUMNS (card) 
    IDXPROPERTIES('fpp' = '0.05', 'numitems'='1000000')
    COMMENT 'idxcomment';
  4. 匯入資料。

    1. 下載以下兩個JAR包,並將其存放在本地(假設存放路徑為D:\)。

    2. 執行以下命令將其上傳為JAR資源。

      -- 添加資源
      ADD JAR D:\odps-zorder-1.0-SNAPSHOT.jar;
      ADD JAR D:\odps-zorder-1.0-SNAPSHOT-jar-with-dependencies.jar;
    3. 建立函數,命令如下:

      CREATE FUNCTION zorder AS 'com.aliyun.odps.zorder.evaluateZValue2WithSize' USING ' odps-zorder-1.0-SNAPSHOT-jar-with-dependencies.jar';
    4. 使用函數寫入資料至分區測試表scope_zorder_pt。

      -- ORDER BY放開LIMIT限制
      SET odps.sql.validate.orderby.limit=false;
      
      --寫入資料
      INSERT OVERWRITE TABLE scope_zorder_pt PARTITION (ds='20241115') SELECT *,zorder(HASH(phone), 100000000, HASH(card), 100000000) AS zvalue FROM scope_tmp ORDER BY zvalue;

      Logview中Json Summary出現如下關鍵字則說明Bloomfilter index彙總成功:

      image

  5. 查詢資料。

    SET odps.sql.enable.bloom.filter.index=true;  
    SELECT * FROM scope_zorder_pt WHERE card='073415764266290' AND ds='20241115';

    返回結果如下:

    +-------------+-----------------+----------------+------------+---------------------+------------+
    | phone       | card            | machine        | geohash    | zvalue              | ds         |
    +-------------+-----------------+----------------+------------+---------------------+------------+
    | 1576426**** | 073415764266290 | 51133960245770 | fWbDDsf    | 3590549286038929408 | 20241115   |
    +-------------+-----------------+----------------+------------+---------------------+------------+

    Logview中顯示如下資訊,表示Bloomfilter index已經生效,攜帶bf尾碼的是虛擬表,對應Bloomfilter index檔案。

    image