背景介紹
在交互式分析場景下,很多時候除了固定字段之外沐鼠,還會有一些動態(tài)字段的需求。比如叹谁,在游戲場景下饲梭,需要動態(tài)存儲用戶每個游戲的play時長。
這種場景下焰檩,我們希望在一張表中同時存儲固定字段和動態(tài)字段的信息排拷,并且可以高效地使用動態(tài)字段做過濾查詢。
Map類型使用示例
CREATETABLEuser_game_play
(
? ??mid UInt64,
?? buvidString,
? ??game_play_durationMap(String, UInt32),
?? log_dateString
)
ENGINE=MergeTree()
PARTITION BY log_date
ORDERBYmid;?
insert into user_game_play values (1, '123',?map('王者榮耀',3600, 'FGO', 1800), '2021-11-14');
SELECT game_play_duration['王者榮耀'] AS duration?FROM test.user_game_play
┌─duration─┐
│????3600 │
└──────────┘
Map類型的取值實現(xiàn)
ClickHouse從v21.1.2.15-stable版本開始支持Map類型(詳見PR#1586)锅尘,其讀取key對應(yīng)value的實現(xiàn)邏輯大致如下:
1. 內(nèi)部用兩個數(shù)組(ColumnArray)分別存儲key和value值,我們分別稱之為 key_array和value_array布蔗。
2. 對于Map的取值操作(即map[‘key’]操作)藤违,先在從key_array中找到要找的key的下標(biāo),然后根據(jù)這個下標(biāo)到value_array里獲取對應(yīng)的值纵揍。
具體實現(xiàn)細(xì)節(jié)詳見源碼FunctionArrayElement::executeMap.
從上述分析可知,Map類型的工作方式本質(zhì)上和用兩個數(shù)組分別存儲key和value的方式是一樣的。只是在功能上做了封裝恩掷,提高了用戶使用的便捷性帚桩,但在性能上并沒有變化。
Map類型的跳數(shù)索引
索引類型
為了提升map操作的性能吧雹,我們在社區(qū)版本的Map類型基礎(chǔ)上骨杂,給其加上了多種類型的skipping
index,包括 bloom_filter 雄卷,tokenbf_v1搓蚪,ngrambf_v1
上面這三種skipping index本質(zhì)上都是用bloom filter存儲每個索引粒度的索引值。其中丁鹉,tokenbf_v1和ngrambf_v1只支持String類型妒潭,bloom_filter可支持各種類型悴能。
1. ngrambf_v1是對字符串中固定長度的substring做bloom filter存儲和檢索。
2. tokenbf_v1是對由非字母數(shù)字符號分隔開的token做bloom filter存儲和檢索雳灾。
3. bloom_filter則是直接對字段取值做bloom filter存儲和檢索漠酿。
Map類型的跳數(shù)邏輯
在數(shù)據(jù)寫入到Map類型字段時,所有的key會被抽取出來生成每個索引粒度對應(yīng)的bloom filter谎亩。
對于針對Map類型字段的過濾條件炒嘲,如:
? ??where game_play_duration[‘王者榮耀’] >= 1800 and game_play_duration[‘王者榮耀’] <=3600
會做以下處理:
1. 從filtering condition中提取map的key。
2. 分析過濾操作符(如 = , >=, <=, >, <, like , in , not in)团驱,如果該過濾條件在map不包含對應(yīng)key時不可能成立摸吠,則利用bloom filter過濾掉不可能包含對應(yīng)key的數(shù)據(jù)塊(索引粒度)。
具體實現(xiàn)細(xì)節(jié)詳見源碼PR #28634嚎花。
添加索引示例
CREATETABLEuser_game_play
(
? ??mid UInt64,
? ??buvidString,
? ??game_play_durationMap(String, UInt32),
? ??log_dateString,
? ??Index idx game_play_duration TYPE bloom_filter GRANULARITY 2,
)
ENGINE=MergeTree()
PARTITION BY log_date
ORDERBY mid;
影響跳數(shù)效果的因素
在我們的性能測試中寸痢,給Map類型添加skipping index可以收獲的性能提升差異很大。
效果好的case可以十幾到幾十倍的性能提升紊选,而效果不好的則沒有明顯提升啼止。
跳數(shù)索引的過濾效果和兩個數(shù)據(jù)特性相關(guān):
1. 索引值的cardinality:這個比較好理解,當(dāng)索引值cardinality很斜铡(比如性別献烦,可取值只有男和女),那么過濾效果通常有限卖词。
2. 索引值的分布是否聚集:ClickHouse的跳數(shù)索引和主鍵索引一樣巩那,也是稀疏索引。當(dāng)索引值分布非常離散時此蜈,即使包含查詢值的記錄占比很小即横,但可能每個數(shù)據(jù)塊(索引粒度)都包含查詢值,那么所有數(shù)據(jù)都需要讀進(jìn)內(nèi)存做過濾判斷裆赵。
Map相關(guān)函數(shù)
ClickHouse社區(qū)版本中已經(jīng)實現(xiàn)了一些map類型相關(guān)的函數(shù)东囚,包括:
1. map : 基于傳入的鍵值對生成Map類型對象。
2. mapKeys : 獲取map對象的所有keys战授。
3. mapValues : 獲取map對象的所有values
4. mapContains : 檢查map對象是否包含指定的key页藻。
更多map相關(guān)函數(shù)細(xì)節(jié)詳見這里。
另外植兰,我們添加了兩個map函數(shù)mapContainsKeyLike和mapExtractKeyLike(已合并進(jìn)社區(qū)版本份帐,詳見這里) 。其中mapContainsKeyLike函數(shù)支持通過tokenbf_v1索引進(jìn)行跳數(shù)過濾钉跷。