全部產品
Search
文件中心

Hologres:列式JSONB

更新時間:Sep 03, 2024

為了提升JSONB資料的查詢效率,Hologres從 V1.3版本開始支援對於JSONB類型開啟列式儲存最佳化,能夠降低JSONB資料的儲存大小並加速查詢。本文將會為您介紹Hologres中列式JSONB的使用。

列式JSONB原理介紹

如下圖所示開啟JSONB列式儲存最佳化後,系統會在底層自動將JSONB的列轉換為強Schema的列式儲存,查詢JSONB中某一個Value時就可以直接命中指定列,從而提升查詢效能。同時因為JSONB中的Value是按列式儲存的,在儲存層可以達到像普通結構化資料一樣的儲存和壓縮效率,從而有效降低儲存,實現降本增效。

說明

JSONB列式儲存最佳化功能對JSON類型資料不適用,實際使用過程中請不要對JSON類型開啟列式儲存最佳化。

image

使用限制

  • 僅Hologres V1.3及以上版本支援JSONB類型開啟列式儲存,建議將Hologres執行個體版本升級至1.3.37及以上版本再開始使用列式JSONB功能,會擷取更好的效能和更優的體驗。升級請使用自助升級或加入即時數倉Hologres交流群申請升級執行個體,詳情請參見如何擷取更多的線上支援?

  • JSONB的列存最佳化僅能用於列存表,行存表暫不支援,並且至少1000條資料才會觸發列存最佳化。

  • 當前僅支援如下操作符的列式儲存最佳化,並且如果查詢中使用不支援的操作符,反而可能會導致查詢效能下降。

    操作符

    右操作資料類型

    描述

    操作與結果

    ->

    text

    通過鍵獲得JSON對象域。

    • 操作樣本:

      select '{"a": {"b":"foo"}}'::json->'a'

    • 返回結果:

      {"b":"foo"}

    ->>

    text

    以TEXT形式獲得JSON對象域。

    • 操作樣本:

      select '{"a":1,"b":2}'::json->>'b'
    • 返回結果:

      2

列式JSONB使用

開啟列式JSONB

通過以下語句對某張表的某個JSONB列開啟JSONB列存最佳化。

-- 開啟xx表的xx列的JSONB列式儲存最佳化
ALTER TABLE <table_name> ALTER COLUMN <column_name> SET (enable_columnar_type = ON);

table_name為表名稱;column_name為列名稱。

重要
  • 開啟JSONB列存最佳化後,系統在Compaction時將歷史資料都轉為列存,待Compaction完畢即完成歷史資料的列存化。

  • Compaction會消耗系統資源(比如記憶體),建議該操作在業務低峰期操作。可以使用vacuum table_name;命令強制觸發Compaction操作,待vacuum命令執行完畢,Compaction操作就執行完畢了。

  • Compaction完成後新寫入的資料會按照列存儲存。

開啟Decimal類型推導

重要

開啟Decimal類型推導前,請確保已開啟JSONB列存最佳化。

Hologres從 V2.0.11版本開始,支援將DECIMAL類型的資料進行列存最佳化。例如如下的JSON資料:

{
  "name":"Mike",
  "statistical_period":"2023-01-01 00:00:00+08",
  "balance":123.45
}

balance的資料在開啟Decimal推導後,也支援按照列存最佳化。開啟方法如下:

-- 開啟xx表的xx列的Decimal列存最佳化
ALTER TABLE <table_name> ALTER COLUMN <column_name> SET (enable_decimal = ON);

table_name為表名稱;column_name為列名稱。

查看某張表的列式JSONB開啟情況

通過以下語句查看某張表的列式JSONB開啟情況。

  • Hologres V1.3.37及以上版本支援如下命令。

    說明

    該命令在Hologres V2.0.17及以下版本只支援看publicSchema下的表,從2.0.18版本開始支援查看其他Schema下的表開啟情況。

    --2.0.17及以下版本僅支援查看public schema的表,2.0.18版本可以查詢其他schema的表
    SELECT * FROM hologres.hg_column_options WHERE schema_name='<schema_name>' AND table_name = '<table_name>';

    其中schema_name為Schema名稱,table_name為表名稱。

  • Hologres V1.3.10~V1.1.36版本使用如下命令。

    SELECT DISTINCT
        a.attnum as num,
        a.attname as name,
        format_type(a.atttypid, a.atttypmod) as type,
        a.attnotnull as notnull, 
        com.description as comment,
        coalesce(i.indisprimary,false) as primary_key,
        def.adsrc as default,
        a.attoptions
    FROM pg_attribute a 
    JOIN pg_class pgc ON pgc.oid = a.attrelid
    LEFT JOIN pg_index i ON 
        (pgc.oid = i.indrelid AND i.indkey[0] = a.attnum)
    LEFT JOIN pg_description com on 
        (pgc.oid = com.objoid AND a.attnum = com.objsubid)
    LEFT JOIN pg_attrdef def ON 
        (a.attrelid = def.adrelid AND a.attnum = def.adnum)
    WHERE a.attnum > 0 AND pgc.oid = a.attrelid
    AND pg_table_is_visible(pgc.oid)
    AND NOT a.attisdropped
    AND pgc.relname = '<table_name>' 
    ORDER BY a.attnum;

    其中table_name為表名稱。

  • 樣本返回結果。

    返回結果可以看到某個列的attoptionsoption屬性為enable_columnar_type = ON,則表示已經配置成功。

    image

關閉列式JSONB

通過以下命令關閉某張表的某個列的JSONB列存最佳化。

-- 關閉xx表的xx列的JSONB列式儲存最佳化
ALTER TABLE <table_name> ALTER COLUMN <column_name> SET (enable_columnar_type = OFF);

table_name為表名稱;column_name為列名稱。

重要
  • 關閉JSONB列存最佳化後,系統在Compaction時將歷史資料都轉為標準的JSONB儲存方式,待Compaction完畢即完成歷史資料的轉換。

  • Compaction會消耗系統資源(比如記憶體),建議該操作在業務低峰期操作。可以使用vacuum table_name;命令強制觸發Compaction操作,待vacuum命令執行完畢,Compaction操作就執行完畢了。

  • Compaction完成後,新寫入的資料會按照JSONB格式儲存。

關閉Decimal類型推導

通過以下命令關閉某張表的某個列的Decimal類型列存最佳化推導。

-- 關閉xx表的xx列的Decimal列存最佳化
ALTER TABLE <table_name> ALTER COLUMN <column_name> SET (enable_decimal = OFF);

table_name為表名稱;column_name為列名稱。

說明

Decimal類型推導關閉後,會立刻觸發Compaction,將原來已經列存最佳化後的Decimal類型的資料轉換為原有模式。

設定Bitmap索引

在Hologres中,bitmap_columns屬性指定位元影像索引,是資料存放區之外的獨立索引結構,以位元影像向量結構加速等值比較情境,能夠對檔案塊內的資料進行快速的等值過濾,適用於等值過濾查詢的情境。Hologres從V2.0版本開始支援對開啟了列存的JSONB設定Bitmap索引。開啟列存JSONB後,系統會解析出int、int[]、bigint、bigint[]、text、text[]、jsonb這7種資料類型。開啟Bitmap索引後,系統會對推導成int、int[]、bigint、bigint[]、text、text[]類型的資料建立Bitmap索引。

使用文法如下:

call set_table_property('<table_name>', 'bitmap_columns', '[<columnName>{:[on|off]}[,...]]');

參數說明:

參數

說明

table_name

表名稱。

columnName

列名稱。

on

當前欄位開啟位元影像索引。

重要

僅支援開啟了列存的JSONB設定Bitmap索引。

off

當前欄位關閉位元影像索引。

使用樣本

  1. 建立表。

    DROP TABLE IF EXISTS user_tags;
    
    -- 建立資料表
    BEGIN;
    CREATE TABLE IF NOT EXISTS user_tags (
        ds timestamptz,
        tags jsonb
    );
    COMMIT;
  2. 開啟tags列的JSONB列存最佳化。

    ALTER TABLE user_tags ALTER COLUMN tags SET (enable_columnar_type = ON);
  3. 查看JSONB列式儲存開啟情況。

    select * from hologres.hg_column_options where table_name = 'user_tags';

    如下返回結果中可以看到tags行的options屬性是enable_columnar_type = on,表示已經配置成功。

     schema_name | table_name | column_id | column_name |       column_type        | notnull | comment | default |          options
    -------------+------------+-----------+-------------+--------------------------+---------+---------+---------+---------------------------
     public      | user_tags  |         1 | ds          | timestamp with time zone | f       |         |         |
     public      | user_tags  |         2 | tags        | jsonb                    | f       |         |         | {enable_columnar_type=on}
    (2 rows)
  4. 匯入資料。

    INSERT INTO user_tags (ds, tags)
    SELECT
        '2022-01-01 00:00:00+08'
        , ('{"id":' || i || ',"first_name" :"Sig",  "gender" :"Male"}')::jsonb
    FROM
        generate_series(1, 10001) i;
  5. (可選)強制觸發資料落盤。

    寫入資料後,系統會在資料落盤時進行JSONB的列存最佳化,為了儘快看到效果,此處使用如下後台命令,強制觸發資料落盤。

    VACUUM user_tags;
  6. 範例查詢。

    使用如下SQL查詢id10first_name

    SELECT
        (tags -> 'first_name')::text AS first_name
    FROM
        user_tags
    WHERE (tags -> 'id')::int = 10;
  7. 通過執行計畫檢查列存最佳化是否使用。

    -- 顯示詳細的統計資訊
    SET hg_experimental_show_execution_statistics_in_explain = ON;
    -- 查看執行計畫
    EXPLAIN ANALYZE
    SELECT
        (tags -> 'first_name')::text AS first_name
    FROM
        user_tags
    WHERE (tags -> 'id')::int = 10;

    結果中有columnar_access_used,表示JSONB使用了列存最佳化。

    image

  8. 針對步驟6中的查詢,還可以對tags設定Bitmap索引,以提升針對某個key等值查詢的效率,設定方法如下。

    call set_table_property('user_tags', 'bitmap_columns', 'tags');
  9. 通過執行計畫檢查Bitmap索引是否生效。

    -- 查看執行計畫
    EXPLAIN ANALYZE
    SELECT
        (tags -> 'first_name')::text AS first_name
    FROM
        user_tags
    WHERE (tags -> 'id')::int = 10;

    返回結果如下:image..png

    結果中有bitmap_used,表示使用了Bitmap索引。

列式JSONB不推薦的使用情境

使用列式JSONB不僅會降低儲存,還會顯著提升查詢效率。但是列式JSONB並不是所有情境都適用,以下情境不建議使用,否則會事倍功半。

查詢會帶出完整JSONB列

Hologres的列式JSONB方案對於大部分使用情境都有比較好的最佳化效果,需要注意的是:對於查詢結果需要帶出完整JSONB列的情境,效能相較於直接儲存原始格式的JSONB會有降低,比如以下SQL:

--建表DDL
CREATE TABLE TBL(key int, json_data jsonb); 
SELECT json_data FROM TBL WHERE key = 123;
SELECT * FROM TBL limit 10;

原因在於底層已經將JSONB資料轉成了列式儲存,所以當需要查詢出完整JSON資料的時候,就需要將那些已經列式儲存的資料再重新拼裝成原來的JSONB格式:

image

這個步驟就會產生大量的IO以及轉換開銷,如果涉及到的資料量很大,列數又很多,甚至可能成為效能瓶頸,所以此情境下建議不要開啟列式最佳化。

極稀疏的JSONB資料

當Hologres列式化JSONB資料遇到稀疏的欄位時,Hologres會將這部分欄位合并至一個叫做holo.remaining的特殊列中,以此來避免列數膨脹的問題。所以如果JSONB資料包含的都是稀疏欄位,比如極端情況下每個欄位都只會出現一次,那麼列式化將不會起效,因為所有欄位都是稀疏的,那麼所有欄位都會合并至holo.remaining欄位,等於沒有進行列式化,這種情況下不會有查詢效能的提升。

包含複雜嵌套結構的JSONB資料

如下JSONB資料的根節點就是一個數組,且該數組中存放的是非同構的JSONB資料,當前Hologres在列式化JSONB資料的時候,遇到類似複雜的嵌套結構,會將這部分資料退化成一列,所以此JSONB資料開啟列式JSONB最佳化,將不會帶來明顯的查詢效能收益。

'[
  {"key1": "value1"}, 
  {"key2": 123},
  {"key3": 123.01}
]'

列式JSONB最佳實務

慢查詢診斷

如果開啟列式JSONB後,發現查詢效能反而比不開啟效能還要差很多,首先排查查詢是否帶出了完整JSONB列,如果SQL過於複雜,可以使用Explain Analyze方式來診斷,SQL命令樣本如下:

CREATE TABLE TBL(key int, json_data json); --建表DDL
ALTER TABLE TBL ALTER COLUMN json_data SET (enable_columnar_type = on);
Explain Analyze SELECT json_data FROM TBL WHERE key = 123;

Explain Analyze的結果中會有Hint的資訊,如果在Hint資訊中有以下內容則代表查詢帶出了完整的JSONB列,導致了效能的退化:

Column 'json_data' has enabled columnar jsonb, but the query scanned the entire Jsonb value

更優的SQL寫法

  • 將JSONB欄位資料轉成TEXT格式有不同的寫法,但是使用->>操作符的效能會更好,比如要擷取json_data列中的name屬性:

    --效能更好
    SELECT json_data->>'name' FROM tbl; 
    --效能一般
    SELECT (json_data->'name')::text FROM tbl;
  • 如果JSON的某個欄位中儲存的是TEXT數組,需要判斷數組中是否包含特定值,建議使用以下寫法:

    SELECT key FROM tbl WHERE jsonb_to_textarray(json_data->'phones') && ARRAY['123456'];

常見問題

開啟列存化後為什麼儲存上漲?

開啟列式JSONB最佳化後,原JSONB資料中的欄位名都不會再儲存了,而只需儲存每個欄位對應的具體值,且列式化後每列的資料類型都是一樣的,列式儲存能有比較好的資料壓縮率,理論上資料的儲存空間會有明顯的下降。

但如果JSONB資料中的欄位比較稀疏,列數膨脹比較厲害,那麼列式化後的每一列都會帶來額外的儲存開銷(列的統計資訊、索引等),且如果列式化後每一列的類型都是TEXT類型,壓縮效果就不會很好。所以實際的儲存壓縮效率與實際業務的資料有關(比如稀疏性等),不一定所有的資料都有很好的壓縮效果。